快速排序思路整理

引言:

快速排序和归并排序是面试当中常常被问到的两种排序算法,在研究过数据结构所有的排序算法后,个人认为最复杂的当属快速排序。从代码量上来看,快速排序并不多,我之所以认为快排难以掌握是因为快排是一种递归算法,同时终止条件较多。如果你刚刚把快排的思路整理过一遍可能觉得不难,然而一个月之后呢?

面试要求的是临场发挥,如果不是烂熟于心,基本就卡壳了。在面试官眼里,你和那些完全不懂快速排序算法的菜逼是一样的,也许实际上你可能私底下已经理解很多遍了,然而并没卵。所以当下问题就是,如何将快排烂熟于心?我觉得记忆一个算法应当在理解的基础上,当你心里面把所有终止和判断条件都一清二楚之后,手写快排就不是件难事了。

源代码:

 1 package test;
 2 /**
 3  *  快速排序
 4  * @author xuanxufeng
 5  *
 6  */
 7 public class QuickSort {
 8
 9     public void quickSort(int[] source,int low,int high){
10         if(low<high){
11             int pos = Partition(source,low,high);
12             quickSort(source,low,pos-1);
13             quickSort(source,pos+1,high);
14         }
15     }
16
17     private int Partition(int[] source, int low, int high) {
18         int temp = source[low];
19         while (low < high) {
20             while (low < high && source[high] >= temp) high--; //一定要是大于等于,否则死循环!!!
21             source[low] = source[high];
22             while (low < high && source[low] <= temp) low++;
23             source[high] = source[low];
24         }
25         source[low] = temp;
26         return low;
27     }
28     public static void main(String[] args) {
29         // TODO Auto-generated method stub
30         int[] a = { 4, 2, 1, 6, 3, 6, 0, -5, 1, 1 };
31         QuickSort qsort = new QuickSort();
32         qsort.quickSort(a, 0, a.length - 1);
33         for (int i = 0; i < a.length; i++) {
34             System.out.print(a[i] + " ");
35         }
36
37     }
38
39 }

思路整理:

我想,对于大多数学过数据结构的人来说,上面代码的大体思路应该是没有什么问题的,如果有问题那你是不是要好好考虑下重学一遍数据结构了。

这里我从几个判定条件入手来捋一捋算法思路:

判定条件1  while (low < high && source[high] >= temp) 中的low < high

来看这个例子,假设我们要对 4 2 1 6 进行排序,即待排序数组 source = {4,2,1,6}; 快速排序的步骤大体是这样的:

4 2 1 6 | 4   ----> 4 2 1 6 | 4 ----> 1 2 1 6 | 4 ----> 1 2 1 6 | 4 ---->  1 2 1 6 | 4 ----> 1 2 4 6 | 4(跳出循环, source[low] = temp; ), 在这里,我们用蓝色代表low指向的元素,红色代表high指向的元素,品红色代码low和high相遇了,即 low = high,而|号后边的数字4代码选取的基准元素(默认是数组第一个数字)。所以,在移动low和high的过程中,终止条件就是low==high,即他们相遇了。

判定条件2

while (low < high) {
            .....
        }

条件1中说了,low和high不能相遇。但是为什么外面还要设置一层循环呢?咱们再看一个例子:

4 2 5 7 2 3 1 6 | 4 ----> 4 2 5 7 2 3 1 6 | 4 ----> 1 2 5 7 2 3 1 6 | 4  ----> 1 2 5 7 2 3 1 6 | 4 ----> 1 2 5 7 2 3 1 6 | 4 ----> 1 2 5 7 2 3 5 6 | 4 , OK。到这里,读者可以考虑一下如果没有外面这层while循环,是Partition函数下一步干嘛?没错,会这样执行1 2 4 7 2 3 5 6 | 4,但是这个数组还没有遍历完啊! 并且说好的4左边的全部比4小,4右边的全部比4大的呢?

因此,通如果low和high没有相遇,就应该让它们一直遍历下去!

