快速排序:“二十世纪最伟大的算法”
核心思想:每次从数组中选择一个元素作为基点,之后把该元素移动到排好序时应该所处的位置,使得基点之前的元素都小于它、之后的元素都大于它。之后对小于它、大于它的子数组分别递归进行快速排序
将元素移动到合适位置的函数:Partition
通常使用第一个元素作为分界的标志点:索引——l,元素值——v
大于v与小于v的分界点索引:j
当前访问的元素索引:i,元素值:e
最终目的,使得:
arr[l+1…j]< v
arr[j+1….i-1]> v
分为两种情况:
1.e > v
2.e < v
再交换l与j位置的元素,使得基点在合适的位置上
//对arr[l...r]部分进行partition操作
//返回p,使得arr[l...p-1]<arr[p];arr[p+1...r]>arr[p]
template<typename T>
int __partition(T arr[],int l,int r){
//优化1:随机选取一个基数,而非最左端的元素
//swap(arr[rand()%(r-l+1)+l],arr[l]);
//基数、标准
T v=arr[l];
//i是当前考虑的位置
//arr[l+1...j]<v;arr[j+1...i)>v
int j=l; //使得两个子数组初始都为空
for(int i=l+1;i<=r;i++){
if(arr[i]<v){
swap(arr[++j],arr[i]);
//j++;
}
}
swap(arr[l],arr[j]);
return j;
}
template<typename T>
void __quickSort(T arr[],int l,int r){
//索引非法处理
if(l>=r)
return;
int p=__partition(arr,l,r);
__quickSort(arr,l,p-1);
__quickSort(arr,p+1,r);
}
template<typename T>
void quickSort(T arr[],int n){
//优化1:设置随机种子
//srand(time(NULL));
__quickSort(arr,0,n-1);
}
缺点1:对于近乎有序的数组排序效率低下,运行过程中形成的递归树平衡度较差,对于完全有序的数组,算法复杂度直接退化为O(n*n)。
优化1:随机选取排序的基准元素
缺点2:对于有大量重复键值的数组排序效率低下,同样可能造成算法复杂度退化为O(n^2)。
优化2:
//双路快速排序:
template<typename T>
int __partition2(T arr[],int l,int r){
swap(arr[rand()%(r-l+1)+l],arr[l]);
T v=arr[l];
//arr[l+1...i]<=v;arr[j...r]>=v
int i=l+1,j=r;
while(true){
while(i<=r&&arr[i]<v)i++;
while(j>=l+1&&arr[j]>v)j--;
if(i>j)break;
swap(arr[i],arr[j]);
//索引移动到下一个要考察的位置
i++;
j--;
}
//循环结束时三个索引:
//i: 从前向后看第一个大于等于v的位置
//j: 从后向前看第一个(数组中最后一个)小于等于v的元素位置
//v: 小于等于v一端
swap(arr[l],arr[j]);
return j;
}
优化3:考虑到可能会有很多“等于v”的键值,还可以进一步优化出三路快速排序。增设一个存放“等于v”元素的子数组,直接省去这部分递归。
索引含义如下:
lt——指向“小于v”部分的最后一个元素,使得arr[l+1…lt] < v
gt——指向已经处理过的后面的第一个“大于v”部分的元素,使得arr[gt…r] > v
则arr[lt+1…i-1]==v
分情况讨论索引为i的元素:
1. e == v:该元素直接纳入“等于v”部分,i++
2. e < v :将该位置元素和“等于v”部分的第一个元素交换,lt++
3. e > v :将该位置元素直接与“gt-1”位置元素进行交换,gt–,i索引无需进行维护,依然指向了一个没有经过处理的元素
整个处理过程结束后,lt、gt分别指向了“小于v”部分和“大于v”部分的最后一个(第一个)元素,i与gt重合,此时交换l位置元素与lt位置元素即可
最后结果:
“小于v”部分:arr[l…lt-1] < v;
“等于v”部分:arr[lt…gt-1] ==v;
“大于v”部分:arr[gt…r] > v;
三路快速排序实现:
//三路快速排序处理:arr[l...r]
//将arr[l...r]分为<v,==v,>v三个部分
//之后递归对<v,>v 两部分继续进行三路快速排序
template<typename T>
void __quickSort3Ways(T arr[],int l,int r){
//递归结束判断
if(l>=r)
return;
//partition
swap(arr[rand()%(r-l+1)+l],arr[l]);
T v=arr[l];
//注意初始值的设定,必须满足三个子数组开始都为空
int lt=l; //arr[l+1...lt]<v
int gt=r+1; //arr[gt...r]>v
int i=l+1; //arr[lt+1...i)==v
while(i<gt){
if(arr[i]<v){
swap(arr[i],arr[lt+1]);
lt++;
i++;
}
else if(arr[i]>v){
swap(arr[i],arr[gt-1]);
gt--;
//i索引无需处理,依然指向了一个未经过处理的元素
}
else{//arr[i]==v
i++;
}
}
swap(arr[l],arr[lt]);
__quickSort3Ways(arr,l,lt-1);
__quickSort3Ways(arr,gt,r);
}
template<typename T>
void quickSort3Ways(T arr[],int n){
srand(time(NULL));
__quickSort3Ways(arr,0,n-1);
}
对于快速排序就介绍到这里了,更基础的选择、插入排序详见前两篇文章:http://blog.csdn.net/draper__qyt/article/details/77980407
http://blog.csdn.net/draper__qyt/article/details/77984775
注:本文的内容来自慕课网的课程《c++,java算法与数据结构》,主讲人为刘宇波老师~极力推荐!
这里附上课程链接:http://coding.imooc.com/class/71.html