C中的快速排序实现

时间:2016-10-10 03:31:45

标签: c algorithm sorting quicksort

目标:我尝试在C中实现快速排序。
问题:C的这种快速排序实现无限循环。我认为分区功能没问题,因为使用测试用例时,pivot(设置为索引0)总是移动到正确的位置。我不明白为什么快速排序功能最终不能达到基本情况。

此实施可能会出现什么问题?

# include <stdio.h>

// Swapping algorithm
void swap(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

// Partitioning algorithm
int partition(int *L, int left, int right){
    int pivot = L[0];

    while (right > left) {
            while (L[left] < pivot) {
                    left = left + 1;
            }
            while (L[right] > pivot) {
                    right = right - 1;
            }
            swap(&L[left], &L[right]);
    }
    swap(&pivot, &L[left]);
    return left;
}

// Quicksort recursion
void quicksort(int *L, int start, int end) {
    if (start >= end) {
            return;
    }
    else {
            int splitPoint = partition(L, start, end);
            quicksort(L, start, splitPoint-1);
            quicksort(L, splitPoint+1, end);
    }
}

int main() {
    int myList[] = {12, 43, -16, 0, 2, 5, 1, 13, 2, 2, -1};
    printf("UNSORTED LIST\n");
    int *pointer = myList;
    for (int i = 0; i < 10; i++) {
            printf("%d ", *(pointer+i));
    }
    quicksort(myList, 0, 9);
    printf("\nSORTED LIST\n");
    for (int i = 0; i < 10; i++) {
            printf("%d ", *(pointer+i));
    }
    printf("\n");
}

2 个答案:

答案 0 :(得分:2)

初始支点选择应为L[left]而不是L[0],不是吗?但是,这不是分区功能中唯一的问题。

此代码有效:

#include <stdio.h>

// Swapping algorithm
static inline
void swap(int *a, int *b)
{
    int temp = *a;
    *a = *b;
    *b = temp;
}

static void dump_list(const char *tag, int *ptr, int left, int right)
{
    printf("%15s [%d..%d]: ", tag, left, right);
    for (int i = left; i <= right; i++)
        printf(" %3d", ptr[i]);
    putchar('\n');
}

// Partitioning algorithm
static
int partition(int *L, int left, int right)
{
    int pivot = left;
    int p_val = L[pivot];

    while (left < right)
    {
        while (L[left] <= p_val)
            left++;
        while (L[right] > p_val)
            right--;
        if (left < right)
            swap(&L[left], &L[right]);
    }
    swap(&L[pivot], &L[right]);
    return right;
}

// Quicksort recursion
static
void quicksort(int *L, int start, int end)
{
    if (start >= end)
        return;
    //dump_list("PRE-PARTITION", L, start, end);
    int splitPoint = partition(L, start, end);
    //dump_list("POST-PARTITION", L, start, end);
    //printf("Split point: %d\n", splitPoint);
    quicksort(L, start, splitPoint - 1);
    quicksort(L, splitPoint + 1, end);
}

int main(void)
{
    int myList[] = {12, 43, -16, 0, 2, 5, 1, 13, 2, 2, -1};
    dump_list("UNSORTED LIST", myList, 0, 9);
    quicksort(myList, 0, 9);
    dump_list("SORTED LIST", myList, 0, 9);
}

它产生输出:

  UNSORTED LIST [0..9]:   12  43 -16   0   2   5   1  13   2   2
    SORTED LIST [0..9]:  -16   0   1   2   2   2   5  12  13  43

启用调试打印后,输出为:

  UNSORTED LIST [0..9]:   12  43 -16   0   2   5   1  13   2   2
  PRE-PARTITION [0..9]:   12  43 -16   0   2   5   1  13   2   2
 POST-PARTITION [0..9]:    2   2 -16   0   2   5   1  12  13  43
Split point: 7
  PRE-PARTITION [0..6]:    2   2 -16   0   2   5   1
 POST-PARTITION [0..6]:    1   2 -16   0   2   2   5
Split point: 5
  PRE-PARTITION [0..4]:    1   2 -16   0   2
 POST-PARTITION [0..4]:  -16   0   1   2   2
Split point: 2
  PRE-PARTITION [0..1]:  -16   0
 POST-PARTITION [0..1]:  -16   0
Split point: 0
  PRE-PARTITION [3..4]:    2   2
 POST-PARTITION [3..4]:    2   2
Split point: 4
  PRE-PARTITION [8..9]:   13  43
 POST-PARTITION [8..9]:   13  43
Split point: 8
    SORTED LIST [0..9]:  -16   0   1   2   2   2   5  12  13  43

答案 1 :(得分:1)

我很生气之前发布的代码都不正确,所以我写了一个快速排序并证明它是正确的。这比我想象的要困难得多。据我所知,下面的代码有效,我认为我的正确性证明是有效的:

#include <stdio.h>

void swap(int* x, int* y)
{
    int temp = *x;
    *x = *y;
    *y = temp;
}

/**
 * Assuming precondition (P) that `end - begin >= 2`, this function reorders the elements
 * of range [begin, end) and returns a pointer `ret` such that the following
 * postconditions hold:
 *   - (Q1): `ret > begin`
 *   - (Q2): `ret < end`
 * and, for some value `p` in [begin, end):
 *   - (Q3): all values in [begin, ret) are lower than or equal to `p`
 *   - (Q4): all values in [ret, end) are greater than or equal to `p`
 */
int* partition(int* begin, int* end)
{
    // These aliases are unnecessary but make the proof easier to understand.
    int* low  = begin;
    int* high = end;

    int pivot = *(low + (high - low)/2);

    // Loop invariants, all trivially verified at the start of the loop:
    //   - (A): values strictly to the left of `low` are lower than or equal to `pivot`
    //   - (B): there is at least one value at or to the right of `low` that is greater
    //     than or equal to `pivot`
    //   - (C): values at or to the right of `high` are greater than or equal to `pivot`
    //   - (D): there is at least one value strictly to the left of `high` that is lower
    //     than or equal to `pivot`
    //   - (E): `low <= high`
    //
    // The loop terminates because `high - low` decreases strictly at each execution of
    // the body (obvious).
    while (true)
    {
        // This loop terminates because of (B).
        while (*low < pivot)
            ++low;

        // Here, we have
        //   - (1): `*low >= pivot`
        //   - (2): `low <= high` because of (E) and (C)
        //   - properties (A) and (B) still hold because `low` has only moved
        //     past values strictly less than `pivot`

        // This loop terminates because of (D).
        do {
            --high;
        } while (pivot < *high);

        // Here, we have
        //   - (3): `*high <= pivot`
        //   - (4): by (C) which held before this loop, elements strictly to the
        //     right of `high` are known to be greater than or equal to `pivot`
        //     (but now (C) may not hold anymore)

        if (low >= high)
        {
            // Due to (1), (A) and (4), (Q3) and (Q4) are established with `pivot`
            // as `p`.
            // Clearly, (B) proves Q2.
            // See the rest of the answer below for a proof of (Q1).
            // This correctly finishes the partition.
            return low;
        }

        // We have `low < high` and we swap...
        swap(low, high);

        // ...and now,
        //   - by (1) and (4), invariant (C) is re-established
        //   - by (1), invariant (D) is re-established
        //   - (5): by (3), `*low <= pivot`

        ++low;
        // (A) already held before this increment. Thus, because of (5), (A)
        // still holds. Additionally, by (1), after the swap, (B) is
        // re-established. Finally, (E) is obvious.
    }
}

void qsort(int* begin, int* end)
{
    // Trivial base case...
    if (end - begin < 2)
        return;

    // ...therefore pre-condition (P) of `partition` is satisfied.
    int* p = partition(begin, end);

    // Thanks to postconditions (Q1) and (Q2) of `partition`, the ranges
    // [begin, p) and [p, end) are non-empty, therefore the size of the ranges
    // passed to the recursive calls below is strictly lower than the size of
    // [begin, end) in this call. Therefore the base case is eventually reached
    // and the algorithm terminates.

    // Thanks to postconditions (Q3) and (Q4) of `partition`, and by induction
    // on the size of [begin, end), the recursive calls below sort their
    // respective argument ranges and [begin, end) is sorted as a result.
    qsort(begin, p);
    qsort(p, end);
}

int main()
{
    int l[] = { 3, 1, 9, 6, 0, 7, 1, 7, 2, 2, 8 };

    size_t n = sizeof(l)/sizeof(int);

    qsort(l, l + n);

    for (size_t i = 0; i < n; ++i)
    {
        printf("%d, ", l[i]);
    }
}

为了证明 (Q1),我们必须证明 low 在到达 return 语句之前至少增加了一次。

如果在循环体的第一次执行期间到达了 return 语句,那么 low >= high 意味着嵌套循环中的 --high;++low; 语句必须已经其中至少执行了 end - begin 次。通过前置条件 (P),end - begin >= 2。通过 (D),循环递减 high 必须以 high >= begin 结束。因此,low 必须至少增加一次,证明 (Q1)。

否则,如果在循环体的后续执行期间到达 return 语句,则无条件 ++low; 语句证明 (Q1)。