Skip to content
当前页大纲

冒泡排序

冒泡排序是最基础的排序算法,由于其直观性,经常作为首个介绍的排序算法。其原理为:

  • 内循环: 使用相邻双指针 j , j + 1 从左至右遍历,依次比较相邻元素大小,若左元素大于右元素则将它们交换;遍历完成时,最大元素会被交换至数组最右边
  • 外循环: 不断重复「内循环」,每轮将当前最大元素交换至 剩余未排序数组最右边 ,直至所有元素都被交换至正确位置时结束。

如下图所示,首轮「内循环」后,数组最大元素已被交换至数组最右边;接下来,只需要完成数组剩余 $N - 1$ 个元素的排序即可(设数组元素数量为 $N$ )。同理,对剩余 $N - 1$ 个元素执行「内循环」,可将第二大元素交换至剩余数组最右端,以此类推……

<Picture41.png,Picture32.png,Picture33.png,Picture34.png,Picture35.png,Picture36.png,Picture37.png,Picture38.png,Picture39.png,Picture40.png>

如下图所示,冒泡排序的「外循环」共 $N - 1$ 轮,每轮「内循环」都将当前最大元素交换至数组最右边,从而完成对整个数组的排序。

Picture1.png

如下图所示,为示例数组 nums = [4, 1, 3, 1, 5, 2] 的冒泡排序算法运行过程。

<Picture2.png,Picture3.png,Picture4.png,Picture5.png,Picture6.png,Picture7.png,Picture8.png,Picture9.png,Picture10.png,Picture11.png,Picture12.png,Picture13.png,Picture14.png,Picture15.png,Picture16.png,Picture17.png,Picture18.png,Picture19.png,Picture20.png,Picture21.png,Picture22.png,Picture23.png,Picture24.png,Picture25.png,Picture26.png,Picture27.png,Picture28.png,Picture29.png,Picture30.png>

代码

Python
def bubble_sort(nums):
    N = len(nums)
    for i in range(N - 1):           # 外循环
        for j in range(N - i - 1):   # 内循环
            if nums[j] > nums[j + 1]:
                # 交换 nums[j], nums[j + 1]
                nums[j], nums[j + 1] = nums[j + 1], nums[j]
Java
void bubbleSort(int[] nums) {
    int N = nums.length;
    for (int i = 0; i < N - 1; i++) {          // 外循环
        for (int j = 0; j < N - i - 1; j++) {  // 内循环
            if (nums[j] > nums[j + 1]) {
                // 交换 nums[j], nums[j + 1]
                int tmp = nums[j];
                nums[j] = nums[j + 1];
                nums[j + 1] = tmp;
            }
        }
    }
}
C++
void bubbleSort(vector<int> &nums) {
    int N = nums.size();
    for (int i = 0; i < N - 1; i++) {          // 外循环
        for (int j = 0; j < N - i - 1; j++) {  // 内循环
            if (nums[j] > nums[j + 1]) {
                // 交换 nums[j], nums[j + 1]
                swap(nums[j], nums[j + 1]);
            }
        }
    }
}

算法特性

  • 时间复杂度 $O(N^2)$ :
    • 最佳 $\Omega(N)$ : 普通冒泡排序的时间复杂度恒为 $O(N^2)$ ,对于近似排序数组,通过加入标志位可实现提前返回(详情请见下文)。
    • 平均与最差 $O(N^2)$ :「外循环」共 $N - 1$ 轮,使用 $O(N)$ 时间;每轮「内循环」分别遍历 $N - 1$ , $N - 2$ , $\cdots$ , $2$ , $1$ 次,平均 $\frac{N}{2}$ 次,使用 $O(\frac{N}{2}) = O(N)$ 时间;因此,总体时间复杂度为 $O(N^2)$ 。
  • 空间复杂度 $O(1)$ : 只需原地交换元素,使用常数大小的额外空间。
  • 冒泡排序是通过不断 交换元素 实现排序(交换 2 个元素需要 3 次赋值操作),因此速度较慢;
  • 原地: 指针变量仅使用常数大小额外空间,空间复杂度为 $O(1)$ ;
  • 稳定: 元素值相同时不交换,因此不会改变相同元素的相对位置;
  • 自适应: 通过增加一个标志位 flag ,若某轮内循环未执行任何交换操作时,说明已经完成排序,因此直接返回。此优化使冒泡排序的最优时间复杂度达到 $O(N)$(当输入数组已排序时);

标志位优化

普通冒泡排序的时间复杂度恒为 $O(N^2)$​ ,与输入数组的元素分布无关。

通过增加一个标志位 flag ,若在某轮「内循环」中未执行任何交换操作,则说明数组已经完成排序,直接返回结果即可。

优化后的冒泡排序的最差和平均时间复杂度仍为 $O(N^2)$ ;在输入数组 已排序 时,达到 最佳时间复杂度 $\Omega(N)$ 。

Python
def bubble_sort(nums):
    N = len(nums)
    for i in range(N - 1):
        flag = False         #  初始化标志位
        for j in range(N - i - 1):
            if nums[j] > nums[j + 1]:
                nums[j], nums[j + 1] = nums[j + 1], nums[j]
                flag = True  # 记录交换元素
        if not flag: break   # 内循环未交换任何元素,则跳出
Java
void bubbleSort(int[] nums) {
    int N = nums.length;
    for (int i = 0; i < N - 1; i++) {
        boolean flag = false; // 初始化标志位
        for (int j = 0; j < N - i - 1; j++) {
            if (nums[j] > nums[j + 1]) {
                int tmp = nums[j];
                nums[j] = nums[j + 1];
                nums[j + 1] = tmp;
                flag = true;  // 记录交换元素
            }
        }
        if (!flag) break;     // 内循环未交换任何元素,则跳出
    }
}
C++
void bubbleSort(vector<int> &nums) {
    int N = nums.size();
    for (int i = 0; i < N - 1; i++) {
        bool flag = false;   // 初始化标志位
        for (int j = 0; j < N - i - 1; j++) {
            if (nums[j] > nums[j + 1]) {
                swap(nums[j], nums[j + 1]);
                flag = true; // 记录交换元素
            }
        }
        if (!flag) break;    // 内循环未交换任何元素,则跳出
    }
}

MIT License.