我猜你还不会冒泡排序 | 刷题打卡

620 阅读4分钟

Date: 2021/03/07

前言

冒泡排序 Bubble Sort 是大家非常熟悉的算法,本文将简单回顾冒泡排序的基本思想,重点是讨论冒泡排序的优化。

最简单的冒泡排序

冒泡排序的基本思想是依次比较相邻的两个元素,如果第一个比第二个大,则交换这两个元素;否则继续比较。经过一轮比较后,就可以将最大的元素放在最后面,再对剩余的元素依次比较,最多经过 n-1 轮比较,得到最终的有序序列。

本文以升序排序为例。

例如对于一组元素 values=[11,10,4,28,24,12,13,16]从前往后依次比较相邻元素 的过程如下图:

冒泡排序一轮比较的过程

经过一轮比较后,确定了最大元素 28 的最终位置,接下来只要对前7个数重复相同的操作即可。

这是最简单的冒泡排序算法,也是理解冒泡排序思想的核心,实现代码如下:

/**
    * 最简单的冒泡排序 O(n*n)
    *
    * @param values
    */
public static void sort(int[] values) {
    int i = 0, j = 0;
    for (i = values.length - 2; i >= 0; --i) {
        for (j = 0; j <= i; j++) {
            if(values[j] > values[j+1]){
                int temp = values[j];
                values[j] = values[j+1];
                values[j+1] = temp;
            }
        }
    }
}

有了上面对冒泡的理解,我们开始考虑冒泡排序的优化。

冒泡排序的优化

优化1:及时止损

冒泡排序有一个很大的缺陷:在排序的过程中,对于一个已经是升序的序列,也要依次进行比较,这显然是多余的操作,这种情况下应该及时终止算法,减少冒泡的次数。

如何判断是否应该终止呢?我们只需要判断冒泡的过程中是否进行了交换操作(设置一个 boolean 变量记录是否交换),如果没有任何交换,说明已经是升序的序列,此时可以终止算法。

还是上面的例子,经过两轮交换后,就可以终止算法了。

冒泡排序的优化1

优化2:减少比较次数

优化1中是判断 所有还未排好序的元素 是否是有序的,在此基础上,我们可以进一步优化。

对于所有未排好序的元素来说,可能不是有序的,但是尾部的一部分元素如果构成了有序序列,也可以跳过对尾部这些元素的比较。也就是说,我们只需要对冒泡过程中 最后一次交换的位置 之前的元素进行比较。

这种思路其实也包含了优化1的情况。优化1中,最后一次交换的位置为 0

例如对于 values=[10,11,4,13,24,12,14,16],第一轮比较结束后,24 排在最后。第二轮比较过程中,记录的最后一次交换的位置是 3,也就是图中黄色元素 12 的位置,第三轮比较只需要比较 12 之前的元素即可。

如果是按照优化1的方法,只判断是否发生了交换,第三轮比较依然会比较到 16 之前元素。

冒泡排序优化2

AC代码

public static void bubbleSort(int[] values) {
    int i = 0, j = 0;
    for (i = values.length - 2; i >= 0; --i) {
        /** 1. tailSortedFlag 是用来记录每一趟最后一次交换时j的位置 ,如果尾部是局部有序的,那么只需要排序最后一次交换			* 位置之前的元素即可。
        * tailSortedFlag的初始值用来处理本就排过序的情况,对于冒泡排序不同的写法,初始值取不同值,这里取0 
        */
        int tailSortedFlag = 0;
        for (j = 0; j <= i; ++j) {
            /** 2. 比较时不取等号是稳定的排序,取等号是不稳定的排序
            * >是升序 <是降序 */
            if (values[j] > values[j + 1]) {
                SortUtils.swap(values, j, j + 1);
                /** 3. 每交换一次就记录以下当前位置 */
                tailSortedFlag = j;
            }
        }
        i = tailSortedFlag;
    }
}

总结

冒泡排序最好的时间复杂度为 O(n)(在序列本身是有序的情况下),最坏时间复杂度为 O(n*n)(序列和最终的目标是逆序的情况下),平均时间复杂度为 O(n*n)

空间复杂度为 O(1)。是稳定的排序算法。

冒泡排序的效率并不高,但是对任何一种算法来说,重要的是其中的思想,优化的过程也能提高我们对算法设计和分析的能力。


本文正在参与「掘金 2021 春招闯关活动」, 点击查看 活动详情