K&R中快速排序程序的核心思想:
如图所示,将未排序的元素根据一个作为划分基准的“主元”(就是图中的65)分成两个子序列,一个子序列中的元素都小于主元,而另一个的元素均大于主元。然后再次递归,分别对两个子序列中的元素进行排序。归纳起来,就是采用分而治之的方法减小问题的规模,再逐个击破。
选主元pivot
本程序所选择的主元是在确定线性表的头尾之后(也就是确定left,right这两个指针),处于中间位置的数字。比如初始调用快速排序函数时,传入的参数中的left和right分别为0和7,那么其中间位置即为(left + right) / 2=3,也就是如下程序的主函数中,数组num里面的元素数字4。
每次调用qsort函数后都把将去划分子集的元素“藏在”最左边
原因:
为了后续操作简便。通过指针i,last从左到右遍历除了“主元”外的每个元素,并将它们与主元进行比较,最终划分成为两个子集,一个子集里都是小于主元的数字,另一个则包含的都是大于主元的数字。在这个过程中将所有小于主元的数字都尽量向前推。
最终last指针指向的位置及其之前的数字均小于主元(也就是说以主元为边界分别将原序列划分成大于和小于主元两个子序列),此时将主元位置直接直接放在last指针所指的位置上,之后就不用再对其位置进行改动了。所以说这种排序方法相对于插入排序的优势就是每次恢复划分子集元素的位置后都无需更改,十分的方便。如果不将其先藏在最左或者最右边,就很难出现这样的效果。
——————————————————————————2022.5.19更新
所以说,快速排序的灵魂就在于这段语句
swap(v, left, (left + right) / 2);
选择中间位置的元素作为主元。如果没有这段语句,则表明我们每次选的主元都是最左边的元素。这会导致一种可怕的情况出现。对于一组从大到小的数字(sorted),每次选最左边的元素为主元。这将导致划分子集的步骤变得没有意义。因为在这过程中last的值始终等于left的值,而且没有swap操作。直观的是小于主元的子集为空。也就意味着
qsort(v, left, last - 1);
这段语句相当于没有!!!
而且,函数中比较的次数也极为糟糕。假设刚开始比较n次,那么总共需要比较n+n-1+n-2+……+1=n(n+1)/2,时间复杂度O(n^2)。
每次递归只有一个元素不参与比较,这大大增加了递归调用的次数,从而使得堆栈溢出的可能性大大增加。所以说,
swap(v, left, (left + right) / 2);
该语句的出现很大程度上减少这种情况出现的概率。为了进一步减少溢出情况的出现。我们可以将上述语句改写成
swap(v, left, left+(right-left)/2);
因为当待排序的数字很多时,left和right就有可能很大,那么两者之和就可能超过对于该类型数据的规定范围,导致溢出。
递归对两个子序列进行同样的操作
如下面的程序段所示
qsort(v, left, last - 1);
qsort(v, last + 1, right);
每次递归调用时,都会将参数传递给
void qsort(int v[], int left, int right)
所以在递归调用之后,左右边界都可能发生变化。
限制条件:
if (left >= right)
return;
如果左边界位置在右边界之后,那么立即跳出所调用的函数。
完整程序:
该函数以递增的顺序对v[left]…v[right]进行排序。
void qsort(int v[], int left, int right)
{
int i, last;
void swap(int v[], int i, int j);
if (left >= right)
return;
swap(v, left, (left + right) / 2);//将划分子集的元素移动到v[0]
last = left;//记录划分子集元素所在的位置,方便在划分子集后直接将其放入到指定位置
//last所指的位置将来要放入之前选定的划分子集的元素
for (i = left + 1; i <= right; i++)//划分子集
if (v[i] < v[left])//其余元素与所选定的划分子集元素进行比较
swap(v, ++last, i);
swap(v, left, last);//恢复划分子集的元素
qsort(v, left, last - 1);
qsort(v, last + 1, right);
}
void swap(int v[], int i, int j)
{
int temp;
temp = v[i];
v[i] = v[j];
v[j] = temp;
}
int main()
{
int i = 0;
int num[] = { 3,6,2,4,1,42,32,10 };
qsort(num, 0, 7);
while(i<8)
printf("%d ", num[i++]);
return 0;
}