判定条件3  while (low < high && source[high] >= temp) 中的 source[high] >= temp

有没有想过为什么是>=呢? 如果换成>会是什么结果? 我可以明确的告诉你,把等号去掉就会死循环了!是不是倒吸一口凉气?我想,任何一个程序员应该都不敢轻视任意一个边界条件,所以即便是一个等号也要认真考半天,为什么要等号,可以不可以去掉?我们继续上个例子:

1 2 1 1 3 -5 0 | 1 ----> 0 2 1 1 3 -5 0 | 1 ----> 0 2 1 1 3 -5 2 | 1 ----> 0 2 1 1 3 -5 2 | 1 ----> 0 -5 1 1 3 -5 2 | 1 ----> 0 -5 1 1 3 -5 2 | 1 ----> 0 -5 1 1 3 1 2 | 1,好了,到了这里读者再往后推算几步,看看有什么发现?是不是一直在做反复赋值的死循环?因为从 low<high一直满足, 而source[high] > temp和source[low] < temp一直都不满足,所以low和high一直都不会移动了,所以自然就死循环了!这个时候,只有把>和<修改成>=和<=,low和high才会继续移动下去!其实,Partition函数返回位置的左边的数应当是小于或等于基准元素,返回位置右边的数应当是大于等于基准元素的值才对!!!!

判定条件4

if(low<high){
            ....
        }

这个是快排作为递归的终止条件,为什么终止条件是这样呢?

我们先看第一种情况,假设Partition函数调用后的结果是这样的,..... 5 7,其中5对应于返回的位置pos. 那么在调用 quickSort(source,pos+1,high); 时对应的实参是多少?这是一个递归调用,这个情况下pos+1显然就等于high了,那么回到这个函数的判定条件,是不是这个Partition函数其实什么都没做?所以这个判定条件就是递归调用的出口!

我们再看第二种情况,假设Partition函数调用后的结果是这样的,.... 2 3 4 ,其中2对应于返回的pos,那么在调用quickSort(source,pos+1,high);后返回的位置应当就是3对应的位置了。这时候还要再往深处调用 quickSort(source,low,pos-1); 和 quickSort(source,pos+1,high); ,但是这个时候low对应于3,pos-1对应于2 ,不满足low<high;同时,pos+1对应于4,high也对应于4,也不满足!所以整个递归调用到这一层次就算是结束了!

好了,整个快排梳理就到这儿了,通过反复梳理记忆,快排一定会烂熟于心!

时间: 2024-10-26 04:46:45

快速排序思路整理的相关文章

搜索与排名思路整理

学习<集体智慧编程>第4章的思路整理: 本章的主要内容就是建立一个模拟的全文搜索引擎,主要的大步骤为:1.检索网页,2.建立索引,3.对网页进行搜索 4.多种方式对搜索结果进行排名 一.检索网页:主要利用python写了一个爬虫软件,通过爬取一个网站上链接来不断的扩充爬取的内容.主要利用了python的urllib库和BeautifulSoup库.这部分比较简单,核心代码如下: def crawl(self,pages,depth=2): for i in range(depth): newp

16 飞机大战:思路整理、重要代码

思路整理 重要代码 0.重写方法万万检查记得要不要继承父类方法 def __init__(self): super().__init__() 1.创建游戏时钟:用来设置游戏刷新率 # 新建游戏时钟对象 self.clock = pygame.time.Clock() ... ... # 设置游戏刷新率 self.clock.tick(60) #60帧/s 2.精灵组 # 创建xx精灵 self.xx = Xx() #其中Xx是Xx类 # 创建xx精灵组 self.xx_group = pygam

能力库开发思路整理

