漫画:什么是冒泡排序?

简介: 什么是冒泡排序?冒泡排序的英文Bubble Sort,是一种最基础的交换排序。大家一定都喝过汽水,汽水中常常有许多小小的气泡,哗啦哗啦飘到上面来。这是因为组成小气泡的二氧化碳比水要轻,所以小气泡可以一点一点向上浮动。

image.pngimage.png



—————  当天上午  —————


image.png

image.png


image.png

image.png

image.png



什么是冒泡排序?


冒泡排序的英文Bubble Sort,是一种最基础的交换排序


大家一定都喝过汽水,汽水中常常有许多小小的气泡,哗啦哗啦飘到上面来。这是因为组成小气泡的二氧化碳比水要轻,所以小气泡可以一点一点向上浮动。

image.png


而我们的冒泡排序之所以叫做冒泡排序,正是因为这种排序算法的每一个元素都可以像小气泡一样,根据自身大小,一点一点向着数组的一侧移动。


具体如何来移动呢?让我们来看一个栗子:


image.png


有8个数组成一个无序数列:5,8,6,3,9,2,1,7,希望从小到大排序。


按照冒泡排序的思想,我们要把相邻的元素两两比较,根据大小来交换元素的位置,过程如下:


首先让5和8比较,发现5比8要小,因此元素位置不变。


接下来让8和6比较,发现8比6要大,所以8和6交换位置。


image.png

image.png


继续让8和3比较,发现8比3要大,所以8和3交换位置。


image.png


继续让8和9比较,发现8比9要小,所以元素位置不变。


接下来让9和2比较,发现9比2要大,所以9和2交换位置。


image.png

image.png


接下来让9和1比较,发现9比1要大,所以9和1交换位置。

image.png

image.png


最后让9和7比较,发现9比7要大,所以9和7交换位置。

image.png

image.png


这样一来,元素9作为数列的最大元素,就像是汽水里的小气泡一样漂啊漂,漂到了最右侧。


这时候,我们的冒泡排序的第一轮结束了。数列最右侧的元素9可以认为是一个有序区域,有序区域目前只有一个元素。

image.png


下面,让我们来进行第二轮排序:

首先让5和6比较,发现5比6要小,因此元素位置不变。


接下来让6和3比较,发现6比3要大,所以6和3交换位置。

image.png


继续让6和8比较,发现6比8要小,因此元素位置不变。


接下来让8和2比较,发现8比2要大,所以8和2交换位置。

image.png

image.png


接下来让8和1比较,发现8比1要大,所以8和1交换位置。

image.png

image.png


继续让8和7比较,发现8比7要大,所以8和7交换位置。

image.pngimage.png


第二轮排序结束后,我们数列右侧的有序区有了两个元素,顺序如下:

image.png


至于后续的交换细节,我们这里就不详细描述了,第三轮过后的状态如下:

image.png


第四轮过后状态如下:

b629c8676fe7204a05c5277acefadbed.png


第五轮过后状态如下:

image.png


第六轮过后状态如下:

ccb699adc3d22703aa2ed828e22293bf.png


第七轮过后状态如下(已经是有序了,所以没有改变):

image.png


第八轮过后状态如下(同样没有改变)

image.png


到此为止,所有元素都是有序的了,这就是冒泡排序的整体思路。


原始的冒泡排序是稳定排序。由于该排序算法的每一轮要遍历所有元素,轮转的次数和元素数量相当,所以时间复杂度是O(N^2) 

image.png


冒泡排序第一版:

public class BubbleSort {

  1. private static void sort(int array[])
  2. {
  3.    int tmp  = 0;
  4.    
       
    for(int i = 0; i < array.length; i++){
  5.        for(int j = 0; j < array.length - i - 1; j++)
  6.        {
  7.            if(array[j] > array[j+1])
  8.            {
  9.                tmp = array[j];
  10.                array[j] = array[j+1];
  11.                array[j+1] = tmp;
  12.            }
  13.        }
  14.    }
  15. }
  16. public static void main(String[] args){
  17.    int[] array = new int[]{5,8,6,3,9,2,1,7};
  18.    sort(array);
  19.    System.out.println(Arrays.toString(array));
  20. }

}


代码非常简单,使用双循环来进行排序。外部循环控制所有的回合,内部循环代表每一轮的冒泡处理,先进行元素比较,再进行元素交换。


image.png

image.png

image.png


