冒泡排序(Bubble Sort)作为最经典的排序算法之一,虽然在大规模数据排序中效率不高,但它简单直观的实现方式使其成为学习算法入门的绝佳案例。

冒泡排序是一种简单的交换排序算法,其核心思想是通过重复比较相邻元素并交换位置错误的元素,使较大的元素逐渐 浮 到数组的末尾。就像水中的气泡会逐渐上升到水面一样,这也是 冒泡 名称的由来。

1、工作原理与步骤

冒泡排序的工作过程可以概括为以下步骤:

  1. 从数组的第一个元素开始,比较相邻的两个元素

  2. 如果第一个元素大于第二个元素,则交换它们的位置

  3. 对每一对相邻元素重复同样的比较和交换操作,从开始第一对到结尾的最后一对

  4. 完成一轮遍历后,最大的元素会 "浮" 到数组的末尾

  5. 针对所有的元素重复以上的步骤,除了最后已经排好序的元素

  6. 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较

1.1 冒泡排序伪代码

function bubbleSort(arr):
    n = length(arr)

    // 外层循环控制需要进行多少轮比较
    for i from 0 to n-1:
        // 内层循环控制每轮比较的元素范围
        // 每轮结束后,最大的元素已"浮"到末尾,不需要再比较
        for j from 0 to n-i-2:
            // 比较相邻元素
            if arr[j] > arr[j+1]:
                // 交换元素
                swap arr[j] and arr[j+1]

    return arrjavajavago

1.2 可视化理解

为了更直观地理解冒泡排序的过程,我们可以通过动画演示来观察元素的比较和交换过程:

冒泡排序动画

在动画中,我们可以清晰地看到:

  • 每一轮排序中,较大的元素如何逐步向数组末端移动

  • 相邻元素如何进行比较(通常用特殊颜色标记)

  • 位置错误的元素如何交换位置

  • 排序完成的元素如何被标记(通常用绿色表示)

2、算法实现

2.1 Java 实现

下面是 Java 语言实现的冒泡排序,包含基本版本和优化版本:

public class BubbleSort {
    // 基本冒泡排序实现
    public static void bubbleSort(int[] arr) {
        if (arr == null || arr.length <= 1) {
            return; // 数组为空或只有一个元素,无需排序
        }
        
        int n = arr.length;
        
        // 外层循环控制排序轮数
        for (int i = 0; i < n - 1; i++) {
            // 内层循环控制每轮比较的元素范围
            for (int j = 0; j < n - i - 1; j++) {
                // 比较相邻元素,如果顺序错误则交换
                if (arr[j] > arr[j + 1]) {
                    // 交换元素
                    int temp = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = temp;
                }
            }
        }
    }
    
    // 优化版冒泡排序(如果某轮没有交换,说明数组已排序完成)
    public static void optimizedBubbleSort(int[] arr) {
        if (arr == null || arr.length <= 1) {
            return;
        }
        
        int n = arr.length;
        boolean swapped; // 标记是否发生交换
        
        for (int i = 0; i < n - 1; i++) {
            swapped = false;
            
            for (int j = 0; j < n - i - 1; j++) {
                if (arr[j] > arr[j + 1]) {
                    // 交换元素
                    int temp = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = temp;
                    
                    swapped = true; // 发生了交换
                }
            }
            
            // 如果没有发生交换,说明数组已经有序,提前退出
            if (!swapped) {
                break;
            }
        }
    }
}

2.2 Go 实现

下面是 Go 语言实现的冒泡排序:

// 基本冒泡排序实现
func bubbleSort(arr []int) {
    if arr == nil || len(arr) <= 1 {
        return // 数组为空或只有一个元素,无需排序
    }
    
    n := len(arr)
    
    // 外层循环控制排序轮数
    for i := 0; i < n-1; i++ {
        // 内层循环控制每轮比较的元素范围
        for j := 0; j < n-i-1; j++ {
            // 比较相邻元素,如果顺序错误则交换
            if arr[j] > arr[j+1] {
                // 交换元素
                arr[j], arr[j+1] = arr[j+1], arr[j]
            }
        }
    }
}

// 优化版冒泡排序(如果某轮没有交换,说明数组已排序完成)
func optimizedBubbleSort(arr []int) {
    if arr == nil || len(arr) <= 1 {
        return
    }
    
    n := len(arr)
    swapped := false // 标记是否发生交换
    
    for i := 0; i < n-1; i++ {
        swapped = false
        
        for j := 0; j < n-i-1; j++ {
            if arr[j] > arr[j+1] {
                // 交换元素
                arr[j], arr[j+1] = arr[j+1], arr[j]
                swapped = true // 发生了交换
            }
        }
        
        // 如果没有发生交换,说明数组已经有序,提前退出
        if !swapped {
            break
        }
    }
}

3、算法优化

标准冒泡排序在某些情况下效率较低,特别是当数组已经部分或完全有序时。我们可以通过以下方式进行优化:

  1. 添加交换标记:如上述代码所示,通过一个布尔变量标记本轮是否发生了交换,如果没有交换则说明数组已经有序,可以提前退出。

  2. 记录最后交换位置:进一步优化可以记录最后一次交换的位置,下一轮排序只需比较到该位置即可,因为之后的元素已经有序。

  3. 双向冒泡排序:普通冒泡排序每次只将最大的元素移到末尾,双向冒泡则同时向两个方向进行,一次遍历中既将最大元素移到末尾,又将最小元素移到开头。

4、算法复杂度分析

时间复杂度

- 最坏情况:O (n²)(完全逆序的数组)

- 最好情况:O (n)(已经有序的数组,使用优化版本)

- 平均情况:O (n²)

空间复杂度

O (1),只需要一个临时变量用于交换元素,属于原地排序算法。

稳定性

冒泡排序是稳定的排序算法,因为当两个元素相等时,不会进行交换,保持它们原有的相对顺序。

5、适用场景

尽管冒泡排序的时间复杂度较高,但在以下场景中仍有其用武之地:

  1. 教学场景:作为入门级排序算法,适合理解排序算法的基本思想

  2. 小规模数据排序:数据量较小时,简单的实现带来的 overhead 可以忽略

  3. 几乎有序的数据:当数据已经接近有序时,优化后的冒泡排序效率较高

  4. 对稳定性要求高的场景:冒泡排序可以保证相等元素的相对顺序不变