9.9.1 快速排序介绍

终于我们的高手要登场了,如果将来你工作后,你的老板要让你写个排序算法,而你会的算法中竟然没有快速排序,我想你还是不要声张,偷偷去把快速排序算法找来敲进电脑,这样至少你不至于被大伙儿取笑。
        事实上,不论是C++ STL、Java SDK或者.NET FrameWork SDK等开发工具包中的源代码里都能找到它的某种实现版本。 
        快速排序算法最早由图灵奖获得者Tony Hoare设计出来的,他在形式化方法理论,以及ALGOL60 编程语言的发明都有卓越的贡献,是上世纪最伟大的计算机科学家之一。而这快速排序算法只是他众多贡献中的一个小发明而已。
        更牛的是,我们现在要学习的这个快速排序算法,被列为20世纪10大算法之一。我们这些玩编程的人还有什么理由不去学习它呢?
        希尔排序相当于直接插入排序的升级,它们同属于插入排序类,堆排序相当于简单选择排序的升级,它们同属于选择排序类。而快速排序其实就是我们前面认为最慢的冒泡排序的升级,它们都属于交换排序类。即它也是通过不断的比较和移动交换来实现排序的,只不过它的实现,增大了记录的比较和移动的距离,将关键字较大的记录从前面直接移动到后面,关键字较小的记录从后面直接移动到前面,从而减少了总的比较次数和移动交换次数。

9.9.2 快速排序算法
        快速排序(Quick Sort)的基本思想是:通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序的目的。

        从字面上感觉不出它的好处来。假设现在要对数组{50,10,90,30,70,40,80,60,20}进行排序。我们通过代码的讲解来学习快速排序的精妙。
        我们来看代码。

/* 对顺序表L作快速排序 */
void QuickSort(SqList *L)
{ QSort(L,1,L->length);
}

又是一句代码,和归并排序一样,由于需要递归调用,因此我们外封装了一个函数。现在我们来看QSort的实现。

/* 对顺序表L中的子序列L->r[low..high]作快速排序 */void QSort(SqList *L,int low,int high){ int pivot;if(low<high){pivot=Partition(L,low,high);  /* 将L->r[low..high]一分为二,算出枢轴值pivot */QSort(L,low,pivot-1);   /*  对低子表递归排序 */QSort(L,pivot+1,high);   /*  对高子表递归排序 */}}

从这里,你应该能理解前面代码“QSort(L,1,L->length);”中1和L->length代码的意思了,它就是当前待排序的序列最小下标值low和最大下标值high。
        这一段代码的核心是“pivot=Partition(L,low,high);”在执行它之前,L.r的数组值为{50,10,90,30,70,40,80,60,20}。Partition函数要做的,就是先选取当中的一个关键字,比如选择第一个关键字50,然后想尽办法将它放到一个位置,使得它左边的值都比它小,右边的值比它大。我们将这样的关键字称为枢轴(pivot)
        在经过Partition(L,1,9)的执行之后,数组变成{20,10,40,30,50,70,80,60,90},并返回值5给pivot,数字5表明50放置在数组下标为5的位置。此时,计算机把原来的数组变成了两个位于50左和右小数组{20,10,40,30}和{70,80,60,90},而后的递归调用“QSort(L,1,5-1);”和“QSort(L,5+1,9);”语句,其实就是在对{20,10,40,30}和{70,80,60,90}分别进行同样的Partition操作,直到顺序全部正确为止。
        到了这里,应该说理解起来还不算困难。下面我们就来看看快速排序最关键的Partition函数实现是怎么样的。

/* 交换顺序表L中子表的记录,使枢轴记录到位,并返回其所在位置 *//* 此时在它之前(后)的记录均不大(小)于它。 */
int Partition(SqList *L,int low,int high)
{ int pivotkey;pivotkey=L->r[low];  /* 用子表的第一个记录作枢轴记录 */while(low<high)      /* 从表的两端交替地向中间扫描 */{ while(low<high&&L->r[high]>=pivotkey)high--;swap(L,low,high); /* 将比枢轴记录小的记录交换到低端 */while(low<high&&L->r[low]<=pivotkey)low++;swap(L,low,high); /* 将比枢轴记录大的记录交换到高端 */}return low;     /* 返回枢轴所在位置 */
}

1) 程序开始执行,此时low=1,high=L.length=9。第4行,我们将L.r[low]=L.r[1]=50赋值给枢轴变量pivotkey,如图9-9-1所示。

2) 第5~13行为while循环,目前low=1<high=9,执行内部语句。
3) 第7行,L.r[high]= L.r[9]=20≯pivotkey=50,因此不执行第8行。
4) 第9行,交换L.r[low]与L.r[high]的值,使得L.r[1]=20,L.r[9]=50。为什么要交换,就是因为第7行的比较知道,L.r[high]是一个比pivotkey=50(即L.r[low])还要小的值,因此它应该交换到50的左侧,如图9-9-2所示。

