快速排序的两种普通的实现方法

do while和while之间

一、挖坑分治法

int AdjustArray(int s[], int l, int r) //返回调整后基准数的位置
{
    int i = l, j = r;
    int x = s[l]; //s[l]即s[i]就是第一个坑
    while (i < j)
    {
        // 从右向左找小于x的数来填s[i]
        while(i < j && s[j] >= x) 
            j--;  
        if(i < j) 
        {
            s[i] = s[j]; //将s[j]填到s[i]中,s[j]就形成了一个新的坑
            i++;
        }
 
        // 从左向右找大于或等于x的数来填s[j]
        while(i < j && s[i] < x)
            i++;  
        if(i < j) 
        {
            s[j] = s[i]; //将s[i]填到s[j]中,s[i]就形成了一个新的坑
            j--;
        }
    }
    //退出时,i等于j。将x填到这个坑中。
    s[i] = x;
 
    return i;
}
void quick_sort1(int s[], int l, int r)
{
    if (l < r)
    {
        int i = AdjustArray(s, l, r);//先成挖坑填数法调整s[]
        quick_sort1(s, l, i - 1); // 递归调用 
        quick_sort1(s, i + 1, r);
    }
}

简洁之后

//快速排序
void quick_sort(int s[], int l, int r)
{
    if (l < r)
    {
        //Swap(s[l], s[(l + r) / 2]); //将中间的这个数和第一个数交换 参见注1
        int i = l, j = r, x = s[l];
        while (i < j)
        {
            while(i < j && s[j] >= x) // 从右向左找第一个小于x的数
                j--;  
            if(i < j) 
                s[i++] = s[j];
            
            while(i < j && s[i] < x) // 从左向右找第一个大于等于x的数
                i++;  
            if(i < j) 
                s[j--] = s[i];
        }
        s[i] = x;
        quick_sort(s, l, i - 1); // 递归调用 
        quick_sort(s, i + 1, r);
    }
}

二、交换分治法

#include <iostream>

using namespace std;

const int N = 1000010;

int q[N];

void quick_sort(int q[], int l, int r)
{
    if (l >= r) return;

    int i = l - 1, j = r + 1, x = q[l + r >> 1];
    while (i < j)
    {
        do i ++ ; while (q[i] < x);
        do j -- ; while (q[j] > x);
        if (i < j) swap(q[i], q[j]);
    }

    quick_sort(q, l, j);
    quick_sort(q, j + 1, r);
}

注意的是:

  1. do while和while的区别--do   while可以剩去循环之前(也可以理解为循环之后)的i++,j--;
  2. 挖坑排序和交换排序需要注意的每次循环之前都要使i++,j--;不然的话会陷入死循环。。。。深刻的记忆啊
  3. 递归调用,用递归来实现分治法
  4. 交换法的递归调用必须要使用j来区分区间,不可以使用i
  5. 挖坑法的递归调用可以使用i或者j,但是必须是i-1,i+1,或者是j-1,j+1;
  6. 挖坑法如果把基准选择到中间的话需要设置标志index来定位目前坑在哪里,如果基准在第一位的话,可以设置index,也可以省略,坑位直接就是i,i走到哪里坑就在哪里。
  7. 交换法在使用i还是j分割的时候,最好使用j来分割,我以为i和j是一样的,后来发现不一样。因为会又i和j相等和j大于i的情况。所以对i来说是有两种情况的,需要分情况来写递归的参数
    	if(i>j){
    	quick_sort(q,l,i-1,n);
    	quick_sort(q,i,r,n+1);
    		
    	}else{
    	quick_sort(q,l,j,n);
    	quick_sort(q,j+1,r,n+1);
    	
    	}

    对j来说不需要分j一直是大于i的,所以选择j的话,可以不需要分情况来改变递归参数。


	quick_sort(q,l,j,n);
	quick_sort(q,j+1,r,n+1);

还要注意到的是i可能会到i负数,所以最好不要是用i-1;来分隔

