动画:一篇文章快速学会冒泡排序

内容介绍

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sLEhReL5-1585735625407)(https://user-gold-cdn.xitu.io/2020/3/18/170edecc0d94c442?w=778&h=566&f=png&s=26079)]

冒泡排序原理

冒泡排序(Bubble Sort)是一种交换排序,它的基本思想是:两两比较相邻的数据,大的往后放。

大家一定看过水中的气泡,小气泡一点一点向上浮动变成大气泡。

冒泡排序这个算法的名字由来是因为元素像水中的气泡冒泡一样,小的元素会经过交换慢慢“浮”到数列的一端。我们来看冒泡排序的动画演示。

冒泡排序的分析

一般没有特殊要求排序算法都是升序排序,小的在前,大的在后。数组由{6, 5, 4, 1, 3, 2}这6个无序元素组成。

冒泡排序交换过程图示(第一轮):

第一次相邻的元素6和元素5比较,发现前面的元素大于后面的元素,交换位置。

交换后

第二次相邻的元素6和元素4比较,发现前面的元素大于后面的元素,交换位置。

交换后

第三次相邻的元素6和元素1比较,发现前面的元素大于后面的元素,交换位置。

交换后

第四次相邻的元素6和元素3比较,发现前面的元素大于后面的元素,交换位置。

交换后

第五次相邻的元素6和元素2比较,发现前面的元素大于后面的元素,交换位置。

交换后

经过第一轮的五次比较,元素6由数组的最前面冒泡到数组的最后面。最大的元素就在数组的最后面。注意观察,经过一轮比较只能将参数比较的数字中的最大的数字冒泡到右边,其余的还是无序的。因此需要进行多轮比较才能将数组变成有序的。

冒泡排序交换过程图示(第二轮):

经过第二轮的四次比较,元素5由数组的最前面冒泡到数组的倒数第二的位置。大的元素就在数组的后面。数组最后面的元素5和元素6可以比较也可以不比较,对结果没有影响。到时候我们写代码的时候注意一下即可。

冒泡排序交换过程图示(第三轮):

交换过程和前两轮分析的类似,我们省略过程,最终第三轮交换后的结果:

冒泡排序交换过程图示(第四轮):

交换过程和前两轮分析的类似,我们省略过程,最终第四轮交换后的结果:

冒泡排序交换过程图示(第五轮):

第五轮比价元素1和元素2,这两个元素本身已经有序了,所以没有变化,最终第五轮交换后的结果和第四轮一样:

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

冒泡排序代码编写

我们发现每一轮都需要从索引0开始,相邻元素两比较,需要使用一个循环(内循环)控制每轮的比较元素。另外数组6个元素需要经过五轮比较,也需要使用一个循环(外循环)控制比较的轮数,而且比较的轮数是元素的数量-1。

public class BubbleSortTest {
    public static void main(String[] args) {
        int[] arr = new int[]{6, 5, 4, 1, 3, 2};
        System.out.println("排序前数组" + Arrays.toString(arr));
        bubbleSort1(arr);
    }

    // 冒泡排序
    public static void bubbleSort1(int[] arr) {
        for (int i = 0; i < arr.length - 1; i++) { // 外循环控制比较的轮数,轮数是元素的数量-1
            for (int j = 0; j < arr.length - 1 - i; j++) { // 内循环控制每轮比较的元素,轮数越大,比较的次数越少
                if (arr[j] > arr[j+1]) { // i和i+1索引比较,也就是相邻元素比较,大的往后放
                    System.out.print("\t元素 " + arr[j] + " 和 " + arr[j+1] + " 比较, ");
                    int temp = arr[j];
                    arr[j] = arr[j+1];
                    arr[j+1] = temp;
                    System.out.println("比较后" + Arrays.toString(arr));
                }
            }
            System.out.println("排序" + (i+1) + "轮后: " + Arrays.toString(arr));
        }
    }
}

运行效果如下:

排序前数组[6, 5, 4, 1, 3, 2]
	元素 65 比较, 比较后[5, 6, 4, 1, 3, 2]
	元素 64 比较, 比较后[5, 4, 6, 1, 3, 2]
	元素 61 比较, 比较后[5, 4, 1, 6, 3, 2]
	元素 63 比较, 比较后[5, 4, 1, 3, 6, 2]
	元素 62 比较, 比较后[5, 4, 1, 3, 2, 6]
排序1轮后: [5, 4, 1, 3, 2, 6]
	元素 54 比较, 比较后[4, 5, 1, 3, 2, 6]
	元素 51 比较, 比较后[4, 1, 5, 3, 2, 6]
	元素 53 比较, 比较后[4, 1, 3, 5, 2, 6]
	元素 52 比较, 比较后[4, 1, 3, 2, 5, 6]
排序2轮后: [4, 1, 3, 2, 5, 6]
	元素 41 比较, 比较后[1, 4, 3, 2, 5, 6]
	元素 43 比较, 比较后[1, 3, 4, 2, 5, 6]
	元素 42 比较, 比较后[1, 3, 2, 4, 5, 6]
排序3轮后: [1, 3, 2, 4, 5, 6]
	元素 32 比较, 比较后[1, 2, 3, 4, 5, 6]
排序4轮后: [1, 2, 3, 4, 5, 6]
排序5轮后: [1, 2, 3, 4, 5, 6]

这是最原始的冒泡排序,该排序算法的每一轮要遍历所有元素,并且轮数和元素数量只少1,时间复杂度:
最优时间复杂度:O(n^2) (即使元素有序还是需要进行比较)
最坏时间复杂度:O(n^2)
稳定性:稳定

这个算法的效率是非常低的。

冒泡排序代码优化1

优化1: 如果有一轮发现没有需要排序的,说明已经有序了,可以不用再遍历比较了。如下图所示,第一轮比较过后,第二轮比较时发现元素已经全部是有序的,直接退出就没有必要再进行后面第三,第四,五轮的比较了。

代码如下:

public class BubbleSortTest2 {
    public static void main(String[] args) {
        int[] arr = new int[]{1, 3, 2, 4, 5, 6};
        System.out.println("排序前数组" + Arrays.toString(arr));
        bubbleSort2(arr);
    }

    // 优化1:如果有一轮发现没有需要排序的,说明已经有序了.可以不用再遍历比较了
    public static void bubbleSort2(int[] arr) { // 0.132s
        for (int i = 0; i < arr.length - 1; i++) {
            boolean sorted = true; // 用于标记数组是否有序,如果这轮一个元素都没有交换说明有序,后面几轮不需要在排序了
            for (int j = 0; j < arr.length - 1 - i; j++) {
                if (arr[j] > arr[j+1]) {
                    int temp = arr[j];
                    arr[j] = arr[j+1];
                    arr[j+1] = temp;
                    sorted = false; // 有元素需要交换,说明数组还是无序的.
                }
            }
            if (sorted) {
                System.out.println("已经有序了, length = " + arr.length + ", 第 " + (i+1) + "轮");
                break;
            }
        }
    }
}

运行效果如下:

排序前数组[1, 3, 2, 4, 5, 6]
已经有序了, length = 6,2

利用布尔变量sorted作为标记。如果在本轮排序中,没有元素交换,说明数组已经有序,直接跳出大循环。经过这次优化后,如果数组已经有顺序了,可以减少比较的轮数。

冒泡排序代码优化2

优化2: 问题是在于j<array.length– i– 1。每轮都会比较到array.length– i– 1这个位置,如果后面已经是有序的就不需要比较,下一轮只需要比较到上一轮的最后一次交换的小值那个地方即可,减少每轮的比较次数。

优化前第二轮需要比较到索引4的位置,优化后第二轮只需要比较到上次最后比较的那个位置,也就是lastPosition的位置

代码如下:

public class BubbleSortTest2 {
    public static void main(String[] args) {
        int[] arr = new int[]{3, 2, 1, 4, 5, 6, 7};

        System.out.println("排序前数组" + Arrays.toString(arr));
        bubbleSort3(arr);
    }

    // 优化2:问题是在于j<array.length– i– 1。如果后面已经是有序的就不需要比较
    // 下一轮只需要比较到上一轮的最后一次交换的小值那个地方即可
    public static void bubbleSort3(int[] arr) { // 0.132s
        int lastPosition = 0; // 记录这一轮最后一次比较的较小那个值,下一轮就比较到这里
        int len = arr.length - 1; // len表示下一轮要比较到哪里?后面会让len = lastPosition
        for (int i = 0; i < arr.length - 1; i++) {
            boolean sorted = true; // 用于标记数组是否有序,如果这轮一个元素都没有交换说明有序,后面几轮不需要在排序了
            for (int j = 0; j < len; j++) {
                if (arr[j] > arr[j+1]) {
                    int temp = arr[j];
                    arr[j] = arr[j+1];
                    arr[j+1] = temp;
                    sorted = false; // 有元素需要交换,说明数组还是无序的.
                    lastPosition = j; // 记录这一轮最后一次比较的较小那个值,下一轮就比较到这里
                } else {
                    System.out.println("第 " + i + " 轮的第 " + j + " 次不需要交换");
                }
            }
            len = lastPosition;
            if (sorted) {
                System.out.println("已经有序了, length = " + arr.length + ", i = " + i);
                break;
            }
        }
    }
}

冒泡排序复杂度

这是最原始的冒泡排序,该排序算法的每一轮要遍历所有元素,并且轮数和元素数量只少1,时间复杂度:

  1. 最优时间复杂度:O(n) 当数据本身已经有序如:{1, 2, 3, 4, 5, 6},我们做了优化,比较一轮就结束。
  2. 最坏时间复杂度:O(n^2) 当数据本身是倒序的如:{6, 5, 4, 3, 2, 1},总执行次数为n^2-n。
  3. 平均时间复杂度:O(n^2)。
  4. 稳定性:稳定

总结

冒泡排序是一种交换排序,它的基本思想是:两两比较相邻的数据,大的往后放。使用嵌套循环来实现冒泡排序,外循环控制比较的轮数,内循环控制每轮比较的次数。
冒泡排序可以进行两个优化:

  1. 如果有一轮发现没有需要排序的,说明已经有序了,可以不用再遍历比较了,减少比较轮数。
  2. 每轮都会比较到array.length– i– 1这个位置,如果后面已经是有序的就不需要比较,下一轮只需要比较到上一轮的最后一次交换的小值那个地方即可,减少每轮的比较次数。
---------- End ----------
原创文章和动画制作真心不易,您的点赞就是最大的支持!

想了解更多文章请关注微信公众号:表哥动画学编程

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值