博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
快速排序解析
阅读量:4087 次
发布时间:2019-05-25

本文共 3510 字,大约阅读时间需要 11 分钟。

快速排序算法

  • 快速排序

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

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

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

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

  • 该方法的基本思想是:

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

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

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

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

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

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

  • 数组0到9
0 1 2 3 4 5 6 7 8 9
72 34 46 87 21 38 88 92 23 89

初始时,我们需要选出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;

0 1 2 3 4 5 6 7 8 9
34 46 87 21 38 88 92 23 89

从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

0 1 2 3 4 5 6 7 8 9
23 34 46 ? 21 38 88 92 87 89

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

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

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

0 1 2 3 4 5 6 7 8 9
23 34 46 38 21 ? 88 92 87 89

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

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

0 1 2 3 4 5 6 7 8 9
23 34 46 38 21 72 88 92 87 89

此时,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)。

你可能感兴趣的文章
设计模式(5)--Builder(建造模式)--创建型
查看>>
使用Perl处理Excel之DMA映射
查看>>
实现 select中指定option选中触发事件
查看>>
poj 2182,乱搞
查看>>
《软件评测师教程》-安全测试与评估
查看>>
使用Alcatraz来管理Xcode插件
查看>>
Canvas动画
查看>>
JavaScript找出唯一不同的数字
查看>>
吉他“和弦”是什么?
查看>>
数据中台元年,企业数字化转型面临的三大挑战
查看>>
为什么要用存储过程,什么时候要用存储过程
查看>>
哈希表应用
查看>>
Web API--自定义异常结果的处理
查看>>
There are no packages available for install
查看>>
C#代码实现把网页文件保存为mht文件
查看>>
Android利用Fiddler进行网络数据抓包
查看>>
Git常用命令及优秀博客
查看>>
vim-缓存区中打开另外一个文件的方法
查看>>
UrlUtils.PopUpUrl
查看>>
ubuntu下安装apidoc
查看>>