排序,顾名思义,就是把一坨数字按照某种特定的顺序排列好了,比如从小到大又或者从大到小。

当然了,当王老师第一次把6,4,7,2,9,8,1七个数字写到黑板上并开始提问:“ 你们有什么办法让这一坨数字从小到大排列呢? ”,作为菜B,你的第一想法肯定是先把脑袋深深淹没在书堆后并默念:“ 千万别点老子 ”。

1. 排序篇之冒泡排序 - 图1

然而事情往往都是“ 你怕啥,就来啥 ”,硬着头皮站起来,不想一个法子看起来是别想坐下去了,而且,秋雅还在旁边看着呢。

作为菜B,上了多少年学,第一次遇到如此艰难的算数运算题,脑海中想动的也自然是最直接明了的办法了:“先拿第一个位置上的数字6跟后面的每一个数字都轮番比试大小,放到本题中呢,就是先跟4比大小,6比4大,那么6和4互换一下位置,然后由4继续和后面的数字进行比较,也就是4和7进行比大小,4比7小,所以没有任何操作,接着4和2比大小,4比2大,所以4和2交换位置,然后由2继续后面的数字比试,也就是2和9比大小,2比9小,所以没有任何操作,接着2比8小,没有任何操作,最后2和1比大小,2比1大,所以2和1交换位置。经过这一轮比较,我们可以保证最小的数字1已经放到第一位了,然后下面从第二位开始就可以了,重复上述动作即可”。

这个时候,不要偷懒,拿起你的纸和笔和我一样,手工走一下第一轮比试吧,会让你加深理解的!我给大家提供一下我的排序流程图:

1. 排序篇之冒泡排序 - 图2

当然了菜B也有菜B的尊严,既然想出来了,想必也是不容易的,用程序实现一把:

  1. <?php
  2. $arr = [ 6, 4, 7, 2, 9, 8, 1 ];
  3. $length = count( $arr );
  4. // 先从挑选第一个数字开始和后面的数字轮番比较
  5. // 也就是用6开始和后面的数字进行比较
  6. for( $outer = 0; $outer < $length; $outer++ ){
  7. // 和后面紧跟着的第一个数字开始一直到末尾最后一个数字,比赛大小
  8. for( $inner = $outer + 1; $inner < $length; $inner++ ){
  9. // 如果比后面的数字大,那么二者交换一下座位
  10. if( $arr[ $outer ] > $arr[ $inner ] ){
  11. $temp = $arr[ $outer ];
  12. $arr[ $outer ] = $arr[ $inner ];
  13. $arr[ $inner ] = $temp;
  14. }
  15. }
  16. }
  17. print_r( $arr );

运行一把代码,结果如下图所示:1. 排序篇之冒泡排序 - 图3

简单总结一下要点:

  • 需要两次循环才可以完成,外面一层循环( outer ),里面再包一层循环( inner )
  • 比大小的时候,如果前面的数字比后面的大,二者是要交换位置的,但是交换完位置后,原来的比试不能中断,还要继续,而且不能再拿原来的数字和后面的比了。比如6和4,6比4大,6和4交换位置,交换完毕后该轮比试还未完毕,但是却不能拿6和后面的比试了,应该拿4和后面的比试
  • 值得注意的是,第一轮比试完毕后,我们竟然很悲催地将2这个倒数第二小的数字弄到了最后,妈蛋,怎么感觉白费功夫

如果你觉得冒泡排序这就已经完成了,那王老师就只能呵呵了,本质上讲,上述办法更多倾向于是一种“交换排序”,并非冒泡,冒泡嘛,形象一点儿,是一个泡泡往上涌,然后和“相邻的泡泡”比试,最后最小的泡泡浮到了水面上。既然我们想要让小的从后排都涌到前排,那么为何不尝试一下最后向前呢?

