快排C++实现

242 阅读1分钟

介绍

一个乱序的数组,首尾用指针l,r表示,遍历的期望是l以左全是小于主元的数,r以右全是大于主元的数。初始以l所指元素作为主元

  1. r往左走直到碰见比主元小的数,交换l,r指针的值,此时主元换到了r处,r右边的数都是比主元大
  2. l往右走直到碰见比主元大的数,交换l,r指针的值,此时主元换到了l处,l左边的数都是比主元小

重复1,2步骤直到l=r,两指针相逢,此位置就是主元在排序数组中的位置,以该位置作为二分点,对前后两部分分别重复上述步骤,直到两指针重合,即区间内只有一个元素为止

中心思想就是让主元跳来跳去直到跳到适合自己的位置

void qSort(vector<int>& nums,int start,int finish){
    if(start>=finish) return;//终止条件
    int l=start,r=finish;
    while(true){
        if(l==r) break;
        while(l<r && nums[r]>=nums[l])
            r--;//主元在左边,找右边临界点
        if(nums[r]<nums[l])
            swap(nums[l],nums[r]);//找到临界点,交换值,主元跑到右边
        while(l<r && nums[r]>=nums[l])
            l++;//主元在右边,找左边临界点
        if(nums[r]<nums[l])
            swap(nums[l],nums[r]);//找到临界点,交换值,主元回到左边,继续下一次循环
    }
    qSort(nums,start,l-1);//主元已经排好序,对前面递归
    qSort(nums,l+1,finish);//对后面递归
}

时间复杂度

主元的选取很重要,遍历一遍之后主元在数组中的位置决定着时间复杂度的大小

最差情况 主元遍历之后处在最边缘的位置,例如已经排好序的数组,第一次遍历后主元在最左边(原地不动),此时将数组分成极不平衡的二部分,0和n-1。第二次遍历后主元在最左边(原地不动),数组分成极不平衡两部分,0和n-2......,以此类推第一次遍历n个元素,第二次遍历n-1,一共要遍历n次才能确定排好序,所以1+2+...+n  复杂度最差是O(n²)

最优情况  每次遍历之后,主元把数组分成平均的两部分,第一次遍历确定1个主元位置,第二次遍历确定2个主元位置,第三次4个主元......第lgn次之后就能把n个元素的数组元素排好序,每次遍历n个元素,时间复杂度为O(nlgn)

虽然最差情况复杂度可以达到O(n²),但是这种情况出现的概率很小,整体快排还是一个时间复杂度比较好的算法

空间复杂度

从使用辅助空间角度看,因为没有使用辅助空间,所以空间复杂度为O(1),如果考虑递归调用,每次递归都要在栈中保存一些数据,平衡情况下调用lgn层,空间复杂度最优 O(lgn),最不平衡条件下调用n层,空间复杂度最差O(n)

golang 实现

func quicksort(a []int64, start, finish int64) (res []int64) { 
  if start >= finish { 
     return   
}   
l := start   
r := finish   
for {      
//找到主元最终位置      
if l == r {        
 break      
}      
//缩小右边界      
for ; l < r; r-- {        
 if a[l] > a[r] {            
tmp := a[l]            
a[l] = a[r]            
a[r] = tmp            
break         
}     
 }      
//缩小左边界      
for ; l < r; l++ {         
if a[l] > a[r] {            
tmp := a[l]            
a[l] = a[r]            
a[r] = tmp            
break         
}      
}   
}   
//对主元左边数组排序   
quicksort(a, start, l-1)   
//对主元右边数组排序   
quicksort(a, l+1, finish)   
return
}