簡體   English   中英

可以在沒有堆棧和遞歸的情況下在 C 中實現快速排序嗎?

[英]Can quicksort be implemented in C without stack and recursion?

我發現這篇文章如何在 c 中不使用堆棧進行迭代快速排序? 但建議的答案確實使用了內聯堆棧數組! (只允許恆定量的額外空間)

參考頁面中的代碼做出了大膽的聲明:

堆棧我的實現不使用堆棧來存儲數據...

然而,函數定義有許多自動存儲的變量,其中有 2 個具有 1000 個條目的數組,最終將使用固定但大量的堆棧空間:

//  quickSort
//
//  This public-domain C implementation by Darel Rex Finley.
//
//  * Returns YES if sort was successful, or NO if the nested
//    pivots went too deep, in which case your array will have
//    been re-ordered, but probably not sorted correctly.
//
//  * This function assumes it is called with valid parameters.
//
//  * Example calls:
//    quickSort(&myArray[0],5); // sorts elements 0, 1, 2, 3, and 4
//    quickSort(&myArray[3],5); // sorts elements 3, 4, 5, 6, and 7

bool quickSort(int *arr, int elements) {

  #define  MAX_LEVELS  1000

  int  piv, beg[MAX_LEVELS], end[MAX_LEVELS], i=0, L, R ;

  beg[0]=0; end[0]=elements;
  while (i>=0) {
    L=beg[i]; R=end[i]-1;
    if (L<R) {
      piv=arr[L]; if (i==MAX_LEVELS-1) return NO;
      while (L<R) {
        while (arr[R]>=piv && L<R) R--; if (L<R) arr[L++]=arr[R];
        while (arr[L]<=piv && L<R) L++; if (L<R) arr[R--]=arr[L]; }
      arr[L]=piv; beg[i+1]=L+1; end[i+1]=end[i]; end[i++]=L; }
    else {
      i--; }}
  return YES; }

縮進樣式非常混亂。 這是重新格式化的版本:

#define MAX_LEVELS  1000

bool quickSort(int *arr, int elements) {
    int piv, beg[MAX_LEVELS], end[MAX_LEVELS], i = 0, L, R;

    beg[0] = 0;
    end[0] = elements;
    while (i >= 0) {
        L = beg[i];
        R = end[i] - 1;
        if (L < R) {
            piv = arr[L];
            if (i == MAX_LEVELS - 1)
                return NO;
            while (L < R) {
                while (arr[R] >= piv && L < R)
                    R--;
                if (L < R)
                    arr[L++] = arr[R];
                while (arr[L] <= piv && L < R)
                    L++;
                if (L < R)
                    arr[R--] = arr[L];
            }
            arr[L] = piv;
            beg[i + 1] = L + 1;
            end[i + 1] = end[i];
            end[i++] = L;
        } else {
            i--;
        }
    }
    return YES;
}

請注意, 1000很大,但對於已經排序的中等大數組上的病理病例來說還不夠。 該函數對此類大小僅為 1000 的數組返回NO ,這是不可接受的。

對於算法的改進版本,更低的值就足夠了,其中較大的范圍被推送到數組中,並且循環在較小的范圍上進行迭代。 這確保了 N 個條目的數組可以處理一組 2 N個條目。 它在排序數組上仍然具有二次時間復雜度,但至少可以對所有可能大小的數組進行排序。

這是一個修改和檢測的版本:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#define MAX_LEVELS  64

int quickSort(int *arr, size_t elements) {
    size_t beg[MAX_LEVELS], end[MAX_LEVELS], L, R;
    int i = 0;

    beg[0] = 0;
    end[0] = elements;
    while (i >= 0) {
        L = beg[i];
        R = end[i];
        if (L + 1 < R--) {
            int piv = arr[L];
            if (i == MAX_LEVELS - 1)
                return -1;
            while (L < R) {
                while (arr[R] >= piv && L < R)
                    R--;
                if (L < R)
                    arr[L++] = arr[R];
                while (arr[L] <= piv && L < R)
                    L++;
                if (L < R)
                    arr[R--] = arr[L];
            }
            arr[L] = piv;
            if (L - beg[i] > end[i] - R) { 
                beg[i + 1] = L + 1;
                end[i + 1] = end[i];
                end[i++] = L;
            } else {
                beg[i + 1] = beg[i];
                end[i + 1] = L;
                beg[i++] = L + 1;
            }
        } else {
            i--;
        }
    }
    return 0;
}

int testsort(int *a, size_t size, const char *desc) {
    clock_t t = clock();
    size_t i;

    if (quickSort(a, size)) {
        printf("%s: quickSort failure\n", desc);
        return 1;
    }
    for (i = 1; i < size; i++) {
        if (a[i - 1] > a[i]) {
            printf("%s: sorting error: a[%zu]=%d > a[%zu]=%d\n",
                   desc, i - 1, a[i - 1], i, a[i]);
            return 2;
        }
    }
    t = clock() - t;
    printf("%s: %zu elements sorted in %.3fms\n",
           desc, size, t * 1000.0 / CLOCKS_PER_SEC);
    return 0;
}

int main(int argc, char *argv[]) {
    size_t i, size = argc > 1 ? strtoull(argv[1], NULL, 0) : 1000;
    int *a = malloc(sizeof(*a) * size);
    if (a != NULL) {
        for (i = 0; i < size; i++)
            a[i] = rand();
        testsort(a, size, "random");
        for (i = 0; i < size; i++)
            a[i] = i;
        testsort(a, size, "sorted");
        for (i = 0; i < size; i++)
            a[i] = size - i;
        testsort(a, size, "reverse sorted");
        for (i = 0; i < size; i++)
            a[i] = 0;
        testsort(a, size, "constant");
        free(a);
    }
    return 0;
}