能力库界面如下: 相关数据库表: 1 CREATE TABLE `base_ability` ( 2 `abillty_id` varchar(36) NOT NULL DEFAULT '' COMMENT '主键', 3 `ability_code` varchar(20) DEFAULT NULL, 4 `ability_name` varchar(20) DEFAULT NULL COMMENT '能力编号', 5 `ability_name_desc` varchar(255) DEFA

Canvas---Canvas图像处理、图片查看器实现思路整理、拖动边界控制

没想到一个图片查看器花了我这么多时间,而且没做好. 现在整理下思路,然后把不足的地方记一下,日后请教他人. 基本思路: 一.图片查看器功能---缩放 要实现自由缩放,先要实现图片对canvas的自适应,就是给你一张大图片,你能够把它合理缩放后恰好绘制在canvas中. 具体做法是:例如:图片为500*500,canvas为240*320,那就取缩放宽度为240,长度为240/500*500,利用缩放宽度与长度,绘制图片即可. 然后是自由缩放,这时,你的缩小放大对象只要是一个矩形就好,然后图片去适

linux: 堆排序和快速排序的整理

快排采用分治法(Divide and Conquer)把一个list分为两个sub-lists. 算法步骤 1. 从数列中跳出一个元素,作为基准(pivot). 2. 重新排序数列,所有比基准值小的元素(elements < pivot)放在基准值的前面,而所有比基准值大的元素(elements > pivot)放在基准值后面,与基准值相等的数可以放在任意一边.此操作即为分区(partition)操作. 3. 递归地把小于基准值元素的子数列和大于基准值元素的子数列进行排序.递归在数列长度为0或

Lync2013 升级Skype For Bussiness 2015 升级思路整理

最近做了次Lync 2013企业版升级到SFB 2015,期间碰到了各种问题.这里就专门整理下升级的思路. 至于升级过程实战的文章,后续有空再写写,其实还是很简单的. 后续todo:SFB 2015 后端alwaysOn建立,Lync 2013升级至SFB 2015并且后端进行AlwaysOn高可用建立(这个可能不靠谱--) 简要升级路线: Lync 2013:使用新的拓扑生成器生成新拓扑并发布,然后在池的每台关联服务器上就地升级功能升级 Lync 2010:首先升级至Lync 2013,然后使

Java做界面思路整理

说起大一就学过C++,但从未接触过VC++,至于做界面也是直到学java才开始,所以自己还是个新手啊... 步入正题,通过自己写的两个小程序,对做界面的思路进行一下整理. 首先,构想出自己想要实现的界面是什么样子.可以在纸上画出个轮廓(我是这么干的...),尽量详尽,比如点击按钮后的实现一个页面的跳转,跳转之后的页面也画出来.为什么要这样呢?都知道界面是由控件和容器组成的,画的目的就是清楚要用哪些组件,并且根据自己的界面,然后组织容器,再进而组织布局.对于布局可能会比较麻烦一点,这要根据你的窗口

大规模高性能网站架构设计思路整理

近期关注了一些主流高并发大型网站如:大众点评.携程.去哪儿等 整理实现思路如下: 一.第一步 1.js .CSS.图片 优化压缩 2.站点动静分离,将动态网站单独部署.静态网站单独部署 3.数据库读写分离,比如:高频率读写的表分离 4.数据库优化,分表.分库.索引等 二.负载均衡 1.软件负载均衡,如:lvs,ngnix等 2.硬件负载均衡,如:F5等 三.缓存 1.数据缓存,如:memcacahe 2.Varnish Cache 3.Squid 四: 1.CDN

JS 数组常见操作汇总,数组去重、降维、排序、多数组合并实现思路整理

壹 ? 引 JavaScript开发中数组加工极为常见,其次在面试中被问及的概率也特别高,一直想整理一篇关于数组常见操作的文章,本文也算了却心愿了. 说在前面,文中的实现并非最佳,实现虽然有很多种,但我觉得大家至少应该掌握一种,这样在面试能解决大部分数组问题.在了解实现思路后,日常开发中结合实际场景优化实现,提升性能也是后期该考虑的. 本文主要围绕数组去重.数组排序.数组降维.数组合并.数组过滤.数组求差集,并集,交集,数组是否包含某项等知识点展开,附带部分知识拓展,在看实现代码前也建议大家先自