2019独角兽企业重金招聘Python工程师标准>>>

冒泡排序是种很简单的排序方式。如果用循环方式,通常就是两层循环。

由于两层循环都是与元素个数N线性相关,所以可以简单估算出它的时间复杂度是O(N2),通常而言,这是较糟糕的复杂度。

当然,这也几乎是所有简单方式的宿命,想简单就别想效率高!

前面篇章说到递归也是一种循环,所以普通循环能解决的问题,用递归也能解决。我们来看看怎么写出它来。

定义好接口先

首先啰嗦几句写代码习惯问题:

要有服务意识,要有API意识。

先把接口定义好,与具体的实现分开。另外则作一些基本的判断,内部实现就不需要再作判断了,也能简化内部实现:

        // 对外接口public static void sort(int[] array) {if (array != null && array.length > 1) {bubbleSort(array);}}// 具体内部实现private static void bubbleSort(int[] array) {// TODO}

想更加通用就用Comparable接口,不过这些不是这里的重点,这里就简单用array了。

主体结构

前面说到,递归分成基本情形(base case)递归情形(recursive case),所以,不管三七二十一,先把主体结构写出来:

private static void bubbleSort(int[] array) {// TODOif (baseCase) {return;} else {// 1. do something maybe// ...// 2. 递归调用// bubbleSort(xxx);}}

base case

这个简单,当只有一个元素时,自然就无需排序了,所以:

            if (array.length == 1) {return;}

显然,base case没做任何事,因此只要把代码条件反转一下,就可以省略掉这个base case了。

recursive case

如何找出递归模式呢?我们从一个具体的事例考虑:

假如有四个元素:6 9 8 4,假设向右边冒泡,那么从左向右两两比较并根据情况决定是否交换:

第一次比较6和9,6比9小,无需交换,继续;

第二次比较9和8,9比8大,所以交换它们得到: 6 8 9 4;

第三次比较9和4,9比4大,交换,得到6 8 4 9。

那么,经过一轮冒泡之后,最大的元素9已经冒泡到了最右边上,然后下一步就可以只考虑剩下的三个元素了,而这正好可视作一个新的待排序的数组。

先不考察冒泡的细节,用一个抽象的bubble表示它,那么可以归纳出如下递归模式:

bubbleSort一个元素为N的数组就是:

  1. 进行一轮冒泡(bubble),

  2. 然后对左边的N-1个元素(子数组)递归地进行bubbleSort。

如下图示:

据此,写出以下代码(省略了base case):

private static void bubbleSort(int[] array) {// TODO pseudo codeif (array.length > 1) {bubble(array);bubbleSort(array.subArray(0, array.length - 1));}}

你可能会想,array并没有subArray方法呀,另外应该是在原数组上排序。没错,这里也注明了这是伪代码(pseudo code),此刻只需要思考抽象的逻辑是否有问题即可,诸如是否会发生无限递归呀等情况。

敲定细节

代码中还是伪代码的形式,显然我们是要在原数组上排序,这使得我们必须增加一个显式的参数

如果array有更加对象化的方法来能直观表示subArray(并非指复制的那种,而是一种指向新位置的引用),那么增加参数就不是必要的了。目前我们无法摆脱array的结构限制去思考。(这其实暴露了java在表达能力上的缺陷或者说array的设计上存在一些问题,使得我们只能笨拙地去表达我们想要的意思)

根据前面的归纳,定为N-1来表示子数组subArray:

bubbleSort(n – 1, array);

注:你可能忍不住会把这里的n想像成array的index。没错,它最终是要映射到index上,但此刻你只要抽象地从概念上把握它即可,这里的n就是指n个元素,它不是什么index。

方法要形成递归,所以定义处也修改,增加一参数n:

private static void bubbleSort(int n, int[] array)

公共接口里调用时也因此要作改动,需要显式传入N,对于初始情况,N也即是array.length:

bubbleSort(array.length, array);

另外有一点要特别注意,因为不是传递subArray,array.length始终不变,所以if语句中的判断已经不正确:

必须由if(array.length > 1)改为if(n > 1)

现在考察bubble,显然N跟array都要往里面传。现在直接将bubble方法考虑成递归的,以减少中间环节。那么,还要再增加一个参数bn,表示有几个元素需要bubble。

让IDE生成方法签名,最终结果如下:

// 对外接口public static void sort(int[] array) {if (array != null && array.length > 1) {bubbleSort(array.length, array);}}// 具体内部实现private static void bubbleSort(int n, int[] array) {if (n > 1) {bubble(n, n, array);bubbleSort(n - 1, array);}}private static void bubble(int bn, int n, int[] array) {// TODO}

注:bubble(n, n, array)中第一个n表示有n个元素需要冒泡,第二个n表示有n个元素需要排序。

其实你也能感觉到,这完全就是面向过程式的编程。

至此,主体结构已经OK了。现在考虑冒泡bubble的实现问题。

冒泡子过程

冒泡同样可视作一个递归的过程。按着一样的思路,先是一个大体的框架:

private static void bubble(int bn, int n, int[] array) {if (baseCase) {return;} else {// 1. do something// ...// 2. 递归调用// bubble(xx, n, array);}}

base case

显然,bn=1时表示只有一个元素,就无需冒泡了,直接返回:

        if (bn == 1) {return;}

显然,这一case也是可以省略的。

recursive case

其实就是一个从左到右不断比较与交换的过程。根据前面的例子,同样可以归纳出它的递归模式如下:

bubble一个元素为N的数组就是:

  1. 对第一个元素(以及它后续的那个元素)进行一次比较和交换(compareAndSwitch),

  2. 然后对余下的N-1个元素递归地进行bubble。

比较与交换同样的先用一个抽象的过程表示,据此,得出代码如下(同样省略了base case):

    private static void bubble(int bn, int n, int[] array) {if (bn > 1) {compareAndSwitch(bn, n, array);bubble(bn - 1, n, array);}}private static void compareAndSwitch(int bn, int n, int[] array) {// TODO}

你会发现这里的bubble与前面的bubbleSort在形式上是非常像的。

有些人可能会在bn或者n的实际含义上纠结,总是忍不住把它们往array的index上去想,这实际上还是没能摆脱往具体实现层面上去思考的惯性。我们应该抽象地去思考,不要老惦记着array,其实关键就在于要表达清楚我们的意思,这样就行了。

我们最终肯定要处理index的问题,但目前还不是时候,你应该坚信index肯定能用bn及n表达出来,你只需往下传递它们就是了。

比较与交换

现在终于要处理让我们有点头痛的index问题了。

我们只需考察最简单的两个元素第一次比较的情况,此时n=2,bn=2,我们要比较array[0],array[1],显然0=n-bn,也即是要比较array[n-bn]和array[n-bn+1]。

最终结果如下:

    private static void compareAndSwitch(int bn, int n, int[] array) {int index = n - bn;if (array[index] > array[index + 1]) {int temp = array[index];array[index] = array[index + 1];array[index + 1] = temp;}}

你可能有点怀疑,这样就正确了吗?我们可以实际测试一下即可,通常八九不离十。

改进

如果一轮bubble没有发生任何交换,意味着数组已经是有序的了,可以提前break。可以引入一个boolean来达到这一点,这里就不演示了。最终的代码如下:

// 对外接口public static void sort(int[] array) {if (array != null && array.length > 1) {bubbleSort(array.length, array);}}// 具体内部实现private static void bubbleSort(int n, int[] array) {if (n > 1) {bubble(n, n, array);bubbleSort(n - 1, array);}}// 冒泡private static void bubble(int bn, int n, int[] array) {if (bn > 1) {compareAndSwitch(bn, n, array);bubble(bn - 1, n, array);}}// 比较与交换private static void compareAndSwitch(int bn, int n, int[] array) {int index = n - bn;if (array[index] > array[index + 1]) {int temp = array[index];array[index] = array[index + 1];array[index + 1] = temp;}}

总结

其实写递归程序的套路经常是比较固定的,大的方面就是一个if else,递归部分可以放if,也可以放在else,就看if条件是如何判断了。然后就是找出递归模式,把条件填充上去。暂时不明朗的地方,可以先用一个抽象来表达,然后逐步地把细节完善。

对递归程序而言,通常一旦正确了,它就是正确的。

当然,这好像是一句废话。为什么这么说呢?要知道正确的方式通常只有一种,而错误的方式则有千万种。

俄国有位师太(叫托尔斯泰),在他的《安娜·卡列妮娜》中曾经曰过:“幸福的家庭总是相似的,不幸的家庭各有各的不幸。”

'All happy families are alike; each unhappy family is unhappy in its own way.'--Leo Tolstoy Anna Karenina

所以,这里真正想表达的意思是,递归式通常更为精练,没有过多枝枝蔓蔓的东西。它的环节比较少,过程式的东西较少,写法通常也很固定,因而减少了你犯错的机会。你只要找准了终止条件和递归模式,它通常就自然而然的正确了。

另外则是要强调抽象的思考问题,不要过早地陷于具体实现及细节的纠缠中,而是要一步步地最终使得一切水落石出。

当然,就这个例子而言,比起普通循环而言,并没有什么优势。

递归反而可能显得有点拖沓,这里不过是想说明普通循环能解决的问题也能用递归来解决;

另一方面,如果还不习惯于递归式的表达,恐怕写起来比普通循环更费劲;

此外,除非是简单小量的排序,否则性能也是个问题。

在下一篇章中,将探讨一个更加复杂的例子,也即是前面提到的求换零钱种数的问题,那时才能体现出递归的优势来。

转载于:https://my.oschina.net/goldenshaw/blog/360530

用递归解决冒泡排序问题相关推荐

  1. Java 递归解决 quot;仅仅能两数相乘的计算器计算x^yquot; 问题

    Java 递归解决 "仅仅能两数相乘的计算器计算x^y" 问题 /*** 求一个数的乘方* 求x^y,y是一个正整数. 设计算器仅仅能计算两数相乘,不能一次计算n个数相乘. * 知 ...

  2. python3数字全排列怎么搞_python使用递归解决全排列数字示例

    第一种方法:递归 复制代码 代码如下: def perms(elements): if len(elements) <=1: yield elements else: for perm in p ...

  3. c语言递归汉诺塔次数,c语言递归解决汉诺塔参数变化的疑惑

    c语言递归解决汉诺塔参数变化的疑惑 答案:3  信息版本:手机版 解决时间 2020-04-05 14:20 已解决 2020-04-05 10:49 #include void main() {vo ...

  4. 使用递归解决斐波那契数列的性能问题

    使用递归解决斐波那契数列的性能问题 参考文章: (1)使用递归解决斐波那契数列的性能问题 (2)https://www.cnblogs.com/mlw1814011067/p/9439651.html ...

  5. c语言递归解决汉诺塔问题

    c语言递归解决汉诺塔问题 参考文章: (1)c语言递归解决汉诺塔问题 (2)https://www.cnblogs.com/didiaoxiaoguai/p/6686407.html 备忘一下.

  6. 【C语言】利用递归解决猴子吃桃问题

    [C语言]利用递归解决猴子吃桃问题 参考文章: (1)[C语言]利用递归解决猴子吃桃问题 (2)https://www.cnblogs.com/ieybl/p/6597937.html 备忘一下.

  7. C++ sqlite3解决中文排序问题

    导言:sqlite3默认的编码方式为UTF8编码,而在UTF8编码下,中文不是按照拼音顺序编码的,所以想解决中文排序问题,必须自定义排序规则,将UTF8编码转换成GB2312编码(GB2312编码中文 ...

  8. 2021年scrapy一分钟内爬取笔趣阁整本小说存为txt|解决章节排序问题

    本人是一个小说迷,喜欢用电纸书看小说,但苦于难以寻找网络小说的txt版本,加之最近学习了一下怎么用scrapy爬取网页数据,所以想到去使用scrapy框架来爬取笔趣阁小说存为txt文件,在爬取过程中对 ...

  9. 100个python算法超详细讲解:递归解决分鱼问题

    1.问题描述 A.B.C.D.E这5个人合伙夜间捕鱼,凌晨时都已经疲惫不堪,于是各自在河边的 树丛中找地方睡着了.第二天日上三竿时,A第一个醒来,他将鱼平分为5份,把多余的 一条扔回河中,然后拿着自己 ...

最新文章

  1. windows远程桌面端口修改
  2. Java性能优化(12):最小化类和成员可访问能力
  3. 开源微服务运行时 Dapr 1.0 版本
  4. linux下qt实现计算器,QT实现计算器
  5. JS一维数组转化为三维数组有这个方法就够了
  6. php 获取图片的宽高,JS怎么获取图片当前宽高
  7. DockerDesktop安装以后,控制台输入docker version报错This error may indicate that the docker daemon is not running
  8. 京东商品知识图谱,约10万商品品牌,约65万品牌销售关系
  9. MVVM 实战之计算器
  10. sql 查询数据长度
  11. 重构计算力 浪潮M5新一代服务器闪耀登场
  12. tomcat8安装及配置详细步骤(win10)
  13. 苹果手机在微信里自带计算机功能,苹果版微信又更新,这些功能全是我想要的!...
  14. 递归例题讲解 一本通1215:迷宫 答案解析
  15. 名帖234 俞和 行书《云锦帖》
  16. python 修改图片尺寸 留白_php 图片指定留白叠加缩放
  17. CB测试证书及IECEE-CB体系
  18. 计算机管理器鼠标不见了怎么办,电脑鼠标箭头不见了的解决方法
  19. vue手机登录页及短信验证
  20. #读书#黑白之间——中国历史文化中的任侠者

热门文章

  1. 深度 | Android 整体设计及背后意义
  2. MFC PreTranslateMessage介绍
  3. EclipseIDEA使用经验
  4. Linux下的高级文件权限ACL
  5. jvm第五节-性能调优工具使用
  6. 1. max31855 热电偶至输出转换器 分析
  7. 连接Access数据库的Provider错误
  8. 101个微软提供的Visual Studio 2005示例
  9. socket编程(二)
  10. 中专计算机总结论文,中专计算机毕业论文...doc