輸出:

random: 100000 elements sorted in 7.379ms
sorted: 100000 elements sorted in 2799.752ms
reverse sorted: 100000 elements sorted in 2768.844ms
constant: 100000 elements sorted in 2786.612ms

這是一個更能抵抗病理情況的輕微修改版本:

#define MAX_LEVELS  48

int quickSort(int *arr, size_t elements) {
    size_t beg[MAX_LEVELS], end[MAX_LEVELS], L, R;
    int i = 0;

    beg[0] = 0;
    end[0] = elements;
    while (i >= 0) {
        L = beg[i];
        R = end[i];
        if (R - L > 1) {
            size_t M = L + ((R - L) >> 1);
            int piv = arr[M];
            arr[M] = arr[L];

            if (i == MAX_LEVELS - 1)
                return -1;
            R--;
            while (L < R) {
                while (arr[R] >= piv && L < R)
                    R--;
                if (L < R)
                    arr[L++] = arr[R];
                while (arr[L] <= piv && L < R)
                    L++;
                if (L < R)
                    arr[R--] = arr[L];
            }
            arr[L] = piv;
            M = L + 1;
            while (L > beg[i] && arr[L - 1] == piv)
                L--;
            while (M < end[i] && arr[M] == piv)
                M++;
            if (L - beg[i] > end[i] - M) {
                beg[i + 1] = M;
                end[i + 1] = end[i];
                end[i++] = L;
            } else {
                beg[i + 1] = beg[i];
                end[i + 1] = L;
                beg[i++] = M;
            }
        } else {
            i--;
        }
    }
    return 0;
}

輸出:

random: 10000000 elements sorted in 963.973ms
sorted: 10000000 elements sorted in 167.621ms
reverse sorted: 10000000 elements sorted in 167.375ms
constant: 10000000 elements sorted in 9.335ms

作為結論:

  • 是的,無需遞歸即可實現快速排序,
  • 不,沒有任何本地自動存儲就無法實現,
  • 是的,只需要恆定數量的額外空間,但這僅僅是因為我們生活在一個小世界,數組的最大大小受可用內存的限制。 大小為 64 的本地對象處理的數組大於 Internet 的大小,遠大於當前 64 位系統可以處理的大小。

顯然,這是可以實現的非遞歸快速僅有的額外空間不變量規定在這里 這建立在 Sedgewick 對快速排序的非遞歸公式的 工作之上。 它不是保留邊界值(低和高),而是執行線性掃描來確定這些邊界。

嗯,它可以,因為我在 fortran IV 中實現了快速排序(很久以前,並且在語言支持遞歸之前 - 這是一個賭注)。 但是,您確實需要某個地方(一個大數組可以)來記住您在執行個別工作時的狀態。

遞歸要容易得多......

根據定義,快速排序是一種“分而治之”的搜索算法,其思想是將給定的數組拆分為更小的分區。 所以你把問題分成子問題,這樣更容易解決。 在不使用遞歸的情況下使用快速排序時,您需要某種結構來存儲您當時不使用的分區。 這就是為什么帖子答案使用數組來使快速排序非遞歸的原因。

可以在沒有堆棧和遞歸的情況下在 C 中實現快速排序嗎?

快速排序需要從每個非平凡分區向前遵循兩條路徑:每個(子)分區的新分區。 有關先前分區的信息(結果分區之一的邊界)需要傳遞到每個新分區。 那么,問題是這些信息在哪里? 特別是,當程序在另一個分區上工作時,有關一個分區的信息在哪里?

對於串行算法,答案是信息存儲在堆棧或隊列或其中之一的功能等價物上。 總是,因為這些是我們為所需目的服務的數據結構的名稱。 特別是,遞歸是一種特殊情況,而不是替代方案。 在遞歸快速排序中,數據存儲在調用堆棧中。 對於迭代實現,您可以在正式意義上實現堆棧,但也可以使用簡單且相對較小的數組作為臨時堆棧。

但是堆棧和隊列等價物可以走得更遠。 例如,您可以將數據附加到文件中,以便稍后回讀。 您可以將其寫入管道。 您可以通過通信網絡將其異步傳輸給您自己。

如果你想發瘋,你甚至可以嵌套迭代來代替遞歸。 這將對可以處理的數組的大小強加一個硬上限,但並不像您想象的那么嚴格。 稍加注意並掌握一些技巧,您就可以使用 25 個循環嵌套處理十億個元素的數組。 如此深的巢穴雖然丑陋而瘋狂,但還是可以想象的。 人類可以手寫。 在這種情況下,一系列嵌套循環作用域及其塊作用域變量充當堆棧等效項。

因此,答案取決於“無堆棧”的確切含義:

  • 是的,您可以改用隊列,盡管它需要具有與要排序的元素大致相同的容量;
  • 是的,您可以使用數組或其他某種順序數據存儲來模擬正式的堆棧或隊列;
  • 是的,您可以將合適的堆棧等價物直接編碼到您的程序結構中;
  • 是的,您可能會想出其他更深奧的堆棧和隊列版本;
  • 但是不,如果沒有填充傳統上使用堆棧或堆棧等效項的多級數據存儲角色,您就無法執行快速排序。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM