快速排序(QuickSort)是对冒泡排序的一种改进。由C. A. R. Hoare在1962年提出。快速排序采用了基于分治的策略。
主要思想:在数组中选取一个枢轴(或称主元,最基本的就是选第一个元素),把小于主元的元素全部放在左边,大于主元的全部放右边(以上即为partition函数),然后再按此方法对主元左边和主元右边的两部分数据(不包含主元!)分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
对于partition的实现,有多种版本:
算法导论上选取最后一个元素做主元,i,j均采用从头到尾单向扫描:j在前面开路,i跟在j后,j只要遇到比主元小的元素,i 就向前前进一步,然后把j找到的比主元小的元素,赋给i,然后,j才再前进。i所经过的每一步,都必须是比主元小的元素,否则,i就不能继续前行。好比j 是先行者,为i开路搭桥,把小的元素作为跳板放到i 跟前,为其铺路前行。
国内教材一般使用双向扫描,为Hoare最初的方法或其变形。一般选第一个元素做主元,i一开始指向第一个元素向后扫描,j指向最后一个向前扫描。从j开始,如果j遇到比主元小的,则将此元素赋值给i所在位置,然后轮到i前进,直到遇到比主元大的赋值给j所在位置…如此j、i轮流扫描直到i>j。
快速排序的平均时间复杂度为O(nlogn),最坏时间复杂度为O(n2)。当输入数据有序时为最坏情况,需要O(n2)。随机化版本的快排最坏时间复杂度仍为O(n2)!但是最坏情况不依赖输入数据,而是随机函数取值不佳。由于快速排序是递归的,故其空间复杂度即为栈的深度,最好情况下为O(logn),最坏情况为O(n),平均空间复杂度为 O(logn),如果每次选取分区长的部分先入栈,即先处理短的分区可以减少算法的递归深度(但递归次数不变,递归次数与处理顺序无关),可使得最坏情况栈深度为O(logn)。快速排序是平均性能最好的排序算法,是不稳定的算法。
快排有多种变形:随机化的快排、“三数取中”划分等。随机化的快速就是每次随机选取一个元素作为主元,方法就是每次先用随机函数在left和right之间随机出一个数字rand,然后将第rand个元素和第left个元素交换,此时就可以继续使用原来的partition,因为这时候再取第一个元素做主元时已经是随机后的那个数了。“三数取中”划分就是每次(随机)选出三个元素,取其中间数作为主元。
#include<iostream>
using namespace std;
int partition(int arr[],int left,int right)
{
int index=arr[left],i=left,j=right;
while(i<j)
{
while(arr[j]>=index && i<j) //必须有等号!否则数字相同会死循环
j--;//若大于主元则向前
arr[i]=arr[j];
while(arr[i]<=index && i<j)
i++;//若小于主元则向后
arr[j]=arr[i];
}
arr[i]=index;
return i;//返回主元最终位置
}
void quickSort(int arr[],int left,int right)
{
if(left<right)
{
int mid=partition(arr,left,right);
quickSort(arr,left,mid-1);
quickSort(arr,mid+1,right);
}
}
int main()
{
int a[]={5,9,-2,10,0,-9,23,-11,6,7};
quickSort(a,0,9);
for(int i=0;i<10;++i)
cout<<a[i]<<" ";
return 0;
}
quickSort中的第二次递归调用不是必须的,可以用迭代控制结构来代替它。这种技术称为尾递归。可以改为如下形式:
void quickSort(int arr[],int left,int right)
{
while(left<right)//if需改为while循环
{
int mid=partition(arr,left,right);
quickSort(arr,left,mid-1);
left=mid+1; //不再用递归!
}
}
参考:
《算法导论》
http://blog.youkuaiyun.com/v_JULY_v/article/details/6262915