您的位置:首页 > 其它

快速排序思路与复杂度

2017-06-08 22:32 204 查看
public class QuickSort {
//思路:选择第一个元素是key,然后从数组最后进行比较,a[j]>=key的时候j--,a[j]<key的时候说明它应该在key的左边,此时a[i] = a[j]使得a[j]处比key小的值在key左边,然后i++,从下一个开始检测a[i]与key的大小
//     当a[i]<=key的时候,i++,当a[i]>key的时候说明它不应该在key左边,a[j] = a[i],注意此时的a[j]其实是在第一次被交换到key左边的值,所以a[j]=a[i]是可以的,没有值被覆盖,一直这么循环到i>=j,此时key左右两边的顺序都正常
public int[] quickSort(int[] a, int n) {
quick(a,0,n-1);
return a;
}

public void quick(int[] arr,int low,int high){
if(low < high){
int mid = partion(arr,low,high);
quick(arr,low,mid-1);
quick(arr,mid+1,high);
}
}

public int partion(int[] a,int low,int high){
int key =  a[low];
int i=low;
int j=high;

if(i < j){
while(i<j){//循环直到i<=j
while(i<j&&a[j]>=key){
j--;
}

if(i<j){
a[i] = a[j];
i++;
}
while(i<j&&a[i]<=key){
i++;
}
if(i<j){
a[j] = a[i];
j--;
}

}
//循环过后a[i]的位置是最开始的key值
a[i] = key;

}
return i;
}
}


算法分析

        快速排序由C. A. R. Hoare在1962年提出。它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

        我看了下网上有些bolg写排序算法,有的是理解错误了;有的呢是太过于复杂;还有的呢就干脆是用临时数组,而不是就地排序。当然我的也并没有多好,只是提够一种思路;

        说说我的基本思路:每次都取数组的第一个元素作为比较标准(哨兵元素),凡是大于这个哨兵元素的都放在它的右边,凡是小于这个哨兵元素的都放在它的左边;

        大概的步骤:

        1、判断参数条件,其实这是递归的出口;

       2、以数组的第一个元素为哨兵元素,让其他元素和它比较大小;(记住这时候第一个元素位置是口的,因为里面的值被作为哨兵元素保存起来了)

        3、开始从数组尾部往前循环得到一个小于哨兵元素的  元素A ,把该  元素A  放到第一个元素位置(也就是哨兵元素位置上,因为哨兵元素位置是空的);(这时候要记住 元素A  的位置是空的了)

       4、开始从数组头部往后循环得到一个大于哨兵元素的   元素B ,把该  元素B  放在上一步中移出的  元素A  的位置上;

       5、依次循环上面3、4步,直到最后一个元素为止,那么最后一个元素就存放哨兵元素了。

       6、把小于哨兵元素的那一部分和大于哨兵元素的那一部分分别递归调用本函数,依次递归排序好所有元素;

时间复杂度

        快速排序涉及到递归调用,所以该算法的时间复杂度还需要从递归算法的复杂度开始说起;
       递归算法的时间复杂度公式:T
= aT[n/b] + f(n)  ;
对于递归算法的时间复杂度这里就不展开来说了;

最优情况下时间复杂度

        快速排序最优的情况就是每一次取到的元素都刚好平分整个数组(很显然我上面的不是);
        此时的时间复杂度公式则为:T
= 2T[n/2] + f(n);T[n/2]
为平分后的子数组的时间复杂度,f
为平分这个数组时所花的时间;
        下面来推算下,在最优的情况下快速排序时间复杂度的计算(用迭代法):
                                         T
=  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
= 2^m T[1] + mn ;其中m = logn;


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


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

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


最差情况下时间复杂度

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

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


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


平均时间复杂度

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

空间复杂度

        其实这个空间复杂度不太好计算,因为有的人使用的是非就地排序,那样就不好计算了(因为有的人用到了辅助数组,所以这就要计算到你的元素个数了);我就分析下就地快速排序的空间复杂度吧;
        首先就地快速排序使用的空间是O(1)的,也就是个常数级;而真正消耗空间的就是递归调用了,因为每次递归就要保持一些数据;
     最优的情况下空间复杂度为:O(logn)  ;每一次都平分数组的情况
     最差的情况下空间复杂度为:O( n )      ;退化为冒泡排序的情况

       
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  快速排序 算法
相关文章推荐