————————————

image.pngimage.png


image.png

image.png



原始的冒泡排序有哪些优化点呢?


让我们回顾一下刚才描述的排序细节,仍然以5,8,6,3,9,2,1,7这个数列为例,当排序算法分别执行到第六、第七、第八轮的时候,数列状态如下:

image.png


很明显可以看出,自从经过第六轮排序,整个数列已然是有序的了。可是我们的排序算法仍然“兢兢业业”地继续执行第七轮、第八轮。


这种情况下,如果我们能判断出数列已经有序,并且做出标记,剩下的几轮排序就可以不必执行,提早结束工作。



冒泡排序第二版


public class BubbleSort {

  1. private static void sort(int array[])
  2. {
  3.    int tmp  = 0;
  4.    for(int i = 0; i < array.length; i++)
  5.    {
  6.        //有序标记,每一轮的初始是true
  7.        boolean isSorted = true;
  8.        for(int j = 0; j < array.length - i - 1; j++)
  9.        {
  10.            if(array[j] > array[j+1])
  11.            {
  12.                tmp = array[j];
  13.                array[j] = array[j+1];
  14.                array[j+1] = tmp;
  15.                //有元素交换,所以不是有序,标记变为false
  16.                isSorted = false;
  17.            }
  18.        }
  19.        if(isSorted){
  20.            break;
  21.        }
  22.    }
  23. }
  24. public static void main(String[] args){
  25.    int[] array = new int[]{5,8,6,3,9,2,1,7};
  26.    sort(array);
  27.    System.out.println(Arrays.toString(array));
  28. }

}


这一版代码做了小小的改动,利用布尔变量isSorted作为标记。如果在本轮排序中,元素有交换,则说明数列无序;如果没有元素交换,说明数列已然有序,直接跳出大循环。


image.png

image.png


为了说明问题,咱们这次找一个新的数列:

image.png


这个数列的特点是前半部分(3,4,2,1)无序,后半部分(5,6,7,8)升序,并且后半部分的元素已经是数列最大值。


让我们按照冒泡排序的思路来进行排序,看一看具体效果:


第一轮


元素3和4比较,发现3小于4,所以位置不变。


元素4和2比较,发现4大于2,所以4和2交换。


image.pngimage.png


元素4和1比较,发现4大于1,所以4和1交换。

image.png

image.png


元素4和5比较,发现4小于5,所以位置不变。


元素5和6比较,发现5小于6,所以位置不变。


元素6和7比较,发现6小于7,所以位置不变。


元素7和8比较,发现7小于8,所以位置不变。


第一轮结束,数列有序区包含一个元素:

image.png


第二轮


元素3和2比较,发现3大于2,所以3和2交换。

image.png

image.png


元素3和1比较,发现3大于1,所以3和1交换。

image.png

image.png


元素3和4比较,发现3小于4,所以位置不变。


元素4和5比较,发现4小于5,所以位置不变。


元素5和6比较,发现5小于6,所以位置不变。


元素6和7比较,发现6小于7,所以位置不变。


元素7和8比较,发现7小于8,所以位置不变。


第二轮结束,数列有序区包含一个元素:

image.png

image.png


image.png

image.png


这个问题的关键点在哪里呢?关键在于对数列有序区的界定。


按照现有的逻辑,有序区的长度和排序的轮数是相等的。比如第一轮排序过后的有序区长度是1,第二轮排序过后的有序区长度是2 ......


实际上,数列真正的有序区可能会大于这个长度,比如例子中仅仅第二轮,后面5个元素实际都已经属于有序区。因此后面的许多次元素比较是没有意义的。


如何避免这种情况呢?我们可以在每一轮排序的最后,记录下最后一次元素交换的位置,那个位置也就是无序数列的边界,再往后就是有序区了。



冒泡排序第三版


public class BubbleSort {

  1. private static void sort(int array[])
  2. {
  3.    int tmp  = 0;
  4.    //记录最后一次交换的位置
  5.    int lastExchangeIndex = 0;
  6.    //无序数列的边界,每次比较只需要比到这里为止
  7.    int sortBorder = array.length - 1;
  8.    for(int i = 0; i < array.length; i++)
  9.    {
  10.        //有序标记,每一轮的初始是true
  11.        boolean isSorted = true;
  12.        for(int j = 0; j < sortBorder; j++)
  13.        {
  14.            if(array[j] > array[j+1])
  15.            {
  16.                tmp = array[j];
  17.                array[j] = array[j+1];
  18.                array[j+1] = tmp;
  19.                //有元素交换,所以不是有序,标记变为false
  20.                isSorted = false;
  21.                //把无序数列的边界更新为最后一次交换元素的位置
  22.                lastExchangeIndex = j;
  23.            }
  24.        }
  25.        sortBorder = lastExchangeIndex;
  26.        if(isSorted){
  27.            break;
  28.        }
  29.    }
  30. }

