快速排序:
快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,其基本思想为:任取待排序元素序列中 的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。快速排序可以让时间复杂度达到O(log(N)* N);
快速排序自从Hoare提出后还衍生出了很多其他的版本,比如挖坑法,先后指针法等等。但是基本思想都差不多,下文都会介绍。
Hoare版本快速排序:
快速排序是由一次次单趟排序组成的。我们先来介绍单趟排序的的操作:(1)选择一个基准值,在这里我们选a[end]为基准值(key = end),选完基准值后,接下来要做的就是将比a[key]小的放左边,大的放在右边。(2)begin下标初始指向数组的最左边,end下标指向最右边。begin向右走找到比a[key]大的停下,end向左找到比a[key]小的停下。(3)交换a[begin]和a[end]。(4)交换后继续(2)(3)操作直到begin和end相遇,交换a[begin]和a[key]的值。下图为单趟排序的图解。
单趟排序代码如下:
int H_partsort(int* a,int begin,int end){//hoare版本快速排序单趟排序
int key = end;
while(begin < end){
while( begin < end && a[begin] <= a[key] ){//找比a[key]大的
begin ++;
}
while( begin < end && a[end] >= a[key] ){//找比a[key]小的
end --;
}
swap(&a[begin],&a[end]);//小的放左边,大的放右边
}
swap(&a[begin],&a[key]);//begin和end相遇后与a[key]交换
return begin;
}
快速排序是由一次次单趟排序组成的。怎么组成的呢?比如刚才的例子我们对数组a[]做了一次单趟排序,并获取了基准值的位置:7(a[7] = 8);然后我们把a[7]左边的(a[0] - a[6])看作一个数组,a[7]右边的(a[8] - a[9])看做另一个数组,继续这样的操作。直到左右数组不存在或者数组只有一个值。下图为图解过程:
代码如下:
void Hoare_Qsort(int* a,int begin,int end){//hoare版本快速排序
if(begin >= end){
return ;
}
int tem = H_partsort(a,begin,end);
Hoare_Qsort(a,begin,tem - 1);//前半部分
Hoare_Qsort(a,tem + 1,end);//后半部分
}
总结:(1)如果我们选择a[end]为基准值,那么begin先找,然后end再找。(2)单趟排序的第四行中:a[begin] <= a[key] 不能写成 a[begin] < a[key]。因为在单趟排序时如果数组中有几个与a[key]相等的值,那么这样写很有可能让单趟排序变成死循环。
挖坑法快速排序: 前后指针法快速排序:
这两种方法的快速排序是在Hoare版本的基础上进行了一些改进,但是基本的思想还是一样的。代码实现如下:
挖坑法:
int Dig_partsort(int *a,int begin,int end){//挖坑法的单趟排序
int key = a[end];//a[end]为坑
while(begin < end){
while(begin < end && a[begin] <= key){
begin ++;//找大
}
a[end] = a[begin];//用找到的大的a[begin]去填坑,然后a[begin]变为新坑
while(begin < end && a[end] >= key ){
end --;//找小
}
a[begin] = a[end];//用找到的小的a[end]去填坑,a[end]变为新坑
}
a[begin] = key;
return begin;
}
void Dig_Qsort(int* a,int begin,int end){//挖坑法快速排序
if( begin >= end ){
return ;
}
int tem = Dig_partsort(a,begin,end);
Dig_Qsort(a,begin,tem - 1);//前半部分
Dig_Qsort(a,tem + 1,end);//后半部分
}
前后指针法:
int pc_partsort(int* a,int begin,int end){//前后指针法单趟排序
int prev = begin - 1;
int cur = begin;
int key = end;
while(cur < end){
if(a[cur] <= a[key]){
swap(&a[++prev],&a[cur++]);
}else{
cur++;
}
}
swap(&a[++prev],&a[key]);
return prev;
}
void pc_Qsort(int* a,int begin,int end){//前后指针法快速排序
if(begin >= end){
return;
}
int tem = pc_partsort(a,begin ,end);
pc_Qsort(a,begin,tem - 1);
pc_Qsort(a,tem + 1,end);
}
快速排序的缺点:
快速排序的平均时间复杂度为O(log(n) * n)因为单趟排序时间复杂度为O(n)平均需要log(n)次单趟排序。但是快速排序有一个缺点:如果所排的数组是一个有序数组那么快速排序的时间复杂度就变成了O(n^2)了因为如果数组是有序的,每次选a[end]作为基准值,就肯定要进行n次单趟排序。
优化:三数取中法,在a[begin] a[end] 和 a[(begin + end) / 2]找一个中间值作为基准值。
int H_partsort(int* a,int begin,int end){//hoare版本快速排序单趟排序
int mid = Get_Mid(a,begin , end);//获取三个数中中间值的下标
swap(&a[mid],&a[end]);
int key = end;
while(begin < end){
while( begin < end && a[begin] <= a[key] ){//找比a[key]大的
begin ++;
}
while( begin < end && a[end] >= a[key] ){//找比a[key]小的
end --;
}
swap(&a[begin],&a[end]);//小的放左边,大的放右边
}
swap(&a[begin],&a[key]);//begin和end相遇后与a[key]交换
return begin;
}