5) 第10行,当L.r[low]= L.r[1]=20,pivotkey=50,L.r[low]<pivotkey,因此第11行,low++,此时low=2。继续循环,L.r[2]=10<50,low++,此时low=3。L.r[3]=90>50,退出循环。
6) 第12行,交换L.r[low]=L.r[3]与L.r[high]=L.r[9]的值,使得L.r[3]=50,L.r[9]=90。此时相当于将一个比50大的值90交换到了50的右边。注意此时low已经指向了3,如图9-9-3所示。

7) 继续第5行,因为low=3<high=9,执行循环体。
8) 第7行,当L.r[high]= L.r[9]=90,pivotkey=50,L.r[high]>pivotkey,因此第8行,high--,此时high=8。继续循环,L.r[8]=60>50,high--,此时high=7。L.r[7]=80>50,high--,此时high=6。L.r[6]=40<50,退出循环。
9) 第9行,交换L.r[low]=L.r[3]=50与L.r[high]=L.r[6]=40的值,使得L.r[3]=40,L.r[6]=50,如图9-9-4所示。

10) 第10行,当L.r[low]= L.r[3]=40,pivotkey=50,L.r[low]<pivotkey,因此第11行,low++,此时low=4。继续循环L.r[4]=30<50,low++,此时low=5。L.r[5]=70>50,退出循环。
11) 第12行,交换L.r[low]=L.r[5]=70与L.r[high]=L.r[6]=50的值,使得L.r[5]=50,L.r[6]=70,如图9-9-5所示。

12) 再次循环。因low=5<high=6,执行循环体后,low=high=5,退出循环,如图9-9-6所示。

13) 最后第14行,返回low的值5。函数执行完成。接下来就是递归调用“QSort(L,1,5-1);”和“QSort(L,5+1,9);”语句,对{20,10,40,30}和{70,80,60,90}分别进行同样的Partition操作,直到顺序全部正确为止。我们就不再演示了。
        通过这段代码的模拟,大家应该能够明白,Partition函数,其实就是将选取的pivotkey不断交换,将比它小的换到它的左边,比它大的换到它的右边,它也在交换中不断的更改自己的位置,直到完全满足这个要求为止。

9.9.3 快速排序复杂度分析
        我们来分析一下快速排序法的性能。快速排序的时间性能取决于快速排序递归的深度,可以用递归树来描述递归算法的执行情况。如图9-9-7,它是{50,10,90,30,70,40,80,60,20}在快速排序过程中的递归过程。由于我们的第一个关键字是50,正好是待排序的序列的中间值,因此递归树是平衡的,此时性能也比较好。

在最优情况下,Partition每次都划分得很均匀,如果排序n个关键字,其递归树的深度就为⌊log2n⌋+1(⌊x⌋表示不大于x的最大整数),即仅需递归log2n次,需要时间为T(n)的话,第一次Partiation应该是需要对整个数组扫描一遍,做n次比较。然后,获得的枢轴将数组一分为二,那么各自还需要T(n/2)的时间(注意是最好情况,所以是平分两半)。于是不断的划分下去,我们就有了下面的不等式推断:
T(n)≤2T(n/2) +n,T(1)=0
T(n)≤2(2T(n/4)+n/2) +n=4T(n/4)+2n
T(n)≤4(2T(n/8)+n/4) +2n=8T(n/8)+3n
……
T(n)≤nT(1)+(log2n)×n= O(nlog2n)

也就是说,在最优的情况下,快速排序算法的时间复杂度为O(nlogn)。
        在最坏的情况下,待排序的序列为正序或者逆序,每次划分只得到一个比上一次划分少一个记录的子序列,注意另一个为空。如果递归树画出来,它就是一棵斜树。此时需要执行n-1次递归调用,且第i次划分需要经过n-i次关键字的比较才能找到第i个记录,也就是枢轴的位置,因比较次数为 ,最终其时间复杂度为O(n2)。
        平均的情况,设枢轴的关键字应该在第k的位置(1≤k≤n),那么

  
        由数学归纳法可证明,其数量级为O(nlogn)。
        就空间复杂度来说,主要是递归造成的栈空间的使用,最好情况,递归树的深度为log2n,其空间复杂度也就为O(logn),最坏情况,需要进行n-1递归调用,其空间复杂度为O(n),平均情况,空间复杂度也为O(logn)。
        可惜的是,由于关键字的比较和交换是跳跃进行的,因此,快速排序是一种不稳定的排序方法。

(下篇将讲解快速排序的各种优化)

出处:http://www.cnblogs.com/cj723/archive/2011/04/27/2029993.html

