一、概念

冒泡排序(Bubble Sorting):通过对待排序序列从前往后,依次比较相邻元素的值,若发现逆序则交换,使值较大的元素主键从前移向后部,就像水底下冒泡一样逐渐往上冒。

通俗来讲就是:相邻元素对比,左边比右边大的话就换个位置,每轮对比下来就能确定一个最大值,第一轮最大值在最后位置,第二轮倒数第二大的值在倒数第二个位置上,以此类推,也就是需要对比的次数为数组长度-1,因为每次对比都能确定一个最大值,且放到最后。

二、图解

比如要冒泡如下数组:[3, 9, -1, 10, -2]

第一趟排序:

[3,-1,9,-2,10]

第二趟排序:

[-1,3,-2,9,10]

第三趟排序:

[-1,-2,3,9,10]

第四趟排序:

[-2,-1,3,9,10]

三、coding

1、lowb演示版

package com.chentongwei.struct.sort;

import java.util.Arrays;
/**
 * Description:
 *
 * @author TongWei.Chen 2019-12-30 17:18:26
 */
public class BubbleSort {
    public static void main(String[] args) {
        int[] arr = {3, 9, -1, 10, -2};
        int temp = 0;
        // 第一趟排序就是将第一大的数排在倒数第一位
        for (int j = 0; j < arr.length - 1 - 0; j ++) {
            // 如果前面的数比后面的数大就交换
            if (arr[j] > arr[j + 1]) {
                temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
        System.out.println("第一次排序后的数组");
        System.out.println(Arrays.toString(arr));

        // 第二趟排序就是将第二大的数排在倒数第二位
        for (int j = 0; j < arr.length - 1 - 1; j ++) {
            // 如果前面的数比后面的数大就交换
            if (arr[j] > arr[j + 1]) {
                temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
        System.out.println("第二次排序后的数组");
        System.out.println(Arrays.toString(arr));

        // 第三趟排序就是将第二大的数排在倒数第二位
        for (int j = 0; j < arr.length - 1 - 2; j ++) {
            // 如果前面的数比后面的数大就交换
            if (arr[j] > arr[j + 1]) {
                temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
        System.out.println("第三次排序后的数组");
        System.out.println(Arrays.toString(arr));

        // 第四趟排序就是将第二大的数排在倒数第二位
        for (int j = 0; j < arr.length - 1 - 3; j ++) {
            // 如果前面的数比后面的数大就交换
            if (arr[j] > arr[j + 1]) {
                temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
        System.out.println("第四次排序后的数组");
        System.out.println(Arrays.toString(arr));
    }
}

每次排序都进行多-1,因为上面说了,每次排序都能确定一个最大的数且放到最后,所以每次都会多-1进行循环,一次比一次循环的少。

第一次排序后的数组
[3, -1, 9, -2, 10]
第二次排序后的数组
[-1, 3, -2, 9, 10]
第三次排序后的数组
[-1, -2, 3, 9, 10]
第四次排序后的数组
[-2, -1, 3, 9, 10]

2、优化版

上面lowb演示版就是为了演示了过程,很明显都是重复代码,只有循环的条件不一样,每次循环都少循环一次,多-1而已。所以循环每次都可以理解成-i(i是递减的),这就很明显了,再套个循环就好了。然后内层循环每次都arr.length - 1 - i

package com.chentongwei.struct.sort;

import java.util.Arrays;

/**
 * Description:
 *
 * @author TongWei.Chen 2019-12-30 17:18:26
 */
public class BubbleSort {
    public static void main(String[] args) {
        int[] arr = {3, 9, -1, 10, -2};
        bubbleSort(arr);
    }

    // 将前面的冒泡排序算法封装成一个方法
    public static void bubbleSort(int[] arr) {
        int temp;
        // 时间复杂度n2
        for (int i = 0; i < arr.length - 1; i ++) {
            for (int j = 0; j < arr.length - 1 - i; j ++) {
                // 如果前面的数比后面的数大就交换
                if (arr[j] > arr[j + 1]) {
                    temp = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = temp;
                }
            }
            System.out.println("第" + (i + 1) + "次排序后的数组");
            System.out.println(Arrays.toString(arr));
        }
    }
}

时间复杂度分析:
最好、最坏、平均时间复杂度均为O(n2)

第一次排序后的数组
[3, -1, 9, -2, 10]
第二次排序后的数组
[-1, 3, -2, 9, 10]
第三次排序后的数组
[-1, -2, 3, 9, 10]
第四次排序后的数组
[-2, -1, 3, 9, 10]

四、问题

如果我数组是完全有序的呢?或者说部分有序,比如排序两次就能得到结果了,但是我这套程序不管你有序无序,都直接排序arr.length-1次。这明显不够最优,比如我们有如下数组:[1, 2, 3, 4, 5],这不太需要每次都排序,排序一次直接拿到结果就行了。

思路:

可以定义一个变量flag,代表这次循环是否需要排序交换位置了,若没有,则代表已经排完了,直接break结束循环即可。具体实现如下:

package com.chentongwei.struct.sort;

import java.util.Arrays;
import java.util.Date;

/**
 * Description:
 *
 * @author TongWei.Chen 2019-12-30 17:18:26
 */
public class BubbleSort {
    public static void main(String[] args) {
        int[] arr = {1, 2, 3, 4, 5};
        bubbleSort(arr);
    }

    // 将前面的冒泡排序算法封装成一个方法
    public static void bubbleSort(int[] arr) {
        int temp;
        // 标识变量,表示是否进行过交换,默认没进行过交换
        boolean flag = false;
        // 时间复杂度n2
        for (int i = 0; i < arr.length - 1; i ++) {
            for (int j = 0; j < arr.length - 1 - i; j ++) {
                // 如果前面的数比后面的数大就交换
                if (arr[j] > arr[j + 1]) {
                    flag = true;
                    temp = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = temp;
                }
            }
            System.out.println("第" + (i + 1) + "次排序后的数组");
            System.out.println(Arrays.toString(arr));
            // 代表再排序中,一次交换都没发生过,也就是已经排好序了。
            if (! flag) {
                break;
            }
            // 重置flag,进行下次判断
            flag = false;
        }
    }
}

第1次排序后的数组
[1, 2, 3, 4, 5]

可以发现结果只输出了一次而已。最好时间复杂度为O(n),最坏时间复杂度为O(n2),但是最好的情况极难出现,所以平均时间复杂度还是O(n2)

五、测试效率

我们测试下10w条数据进行冒泡排序需要耗时多久。

package com.chentongwei.struct.sort;

import java.util.Arrays;

/**
 * Description:
 *
 * @author TongWei.Chen 2019-12-30 17:18:26
 */
public class BubbleSort {
    public static void main(String[] args) {
        // 测试冒泡排序的效率,假设给10w个数据测试
        int[] arr = new int[100000];
        for (int i = 0; i < arr.length; i ++) {
            // 生成【0,8000000】之间的一个随机数
            arr[i] = (int)(Math.random() * 8000000);
        }
        long startTime = System.currentTimeMillis();
        bubbleSort(arr);
        long endTime = System.currentTimeMillis();
        System.out.println("冒泡10w个数据所需要的时间是:" + (endTime - startTime) / 1000);
    }

    // 将前面的冒泡排序算法封装成一个方法
    public static void bubbleSort(int[] arr) {
        int temp;
        // 标识变量,表示是否进行过交换,默认没进行过交换
        boolean flag = false;
        // 时间复杂度n2
        for (int i = 0; i < arr.length - 1; i ++) {
            for (int j = 0; j < arr.length - 1 - i; j ++) {
                // 如果前面的数比后面的数大就交换
                if (arr[j] > arr[j + 1]) {
                    flag = true;
                    temp = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = temp;
                }
            }
            // 代表再排序中,一次交换都没发生过,也就是已经排好序了。
            if (! flag) {
                break;
            }
            // 重置flag,进行下次判断
            flag = false;
        }
    }
}

冒泡10w个数据所需要的时间是:23

可以看到是23s排序完10w条数据。这只是八大排序其中之一,后面每种排序算法我们都对比一下,看看差距到底有多大。