快速排序 Quick Sort 的基本思想是:通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序的目的。
从字面上感觉不出它的好处来。假设现在要对数组{50,10,90,30,70,40,80,60,20}进行排序。我们通过代码的讲解采学习快速排序的精妙。
// 对顺序表L做快速排序
Void QuickSort(SqList *L)
{QSort(L, 1, L->length);
}
由于需要递归调用,因此我们外封装了一个函数。
// 对顺序表L中的子序列L->r[low..high]做快速排序
Void QSort(SqList *L, int low, int high)
{int pivot;if(low < high){pivot = Partition(L, low, high); // 将L->r[low..high]一分为二,算出枢轴值piovtQSort(L, low, pivot - 1); // 对低子表递归排序QSort(L, pivot+1, high); // 对高子表递归排序}
}
从这里,你应该能理解前面代码“QSort(L, 1, L->length);”中1和L->length代码的意思了,它就是当前待排序的序列最小下标值low和最大下标值high。
这一段代码的核心是“pivot=Partition(L,low,high);”在执行它之前,L.r的数组值为[50,10,90,30,70,40,80,60,20]。Partition函数要做的,就是先选取当中的一个关键字,比如选择第一个关键字50,然后想尽办法将它放到一个位置,使得它左边的值都比它小,右边的值比它大,我们将这样的关键字称为枢轴(pivot)。
在经过Partition(L,1,9)的执行之后,数组变成{20,10,40,30,50,70,80,60,90},并返回值5给pivot,数字5表明50放置在数组下标为5的位置。此时,计算机把原来的数组变成了两个位于50左和右小数组{20,10,40,30}和{70,80,60,90},而后的递归调用“QSort(L,1,5-1);"和“QSort(L,5+1,9);”语句,其实就是在对{20,10,40,30}和{70,80,60,90}分别进行同样的Partition操作,直到顺序全部正确为止。
到了这里,应该说理解起来还不算困难。下面我们就来看看快速排序最关键的Partition函数实现。
// 交换顺序表L中子表的记录,使枢轴记录到位,并返回其所在位置
// 此时在它之前(后)的记录均不大(小)于它。
int Partition(SqList *L,int low,int high)
{int pivotkey;pivotkey=L->r[low]; /*用子表的第一个记录作枢轴记录 */while(lowwhile(low < high && L->r[high] >= pivotkey)high--;swap(L, low, high); /* 将比枢轴记录小的记录交换到低端while(low r[low] <= pivotkey)low++;swap(L, low, high); /* 将比枢轴记录大的记录交换到高端 */}return low; /*返回枢轴所在位置 */
}
1、程序开始执行,此时low = 1, high = L, length = 9。第四行,我们将L.r[low] = L.r[1] = 50赋值给枢轴变量pivotkey;
2、第5-13行行为while循环,目前low = 1 < high = 9,执行内部语句。
3、第七行,L.r[high] = L.r[9] = 20 > pivotkey = 50,因此不执行第8行。
4、第9行,交换L.r[low]与L.r[high]的值,使得L.r[1] = 20, L.r[9] = 50。为什么要交换,就是因为通过第7行的比较知道,L.r[high]是一个比pivotkey = 50(即L.r[low])还要小的值,因此它应该交换到50的左侧;
5、第10行,当L.r[low] = L.r[1] = 20,pivotkey = 50, L.r[low] < pivotkey,因此第11行,low++,此时low = 2。继续循环,L.r[2] = 10 < 50,low++,此时low = 3。L.r[3] = 90 > 50,退出循环。
6、第12行,交换L.r[low] = L.r[3] 与 L.r[high] = L.r[9]的值,使得L.r[3] = 50,L.r[9] = 90。此时相当于将一个比50大的值90交换到了50的右边。注意此时low已经指向了3,。
7、继续第5行,因为low = 3 < high = 9,执行循环体。
通过这段代码的模拟,大家应该能够明白, Partition 函数,其实就是将选取的pivotkey 不断交换,将比它小的换到它的左边,比它大的换到它的右边,它也在交换中不断更改自己的位置,直到完全满足这个要求为止。