这种情况下,当outer = 1的时候,我们从1开始,让1和8比,1比8小,互换位置,1和9比,以此类推,推演图如下(再次强烈建议大家亲自动手动笔画推演图,非常有助于理解,这也是我为什么不用画图软件的原因,希望能够带动围观者一起下笔亲自画):

1. 排序篇之冒泡排序 - 图4

当outer = 1这一轮排序完成后,最终序列是1,6,4,7,2,9,8。而在上面第一版本的排序第一轮排序完成是1,6,7,4,9,8,2。

通过对比,我们可以觉察出来,这种算法有一种将小数字往前排,大数字往后排的趋势动力,但从这点上看,确实是要比上个版本进步了很多的。现在,我们通过程序来实现一把:

  1. <?php
  2. $arr = [ 6, 4, 7, 2, 9, 8, 1 ];
  3. $length = count( $arr );
  4. // 外部循环
  5. for( $outer = 0; $outer < $length; $outer++ ){
  6. // 当外部循环开始第一轮的时候,从倒数第一位开始往前对比,一直到与正数第一位比较完后终止
  7. // 当外部循环开始第一轮的时候,从倒数第一位开始往前对比,一直到与正数第二位比较完后终止
  8. for( $inner = $length - 1; $inner > $outer; $inner-- ){
  9. if( $arr[ $inner ] < $arr[ $inner - 1 ] ){
  10. $temp = $arr[ $inner ];
  11. $arr[ $inner ] = $arr[ $inner - 1 ];
  12. $arr[ $inner - 1 ] = $temp;
  13. }
  14. }
  15. }
  16. print_r( $arr );

可以将outer外部循环每次完成后的数组打印出来,你可以观察整个数组中数字移动的趋势,整体看,是小的数字在就像泡泡一样往上浮起来,而大的数字则在往下沉,这才叫“冒泡”!

现在,我们假设一种极端的情况,比如给我们提供的数组是1,2,3,4,5,6,怎么办?你一看就知道,这不用排啊,是啊,王老师也一看就知道不用排了,但计算机可不知道啊!这种明明已经排好序的数组,在你的代码业务流程中依然还是要让程序白白跑一边的,在低碳环保的今天,王老师并不认为这是一种可取的做法,白了你一眼说:“应该极力降低这种损耗成本,动动脑子改改吧,改完后再提交到班级gitlab上”。

实际上,如果说这一坨数字本身就是从小到达顺序的,那么我们从代码上看就知道,它一定不会发生“交换位置”这一段流程,所以,在鼓励师秋雅给你一顿按摩吹捧后,你决定如此修改一下代码:

  1. $arr = [ 1,2,3,4,5,6 ];
  2. $length = count( $arr );
  3. // 外部循环
  4. $swap = true;
  5. for( $outer = 0; $outer < $length && $swap; $outer++ ){
  6. echo "outer : ".$outer.PHP_EOL;
  7. $swap = false;
  8. // 当外部循环开始第一轮的时候,从倒数第一位开始往前对比,一直到与正数第一位比较完后终止
  9. // 当外部循环开始第一轮的时候,从倒数第一位开始往前对比,一直到与正数第二位比较完后终止
  10. for( $inner = $length - 1; $inner > $outer; $inner-- ){
  11. if( $arr[ $inner ] < $arr[ $inner - 1 ] ){
  12. $temp = $arr[ $inner ];
  13. $arr[ $inner ] = $arr[ $inner - 1 ];
  14. $arr[ $inner - 1 ] = $temp;
  15. $swap = true;
  16. }
  17. }
  18. }
  19. print_r( $arr );

运行代码,我们可以看到程序只打印了一次outer,也就说程序只执行了一次outer大循环后,之后便不再浪费自己脑力体力了。

撒花,一个从无到有,从烂到好冒泡排序就此诞生,完结!

后感:第一次写算法,文笔组织不是特别好,也能明显感觉出来写算法要比写其他文章表达难度更高,很多东西用语言不太容易描述,可能动画会更好,但我还是希望各位亲自动手,上笔上纸,亲自操刀演练,方能理解深刻。