重在递归的理解

算法步骤:
(1):选取主元(数组中随机一个即可,为简化程序,以下选取数组开头为主元);
(2):小于等于主元的放左边,大于等于主元的放右边;
(3):分别对左边,右边递归,即重复(1)(2)步。

void QuickSort(int array[], int left, int right)
{
    if (left >= right)
        return;

    int base = array[left];
    int i = left;
    int j = right;

    while (i < j)  // 小于等于主元的放左边,大于等于主元的放右边
    {
        while (i < j && array[j] >= base)
            j--;
        while (i < j && array[i] <= base)
            i++;
        swap(array[i], array[j]);
    }

    swap(array[left], array[i]);

    QuickSort(array, left, i - 1);  // 分别对左边,右边递归
    QuickSort(array, i + 1, right);
}

有必要对以上代码的细节方面作个说明:
(1):递归结束条件

if (left >= right)
    return;

结束条件应该是大于等于,而不仅仅是等于。

考虑这样的情况:在一段区间中,取开头作为主元,很巧的是该区间除开头(即主元),其它所有的数都大于主元,于是分别对两边递归:

QuickSort(array, left, i - 1);
QuickSort(array, i + 1, right);

第一个函数的后两个参数: left 与 i - 1 ,前者大,后者小。
(2):等于主元的数如何放置

while (i < j && array[j] >= base)
    j--;
while (i < j && array[i] <= base)
    i++;

本文的代码对等于主元的数采取的策略是:保持原位不动。

我们试着改变下代码:

while (i < j && array[j] > base)  // 去掉等号
    j--;
while (i < j && array[i] <= base)
    i++;

我们把等于主元的数全部移动到左边。这么做,程序依旧运行正常。

我们再试着改下代码:

while (i < j && array[j] > base)  // 去掉等号
    j--;
while (i < j && array[i] < base)  // 去掉等号
    i++;

左边等于主元的数移到右边,右边等于主元的数移到左边。

这样的做法有点诡异,似乎除了多了无用的交换操作耗时外,并不会对程序造成其它的影响。

但是请考虑这样的情况:

3 作为主元,i 和 j 指向的位置如上图,接着交换两个位置的值,

while (i < j)  
{
    while (i < j && array[j] > base)  // 去掉等号
        j--;
    while (i < j && array[i] < base)  // 去掉等号
        i++;
    swap(array[i], array[j]);  // 交换两值
}

程序会永远在这步进入死循环。

有的人提议:只要在 swap 后再加一句代码就行了嘛。如下:

while (i < j)  
{
    while (i < j && array[j] > base)  // 去掉等号
        j--;
    while (i < j && array[i] < base)  // 去掉等号
        i++;
    swap(array[i], array[j]);  // 交换两值
    i++; j++;  // 新增的代码
}
swap(array[left], array[i]);

但这么做,又引出了新的麻烦。

试想,若 i 的下一个位置就是 j(即 i+1 等于 j),在 i++; j++; 后,while 语句停止,swap 语句交换了主元和位置 i 的值,问题来了,i 指向的值或许是大于主元的,这就造成了程序的逻辑错误。

(3):i 和 j 的操作顺序

while (i < j && array[j] >= base)
    j--;
while (i < j && array[i] <= base)
    i++;

为何 j 在前,i 在后?

其实这与我们选取的主元位置有关。程序中我们以数组首开头作为主元。

while (i < j)
{
    while (i < j && array[j] >= base)
        j--;
    while (i < j && array[i] <= base)
        i++;
    swap(array[i], array[j]);
}

swap(array[left], array[i]);

while 语句停止的条件正好是 i 等于 j,接下来交换主元与 array[i] ,值得我们注意的是, array[i] 被交换至左边,但它可以保证其值总是小于等于主元么?

简单推理之下,若我们让 j 先行,i 后走,就可以保证被交换前,其值总是小于等于主元的。反之,若误写了下面的代码,排序后的结果就会是错的。

while (i < j && array[i] <= base)  // i 在前
    i++;
while (i < j && array[j] >= base)  // j 在后
    j--;