  31. public static void main(String[] args){
  32.    int[] array = new int[]{3,4,2,1,5,6,7,8};
  33.    sort(array);
  34.    System.out.println(Arrays.toString(array));
  35. }

}


这一版代码中,sortBorder就是无序数列的边界。每一轮排序过程中,sortBorder之后的元素就完全不需要比较了,肯定是有序的。

image.png

image.png


image.png


image.png


几点补充:


本漫画纯属娱乐,还请大家尽量珍惜当下的工作,切勿模仿小灰的行为哦。


                   —————END—————

相关文章
|
搜索推荐 算法
齐姐漫画:排序算法(一)
借用《算法导论》里的例子,就是我们打牌的时候,每新拿一张牌都会把它按顺序插入,这,其实就是插入排序。
123 0
齐姐漫画:排序算法(一)
|
搜索推荐 算法 IDE
齐姐漫画:排序算法(二)之「 归并排序」和「外排序」
那我们借用 cs50 里的例子,比如要把一摞卷子排好序,那用并归排序的思想是怎么做的呢?
140 0
齐姐漫画:排序算法(二)之「 归并排序」和「外排序」
漫画:什么是选择排序?
我们假定要获得升序数列,冒泡排序的原理是什么呢? 顾名思义,就是把每一元素和下一个元素进行比较和交换,使得较大的元素像气泡一样向右侧移动:
漫画:什么是选择排序?
漫画:什么是插入排序?
人们如何进行扑克牌的排序呢? 举个例子,比如我手中有红桃6,7,9,10这四张牌,已经处于升序排列:这时候,我又抓到了一张红桃8,如何让手中的五张牌重新变成升序呢?用冒泡排序,选择排序,亦或是快速排序?
131 0
漫画:什么是插入排序?
|
存储 缓存 搜索推荐
漫画:“排序算法” 大总结
冒泡排序: 漫画:什么是冒泡排序? 选择排序: 漫画:什么是选择排序? 插入排序: 漫画:什么是插入排序? 此外还有冒泡排序的变种,鸡尾酒排序: 漫画:什么是鸡尾酒排序?
137 0
漫画:“排序算法” 大总结
|
存储 算法
漫画:什么是归并排序?
举个例子,有A、B、C、D、E、F、G、H一共8个武术家参考参加比武大会。 第一轮,两两一组,有4名选手胜出(四分之一决赛) 第二轮,两两一组,有两名选手胜出(半决赛) 第三轮,仅剩的两人一组,冠军胜出(总决赛)
漫画:什么是归并排序?
|
搜索推荐 算法 Shell
漫画:什么是希尔排序?
像这样逐步分组进行粗调,再进行直接插入排序的思想,就是希尔排序,根据该算法的发明者,计算机科学家Donald Shell的名字所命名。 上面示例中所使用的分组跨度(4,2,1),被称为希尔排序的增量,增量的选择可以有很多种,我们在示例中所用的逐步折半的增量方法,是Donald Shell在发明希尔排序时提出的一种朴素方法,被称为希尔增量。
114 0
漫画:什么是希尔排序?
|
算法 搜索推荐
漫画:什么是基数排序?
数组每一个下标位置的值,代表了数列中对应整数出现的次数。 有了这个“统计结果”,排序就很简单了。直接遍历数组,输出数组元素的下标值,元素的值是几,就输出几次: 0,1,1,2,3,3,3,4,4,5,5,6,7,7,8,9,9,9,9,10 显然,这个输出的数列已经是有序的了。 这就是计数排序的朴素版本。
119 0
漫画:什么是基数排序?
|
存储
漫画:什么是计数排序?
如何给无序的随机整数排序呢? 非常简单,让我们遍历这个无序的随机数列,每一个整数按照其值对号入座,对应数组下标的元素进行加1操作。 比如第一个整数是9,那么数组下标为9的元素加1
100 0
漫画:什么是计数排序?