快速排序解析

快速排序算法

  • 快速排序

  • 快排的分析参考了MoreWindows先生的博客

  • 快速排序由于排序效率在同为O(N*logN)的几种排序方法中效率较高,因此经常被采用,再加上快速排序思想—–分治法也确实实用,因此很多软件公司的笔试面试,包括像腾讯微软等知名IT公司都喜欢考这个,还有大大小的程序方面的考试如软考,考研中也常常出现快速排序的身影。

  • 总的说来,要直接默写出快速排序还是有一定难度的

  • 快速排序是C.R.A.Hoare(托尼·霍尔)于1962年提出的一种划分交换排序。它采用了一种分治的策略,通常称其为分治法(Divide-and-ConquerMethod)。

  • 该方法的基本思想是:

1.先从数列中取出一个数作为基准数,所谓基准数就是选出来一个数做为参考的数字而已。

2.分区过程,将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边。

3.再对左右区间重复第二步,直到各区间只有一个数。

虽然快速排序称为分治法,但分治法这三个字显然无法很好的概括快速排

序的全部步骤。因此我的对快速排序作了进一步的说明:挖坑填数+分治法

先来看实例吧,定义下面再给出(最好能用自己的话来总结定义,这样对实现代码会有帮助)。

  • 数组0到9
0123456789
72344687213888922389

初始时,我们需要选出3个数据:

  • 1.数组的最小下标 i = 0;
  • 2.数组的最大下标 j = 9;
  • 3.作为基准数的数字 key = a[i] = 72
  • 这三个数据不管哪篇博客里都一定会有,因为算法就这么规定的,有的人叫它low,high,key,有的人叫它left,right,key,也有人说他是i,j,k,反正你明白你喜欢就好

由于已经将a[0]中的数保存到key中,可以理解成在数组a[0]上挖了个坑,

可以将其它数据填充到这来,此时的key只作为一个参考值,也可以想象

成a[0]上没有数据。

i = 0;
j = 9;
key = a[0] = 72;

0123456789
344687213888922389

从j开始向前找一个比key小或等于key的数。当j=8,符合条件,将a[8]挖

出再填到上一个坑a[0]中。a[0]=a[8]; i++; 这样一个坑a[0]就被搞定了,

但又形成了一个新坑a[8],这怎么办了?简单,再找数字来填a[8]这个

坑。这次从i开始向后找一个大于X的数,当i=3,符合条件,将a[3]挖出

再填到上一个坑中a[8]=a[3]; j–;

数组变为:

i = 3; j = 7; X=72

0123456789
233446?213888928789

再重复上面的步骤,先从后向前找小于等于key的值,再从前向后找大于等于key的值。

从j开始向前找,当j=5,符合条件,将a[5]挖出填到上一个坑中,a[3] = a[5]; i++;

此时i = 4, j = 5, key = 72

0123456789
2334463821?88928789

从i开始向后找,当i=5时,由于i==j退出。

此时,i = j = 5,而a[5]刚好又是上次挖的坑,因此将key填入a[5]。

0123456789
23344638217288928789

此时,a[5]左边的数字都小于等于key(72)右边都大于等于key(72)

因此再对a[0…4]和a[6…9]这二个子区间重复上述步骤就可以了。

对挖坑填数进行总结

1.i =L; j = R; 将基准数挖出形成第一个坑a[i]。

2.j–由后向前找比它小的数,找到后挖出此数填前一个坑a[i]中。

3.i++由前向后找比它大的数,找到后也挖出此数填到前一个坑a[j]中。

4.再重复执行2,3二步,直到i==j,将基准数填入a[i]中。

照着这个总结很容易实现挖坑填数的代码:

int AdjustArray(int s[], int left, int right) //返回调整后基准数的位置
{
    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 sort(int s[], int l, int r)
{
    if (l < r)
    {
        int i = AdjustArray(s, l, r);//先成挖坑填数法调整s[]
        sort(s, l, i - 1); // 递归调用 
        sort(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);  
        }  
    }  

算法复杂度

最坏情况下的快排时间复杂度:

最坏情况发生在划分过程产生的俩个区域分别包含n-1个元素和一个0元素

的时候,

即假设算法每一次递归调用过程中都出现了,这种划分不对称。那么划分

的代价为O(n),

因为对一个大小为0的数组递归调用后,返回T(0)=O(1)。

估算法的运行时间可以递归的表示为:

T(n)=T(n-1)+T(0)+O(n)=T(n-1)+O(n).

可以证明为T(n)=O(n^2)。


因此,如果在算法的每一层递归上,划分都是最大程度不对称的,那么算

法的运行时间就是O(n^2)。


最快情况下快排时间复杂度:

最快情况下,即PARTITION可能做的最平衡的划分中,得到的每个子问

题都不能大于n/2.

因为其中一个子问题的大小为|n/2|。另一个子问题的大小为|-n/2-|-1.

在这种情况下,快速排序的速度要快得多:

T(n)<=2T(n/2)+O(n).可以证得,T(n)=O(nlgn)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Zok93

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值