接上篇博文:
[C++]数据结构:排序算法Part1----冒泡排序、选择排序、插入排序、堆排序
5.快速排序:
快速排序的核心思想是分而治之算法。所谓的分而治之,简单来说就是把复杂问题分成几个子问题,然后分别解决小问题,最后再将解组合起来,得到原问题的解。
那么分而治之如何应用到排序算法中呢?
在快速排序中,n个元素被分成了了三段。左端left,右端right,和中段middle。中段仅包含一个元素,作为基准元素,左段的各元素都小于等于中段元素,右段元素都大于等于中段元素。middle元素被称为支点。
基本的操作流程大致如下:在待排序的n个元素中任意选择一个作为基准元素(通常取第一个),把该元素放入最终的位置上,数据序列被此元素划分成两部分,所有关键字比该元素关键字小的元素放置在前一部分,所有比它大的元素放在后一部分,这个过程称为一趟快速排序。对分成的两部分重复上述过程,直到每部分只有一个元素或空为止。
快排的源码如下:
#include <iostream>
using namespace std;
template <class T>
void show(T arr,int n){
for(int i =0;i<n-1;i++){
cout<<arr[i]<<",";
}
cout<<arr[n-1]<<endl;
}
void QuickSort( int a[], int l, int r )
{
show(a,9);
if (l>=r) return;
int i, j, temp;
temp = a[l];
i = l; j = r;
while (i<j) {
while(i<j&&temp<a[j])
j--;
a[i] = a[j];
while(i<j&&temp>a[i])
i++;
a[j] = a[i];
}
a[i] = temp;
QuickSort( a, l, i-1 );
QuickSort( a, i+1, r );
}
void main()
{
int inputNumber[]={2,7,5,9,1,4,6,3,8};
int count = 9;
cout<<"原始数组:"<<endl;
show(inputNumber,count);
cout<<"排序过程:"<<endl;
QuickSort(inputNumber,0,count);
cout<<"排序结果:"<<endl;
show(inputNumber,count);
}
程序运行结果的截图:
下面来谈一下快排的复杂度问题。
快速排序的时间性能取决于快速排序递归的深度,可以用递归树来描述递归算法的执行情况。
比如{50,10,90,30, 70,40,80,60,20}在快速排序过程中的递归过程。由于我们的第一个关键字是50,正好是待排序的序列的中间值,因此递归树是平衡的,此时性能也比较好。
在最优情况下,Partition每次都划分得很均匀,如果排序n个关键字,其递归树的深度就为.log2n.+1(.x.表示不大于x的最大整数),即仅需递归log2n次,需要时间为T(n)的话,第一次Partiation应该是需要对整个数组扫描一遍,做n次比较。然后,获得的枢轴将数组一分为二,那么各自还需要T(n/2)的时间(注意是最好情况,所以平分两半)。于是不断地划分下去,我们就有了下面的不等式推断。
T(n)≤2T(n/2) +n,T(1)=0
T(n)≤2(2T(n/4)+n/2) +n=4T(n/4)+2n
T(n)≤4(2T(n/8)+n/4) +2n=8T(n/8)+3n
……
T(n)≤nT(1)+(log2n)×n= O(nlogn)
也就是说,在最优的情况下,快速排序算法的时间复杂度为O(nlogn)。
在最坏的情况下,待排序的序列为正序或者逆序,每次划分只得到一个比上一次划分少一个记录的子序列,注意另一个为空。如果递归树画出来,它就是一棵斜树。此时需要执行n‐1次递归调用,且第i次划分需要经过n‐i次关键字的比较才能找到第i个记录,也就是枢轴的位置,因此比较次数为
最终其时间复杂度为O(n2)。
平均的情况,设枢轴的关键字应该在第k的位置(1≤k≤n),数学归纳法可证明,其数量级为O(nlogn)。
再来看下快排的稳定性:
快速排序有两个方向,左边的i下标一直往右走,当a[i] <=a[cen