《大话数据结构》第9章 排序 9.9 快速排序(上)相关推荐

  1. 大话数据结构 第七章 图(二) 最小生成树、最短路径、拓扑排序、关键路径算法

    大话数据结构 第七章 图(二) 最小生成树.最短路径.拓扑排序.关键路径算法 最小生成树 定义 Prim算法 Kruskal算法 最短路径 Dijkstra算法 Floyd算法 拓扑排序 AOV网 拓 ...

  2. 《大话数据结构》样章试读

    <大话数据结构>样章试读 各位童鞋,<大话数据结构>从写作到出版,虽然经历了一些坎坷,但终于还是在今天正式在一些网店发售了.现在提供两章的完整版试读PDF文件,希望能给您有所 ...

  3. 《大话数据结构》第9章 排序 9.9 快速排序(下)

    9.9.4 快速排序优化 刚才讲的快速排序还是有不少可以改进的地方,我们来看一些优化的方案. 1.优化选取枢轴         如果我们选取的pivotkey是处于整个序列的中间位置,那么我们可以将整 ...

  4. 读书笔记-《大话数据结构》第二章算法

    2.3两种算法的比较 #include <iostream> #if 0 //需要运行 100次 int main() {int i,sum=0,n=100;for(i=1;i<=n ...

  5. 【大话数据结构算法】希尔排序

    希尔排序的实质就是分组插入排序,该方法又称为缩小增量排序. 直接插入排序适合于序列基本有序的情况,希尔排序的每趟排序都会使整个序列变得更加有序,等整个序列基本有序了,再来一趟直接插入排序,这样会使排序 ...

  6. 大话数据结构 摘录 第一章 数据结构绪论

    文章目录 启示:数据结构 学习数据机构的重要性 数据结构引发的案例 数据结构的起源 程序设计=数据结构+算法 基础概念与术语 数据 数据元素 数据项 数据对象 数据结构 数据结构:是相互之间存在一种或 ...

  7. 读书笔记-《大话数据结构》第一章数据结构绪论

    1.3数据结构的起源 数据结构:是一门研究非数值计算的程序设计问题中的操作对象,以及它们之间的关系和操作等相关问题的学科. 程序设计=数据结构+算法 1.4基本概念和术语 1.4.1数据:描述客观事物 ...

  8. 大话数据结构第四章栈的应用

    一.斐波契那数列 就是下一项的值等于相邻的上两项元素之和. 这个规律就是斐波契那数列.由此,我知道这个世界是少不了规律的.就算没有规律也要用规律来逼近描述无规律的现象. 迭代和递归的区别:迭代是循环, ...

  9. 大话数据结构第四章栈的基本概念与出栈入栈操作

    一.栈的顺序存储结构 1.基本概念 栈顶就是表尾. 栈顶是栈插入和删除的地方. 栈就是只允许在表尾进行添加或删除,是顺序存储结构线性表的特例或者说简化. 为什么说是简化呢?相对于顺序存储结构来说栈只允 ...

最新文章

  1. 如何让ie 7 支持box-shadow
  2. Windows server 2008 R2 登录密码恢复
  3. 《Docker技术入门与实战》读书笔记
  4. 结束SQL阻塞的进程
  5. 【Redis】15.Redis主从复制
  6. 导致集群重启_干货丨如何水平扩展和垂直扩展DolphinDB集群?
  7. LeetCode 2119. 反转两次的数字
  8. [心得] 如何利用liquibase進行資料庫版本控制 - 實際練習
  9. [轻音乐] - 理查德·克莱德曼专辑[8CD]
  10. php把 图片上传到 图片服务器
  11. 什么是a站、b站、c站、d站、e站、f站、g站、h站、i站、j站、k站、l站、m站、n站?00后的世界我不懂!
  12. 笔记本界面怎么显示服务器界面,电脑桌面显示工作方案(共8篇) .docx
  13. 从SS7到VOIP sip
  14. 腾讯音乐娱乐数据分析4.15笔试
  15. kali初讲——Metasploit工具MSF初学
  16. Kafka 处理器客户端介绍
  17. Remix 搭建与简单使用
  18. 《自己动手设计物联网》Kindle 版已上架
  19. 视频营销(Video Marketing)1-视频营销基础
  20. OpenCV库中watershed函数(分水岭算法)的详细使用例程

热门文章

  1. Elasticsearch-01CentOS7单节点部署ES5.6.16
  2. Android系统服务
  3. CuteBot智能小车
  4. html仿命令行界面,实战:vue仿dos命令界面
  5. C语言中文网js,第一个JavaScript程序
  6. python学习笔记(六)——函数的作用域和装饰器
  7. Matlab-重构和重新排列数组
  8. 学计算机科学与技术的专业特长,计算机科学与技术专业简历范文介绍
  9. ajax java 插件_对JQuery中Ajax应用与jQuery插件的理解与笔记
  10. Python_管理项目