排序思想:
快速排序是对冒泡排序的一种改进。其基本思想是基于分治法的:在待排序表[1…n]中任取一个元素pivot作为基准,通过一趟排序将待排序表划分为独立的两部分L[1…k-1]和L[k+1…n],使得L[1...k-1]中所有元素小于等于pivot,L[k+1…n]中所有元素大于等于pivot,则pivot放在了其最终位置L(k)上,这个过程称为一趟快速排序。而后分别递归地对两个子表重复上述过程,直至每部分内只有一个元素或空为止,即所有元素放在了其最终位置上。
首先假设划分算法已知,记为Partition(),返回的是上述中的k,注意到L(k)已经在最终的位置,所以可以先对表进行划分,而后对两个表调用同样的排序操作。因此可以递归地调用快速排序算法进行排序,具体的程序结构如下:
void QuickSort(ElemType A[], int low, int high)
{
if(low < high) // 边界条件,即递归跳出的条件
{
// Partition()就是划分操作,将表A[low...high]划分为满足上述条件的两个子表
int pivotpos = Partition(A, low, high); // 划分
QuickSort(A, low, pivotpos - 1); //依次对两个子表进行递归排序
QuickSort(A, pivotpos + 1, high);
}
}
从上面的代码也不难看出快速排序算法的关键在于划分操作,同时快速排序算法的性能也主要取决于划分操作的好坏。
一趟快速排序的算法过程是:
1)设置两个索引low、high,排序开始的时候:low=0,high=n-1;
2)以第一个数组元素作为枢轴值,赋值给pivot,即pivot = A[low];
3)从high开始向前搜索,即由后开始向前搜索(high--),找到第一个小于pivot的值A[high],将A[high]的值赋给A[low] ( A[low] = A[high] );
4)从low开始向后搜索,即由前开始向后搜索(low++),找到第一个大于pivot的A[low],将A[low] 的值赋给 A[high] (A[high] = A[low] );
5)重复第3、4步,直到 low=high;(3,4步中,没找到符合条件的值,即3中A[high]大于等于pivot,4中A[low]小于等于pivot的时候改变high、low 的值,使得hihg = high-1,low = low+1,直至找到为止。找到符合条件的值,进行交换的时候low,high 指针位置不变。另外,low==high 这一过程一定正好是low++或high--完成的时候,此时令循环结束)。
将上述过程转化为代码:
int Partition(ElemType A[], int low, int high)
{
ElemType pivot = A[low]; // 将当前表中第一个元素设为枢轴值,对表进行划分
while(low < high) // 循环跳出条件
{
while(low < high && A[high] >= pivot)
--high;
A[low] = A[high]; // 将比枢轴值小的元素移动到左端
while(low < high && A[low] <= pivot)
++low;
A[high] = A[low]; // 将比枢轴大的元素移动到右端
}
A[low] = pivot; // 枢轴元素存放到最终位置
return low; // 返回存放枢轴的最终位置
}
排序演示:
设待排序表A[8] = {4,6,2,3,1,5,7,9};
1)以第一个数组元素作为枢轴值,赋值给pivot,即 pivot = A[0] = 4;
2)从high开始向前搜索,即由后开始向前搜索(high--),循环程序,找到第一个小于pivot的值 A[4] = 1,将A[4] = 1 赋给 A[0] (A[0] = A[4]);
3) 从low开始向后搜索,即由前开始向后搜索(low++),找到第一个大于pivot的A[1] = 6,将A[2] = 6 赋给 A[4] (A[4] = A[2]);
4) 继续从high开始向前搜索,找到第二个小于pivot的值 A[3] = 3,赋值给A[low], A[1] = A[3] = 3
5)然后再从low开始向后搜索,直到low = high ,没有找到大于pivot 的元素,循环停止;此时low = high = 3;
6) 执行A[low] = pivot,将枢轴元素存放到最终位置
7)此时,一趟快速排序执行完毕:枢轴值左边的表值都不大于4,右边的表值都不小于4;
8)再分别对两个子表进行递归快速排序,知道表中所有元素都排好序。
算法复杂度分析:
空间复杂度:
递归造成的栈空间的使用,最好情况,递归树的深度为log2n,其空间复杂度也就为O(logn),最坏情况,需要进行n-1递归调用,其空间复杂度为O(n),平均情况,空间复杂度也为O(logn)。
时间复杂度:
在最优情况下,Partition每次都划分得很均匀,如果排序n个关键字,其递归树的深度就为(log2n)+1(以2为底;向下取整),即仅需递归log2n次(以2为底),需要时间为T(n)的话,第一次Partition应该是需要对整个数组扫描一遍,做n次比较。然后,获得的枢轴将数组一分为二,那么各自还需要T(n/2)的时间,也就是说,在最优的情况下,快速排序算法的时间复杂度为O(logn)。
在最坏的情况下,待排序的序列为正序或者逆序,每次划分只得到一个比上一次划分少一个记录的子序列,注意另一个为空。如果递归树画出来,它就是一棵斜树。最终其时间复杂度为O(n^2)。
平均情况下,复杂度为O(nlogn)