总结:

  1. 将基准放在中间比放在头要稍快一些。
  2. 通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
  3. 快速排序的时间复杂度计算---快速排序涉及到递归调用,所以该算法的时间复杂度还需要从递归算法的复杂度开始说起; 递归算法的时间复杂度公式:T[n] = aT[n/b] + f(n) ;
  4. 快速排序的速度并不稳定,最快是nlogN,最慢是n平方

对比:


void quick_sort(int q[],int l,int r){
	if(l>=r){
		return;
	}
			int i=l,j=r,x=q[l+r>>1];
	while(i<j){
	
		while(q[i]<x){
			++i;
		}
		while(q[j]>x){
			--j;
		}	
	
		if(i<j) {	
			swap(q[i],q[j]);
			i++;
			j--;
		} 
	}
	quick_sort(q,l,j);
	quick_sort(q,j+1,r);

}


void kkk(int q[],int l,int r){
    int i=l-1;
    int j=r+1;
    int x=q[l+r>>1];
    if(l<r){
    	 while(i<j){
        i++;
        while(q[i]<x){
            i++;
        }
        j--;
        while(q[j]>x){
            j--;
        }
        if(i<j){
            swap(q[i],q[j]);
        }
        
    }
      kkk(q,l,j);
    kkk(q,j+1,r);
    
	}
   
  
    
}

对比上方两幅图:

我以为把i++;j--位置放前面和后面没有什么。

最后发现如果放在后面的话i和j会多加1;。。。。

最优情况下时间复杂度

         快速排序最优的情况就是每一次取到的元素都刚好平分整个数组(很显然我上面的不是);

        此时的时间复杂度公式则为:T[n] = 2T[n/2] + f(n);T[n/2]为平分后的子数组的时间复杂度,f[n] 为平分这个数组时所花的时间;

        下面来推算下,在最优的情况下快速排序时间复杂度的计算(用迭代法):

                                          T[n] =  2T[n/2] + n                                                                     ----------------第一次递归

                 令:n = n/2        =  2 { 2 T[n/4] + (n/2) }  + n                                               ----------------第二次递归

                                            =  2^2 T[ n/ (2^2) ] + 2n

                令:n = n/(2^2)   =  2^2  {  2 T[n/ (2^3) ]  + n/(2^2)}  +  2n                         ----------------第三次递归  

                                            =  2^3 T[  n/ (2^3) ]  + 3n

                ......................................................................................                        

                令:n = n/(  2^(m-1) )    =  2^m T[1]  + mn                                                  ----------------第m次递归(m次后结束)

               当最后平分的不能再平分时,也就是说把公式一直往下跌倒,到最后得到T[1]时,说明这个公式已经迭代完了(T[1]是常量了)。

               得到:T[n/ (2^m) ]  =  T[1]    ===>>   n = 2^m   ====>> m = logn;

               T[n] = 2^m T[1] + mn ;其中m = logn;

               T[n] = 2^(logn) T[1] + nlogn  =  n T[1] + nlogn  =  n + nlogn  ;其中n为元素个数

               又因为当n >=  2时:nlogn  >=  n  (也就是logn > 1),所以取后面的 nlogn;

               综上所述:快速排序最优的情况下时间复杂度为:O( nlogn )

最差情况下时间复杂度

        最差的情况就是每一次取到的元素就是数组中最小/最大的,这种情况其实就是冒泡排序了(每一次都排好一个元素的顺序)

     这种情况时间复杂度就好计算了,就是冒泡排序的时间复杂度:T[n] = n * (n-1) = n^2 + n;

     综上所述:快速排序最差的情况下时间复杂度为:O( n^2 )

平均时间复杂度

        快速排序的平均时间复杂度也是:O(nlogn)

空间复杂度

        其实这个空间复杂度不太好计算,因为有的人使用的是非就地排序,那样就不好计算了(因为有的人用到了辅助数组,所以这就要计算到你的元素个数了);我就分析下就地快速排序的空间复杂度吧;

        首先就地快速排序使用的空间是O(1)的,也就是个常数级;而真正消耗空间的就是递归调用了,因为每次递归就要保持一些数据;

     最优的情况下空间复杂度为:O(logn)  ;每一次都平分数组的情况

     最差的情况下空间复杂度为:O( n )      ;退化为冒泡排序的情况

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值