面试:排序算法之冒泡排序

在前面的文章中,我们探讨了高效的快速排序和直观的选择排序。今天,我们要来聊一聊几乎所有程序员接触的第一个排序算法——冒泡排序(Bubble Sort)。

今天,我们要来聊一聊几乎所有程序员接触的第一个排序算法——冒泡排序(Bubble Sort)。

虽然在实际的工程开发中,冒泡排序因为效率问题很少被直接作为首选,但它绝对是理解“交换排序”思想的最佳入门教材。今天,我们就来拆解这个经典算法,并看看如何用 Java 写出一个“聪明”的冒泡排序。

核心思想:水底的气泡

冒泡排序的名字非常形象。你可以把待排序的数组想象成一个竖直的水池,数组左边是池底,右边是水面。数组里的每一个元素都是一个“气泡”,元素的值越大,气泡就越“重”。

它的核心运行机制就是相邻元素两两比较:

1、从头开始: 从数组的最左侧(第一个元素)开始,比较相邻的两个元素。

2、两两交换: 如果左边的元素比右边的大(即前一个比后一个大),就把它们俩交换位置。

3、一轮浮出: 这样一路比较和交换下去,一轮操作结束后,当前未排序部分中最大的那个元素,就会像气泡一样“浮”到数组的最右端(正确的位置)。

4、重复循环: 排除掉已经排好序的末尾元素,对剩下的元素重复上述过程,直到没有任何元素需要交换为止。

代码实现(含进阶优化)

常规的冒泡排序无论数组是否有序,都会傻傻地执行完所有的循环。为了体现我们的专业性,这里的加入了一个 swapped 标志位进行优化。如果某一轮遍历中没有发生任何交换,说明数组已经完全有序,我们可以直接提前下课!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
public class BubbleSortDemo {

    public static void main(String[] args) {
        int[] arr = {64, 34, 25, 12, 22, 11, 90};
        System.out.println("排序前:");
        printArray(arr);

        bubbleSort(arr);

        System.out.println("排序后:");
        printArray(arr);
    }

    /**
     * 冒泡排序(优化版)
     * @param arr 待排序数组
     */
    public static void bubbleSort(int[] arr) {
        int n = arr.length;
        // 外层循环:控制需要进行多少轮冒泡
        for (int i = 0; i < n - 1; i++) {
            // 优化标志位:记录本轮是否发生了数据交换
            boolean swapped = false;

            // 内层循环:负责相邻元素的比较和交换
            // 注意边界 n - 1 - i:因为每一轮结束后,最后的 i 个元素已经是有序的了,无需再比
            for (int j = 0; j < n - 1 - i; j++) {
                // 如果前面的元素大于后面的元素,则交换
                if (arr[j] > arr[j + 1]) {
                    swap(arr, j, j + 1);
                    swapped = true; // 发生了交换,修改标志位
                }
            }

            // 如果这一轮从头到尾都没有发生过交换,说明数组已经完全有序了
            // 直接跳出外层循环,提前结束算法!
            if (!swapped) {
                break;
            }
        }
    }

    /**
     * 交换数组中两个元素的方法
     */
    private static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

    /**
     * 打印数组辅助方法
     */
    private static void printArray(int[] arr) {
        for (int num : arr) {
            System.out.print(num + " ");
        }
        System.out.println();
    }
}

核心代码解析:

1、外层循环边界 (i < n - 1): 既然每次能排好一个最大值,那么 $N$ 个元素的数组,排好 $N-1$ 个最大值后,剩下的最后那个(也就是最小的)自然就在第一位了,所以只需循环 $N-1$ 次。

2、内层循环边界 (j < n - 1 - i): 这是冒泡排序的一个关键细节。随着外层循环 i 的增加,数组末尾已经排好序的元素越来越多。我们不需要再去比较那些已经归位的“大气泡”,所以每次内层循环的终点都要减去 i。

3、提前退出机制 (swapped): 这个布尔变量是优化的灵魂。如果没有它,即使传入一个完全排好序的数组 [1, 2, 3, 4, 5],算法也会强行做完所有的比较,纯属浪费时间。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计