11.3 泡沫排序¶
泡沫排序(bubble sort)透過連續地比較與交換相鄰元素實現排序。這個過程就像氣泡從底部升到頂部一樣,因此得名泡沫排序。
如圖 11-4 所示,冒泡過程可以利用元素交換操作來模擬:從陣列最左端開始向右走訪,依次比較相鄰元素大小,如果“左元素 > 右元素”就交換二者。走訪完成後,最大的元素會被移動到陣列的最右端。
圖 11-4 利用元素交換操作模擬冒泡
11.3.1 演算法流程¶
設陣列的長度為 \(n\) ,泡沫排序的步驟如圖 11-5 所示。
- 首先,對 \(n\) 個元素執行“冒泡”,將陣列的最大元素交換至正確位置。
- 接下來,對剩餘 \(n - 1\) 個元素執行“冒泡”,將第二大元素交換至正確位置。
- 以此類推,經過 \(n - 1\) 輪“冒泡”後,前 \(n - 1\) 大的元素都被交換至正確位置。
- 僅剩的一個元素必定是最小元素,無須排序,因此陣列排序完成。
圖 11-5 泡沫排序流程
示例程式碼如下:
bubble_sort.cpp
/* 泡沫排序 */
void bubbleSort(vector<int> &nums) {
// 外迴圈:未排序區間為 [0, i]
for (int i = nums.size() - 1; i > 0; i--) {
// 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端
for (int j = 0; j < i; j++) {
if (nums[j] > nums[j + 1]) {
// 交換 nums[j] 與 nums[j + 1]
// 這裡使用了 std::swap() 函式
swap(nums[j], nums[j + 1]);
}
}
}
}
bubble_sort.java
/* 泡沫排序 */
void bubbleSort(int[] nums) {
// 外迴圈:未排序區間為 [0, i]
for (int i = nums.length - 1; i > 0; i--) {
// 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端
for (int j = 0; j < i; 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;
}
}
}
}
bubble_sort.cs
/* 泡沫排序 */
void BubbleSort(int[] nums) {
// 外迴圈:未排序區間為 [0, i]
for (int i = nums.Length - 1; i > 0; i--) {
// 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端
for (int j = 0; j < i; j++) {
if (nums[j] > nums[j + 1]) {
// 交換 nums[j] 與 nums[j + 1]
(nums[j + 1], nums[j]) = (nums[j], nums[j + 1]);
}
}
}
}
bubble_sort.js
/* 泡沫排序 */
function bubbleSort(nums) {
// 外迴圈:未排序區間為 [0, i]
for (let i = nums.length - 1; i > 0; i--) {
// 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端
for (let j = 0; j < i; j++) {
if (nums[j] > nums[j + 1]) {
// 交換 nums[j] 與 nums[j + 1]
let tmp = nums[j];
nums[j] = nums[j + 1];
nums[j + 1] = tmp;
}
}
}
}
bubble_sort.ts
/* 泡沫排序 */
function bubbleSort(nums: number[]): void {
// 外迴圈:未排序區間為 [0, i]
for (let i = nums.length - 1; i > 0; i--) {
// 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端
for (let j = 0; j < i; j++) {
if (nums[j] > nums[j + 1]) {
// 交換 nums[j] 與 nums[j + 1]
let tmp = nums[j];
nums[j] = nums[j + 1];
nums[j + 1] = tmp;
}
}
}
}
bubble_sort.dart
/* 泡沫排序 */
void bubbleSort(List<int> nums) {
// 外迴圈:未排序區間為 [0, i]
for (int i = nums.length - 1; i > 0; i--) {
// 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端
for (int j = 0; j < i; 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;
}
}
}
}
bubble_sort.zig
// 泡沫排序
fn bubbleSort(nums: []i32) void {
// 外迴圈:未排序區間為 [0, i]
var i: usize = nums.len - 1;
while (i > 0) : (i -= 1) {
var j: usize = 0;
// 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端
while (j < i) : (j += 1) {
if (nums[j] > nums[j + 1]) {
// 交換 nums[j] 與 nums[j + 1]
var tmp = nums[j];
nums[j] = nums[j + 1];
nums[j + 1] = tmp;
}
}
}
}
視覺化執行
11.3.2 效率最佳化¶
我們發現,如果某輪“冒泡”中沒有執行任何交換操作,說明陣列已經完成排序,可直接返回結果。因此,可以增加一個標誌位 flag
來監測這種情況,一旦出現就立即返回。
經過最佳化,泡沫排序的最差時間複雜度和平均時間複雜度仍為 \(O(n^2)\) ;但當輸入陣列完全有序時,可達到最佳時間複雜度 \(O(n)\) 。
bubble_sort.py
def bubble_sort_with_flag(nums: list[int]):
"""泡沫排序(標誌最佳化)"""
n = len(nums)
# 外迴圈:未排序區間為 [0, i]
for i in range(n - 1, 0, -1):
flag = False # 初始化標誌位
# 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端
for j in range(i):
if nums[j] > nums[j + 1]:
# 交換 nums[j] 與 nums[j + 1]
nums[j], nums[j + 1] = nums[j + 1], nums[j]
flag = True # 記錄交換元素
if not flag:
break # 此輪“冒泡”未交換任何元素,直接跳出
bubble_sort.cpp
/* 泡沫排序(標誌最佳化)*/
void bubbleSortWithFlag(vector<int> &nums) {
// 外迴圈:未排序區間為 [0, i]
for (int i = nums.size() - 1; i > 0; i--) {
bool flag = false; // 初始化標誌位
// 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端
for (int j = 0; j < i; j++) {
if (nums[j] > nums[j + 1]) {
// 交換 nums[j] 與 nums[j + 1]
// 這裡使用了 std::swap() 函式
swap(nums[j], nums[j + 1]);
flag = true; // 記錄交換元素
}
}
if (!flag)
break; // 此輪“冒泡”未交換任何元素,直接跳出
}
}
bubble_sort.java
/* 泡沫排序(標誌最佳化) */
void bubbleSortWithFlag(int[] nums) {
// 外迴圈:未排序區間為 [0, i]
for (int i = nums.length - 1; i > 0; i--) {
boolean flag = false; // 初始化標誌位
// 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端
for (int j = 0; j < i; 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;
flag = true; // 記錄交換元素
}
}
if (!flag)
break; // 此輪“冒泡”未交換任何元素,直接跳出
}
}
bubble_sort.cs
/* 泡沫排序(標誌最佳化)*/
void BubbleSortWithFlag(int[] nums) {
// 外迴圈:未排序區間為 [0, i]
for (int i = nums.Length - 1; i > 0; i--) {
bool flag = false; // 初始化標誌位
// 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端
for (int j = 0; j < i; j++) {
if (nums[j] > nums[j + 1]) {
// 交換 nums[j] 與 nums[j + 1]
(nums[j + 1], nums[j]) = (nums[j], nums[j + 1]);
flag = true; // 記錄交換元素
}
}
if (!flag) break; // 此輪“冒泡”未交換任何元素,直接跳出
}
}
bubble_sort.go
/* 泡沫排序(標誌最佳化)*/
func bubbleSortWithFlag(nums []int) {
// 外迴圈:未排序區間為 [0, i]
for i := len(nums) - 1; i > 0; i-- {
flag := false // 初始化標誌位
// 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端
for j := 0; j < i; j++ {
if nums[j] > nums[j+1] {
// 交換 nums[j] 與 nums[j + 1]
nums[j], nums[j+1] = nums[j+1], nums[j]
flag = true // 記錄交換元素
}
}
if flag == false { // 此輪“冒泡”未交換任何元素,直接跳出
break
}
}
}
bubble_sort.swift
/* 泡沫排序(標誌最佳化)*/
func bubbleSortWithFlag(nums: inout [Int]) {
// 外迴圈:未排序區間為 [0, i]
for i in nums.indices.dropFirst().reversed() {
var flag = false // 初始化標誌位
for j in 0 ..< i {
if nums[j] > nums[j + 1] {
// 交換 nums[j] 與 nums[j + 1]
nums.swapAt(j, j + 1)
flag = true // 記錄交換元素
}
}
if !flag { // 此輪“冒泡”未交換任何元素,直接跳出
break
}
}
}
bubble_sort.js
/* 泡沫排序(標誌最佳化)*/
function bubbleSortWithFlag(nums) {
// 外迴圈:未排序區間為 [0, i]
for (let i = nums.length - 1; i > 0; i--) {
let flag = false; // 初始化標誌位
// 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端
for (let j = 0; j < i; j++) {
if (nums[j] > nums[j + 1]) {
// 交換 nums[j] 與 nums[j + 1]
let tmp = nums[j];
nums[j] = nums[j + 1];
nums[j + 1] = tmp;
flag = true; // 記錄交換元素
}
}
if (!flag) break; // 此輪“冒泡”未交換任何元素,直接跳出
}
}
bubble_sort.ts
/* 泡沫排序(標誌最佳化)*/
function bubbleSortWithFlag(nums: number[]): void {
// 外迴圈:未排序區間為 [0, i]
for (let i = nums.length - 1; i > 0; i--) {
let flag = false; // 初始化標誌位
// 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端
for (let j = 0; j < i; j++) {
if (nums[j] > nums[j + 1]) {
// 交換 nums[j] 與 nums[j + 1]
let tmp = nums[j];
nums[j] = nums[j + 1];
nums[j + 1] = tmp;
flag = true; // 記錄交換元素
}
}
if (!flag) break; // 此輪“冒泡”未交換任何元素,直接跳出
}
}
bubble_sort.dart
/* 泡沫排序(標誌最佳化)*/
void bubbleSortWithFlag(List<int> nums) {
// 外迴圈:未排序區間為 [0, i]
for (int i = nums.length - 1; i > 0; i--) {
bool flag = false; // 初始化標誌位
// 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端
for (int j = 0; j < i; 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;
flag = true; // 記錄交換元素
}
}
if (!flag) break; // 此輪“冒泡”未交換任何元素,直接跳出
}
}
bubble_sort.rs
/* 泡沫排序(標誌最佳化) */
fn bubble_sort_with_flag(nums: &mut [i32]) {
// 外迴圈:未排序區間為 [0, i]
for i in (1..nums.len()).rev() {
let mut flag = false; // 初始化標誌位
// 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端
for j in 0..i {
if nums[j] > nums[j + 1] {
// 交換 nums[j] 與 nums[j + 1]
nums.swap(j, j + 1);
flag = true; // 記錄交換元素
}
}
if !flag {
break; // 此輪“冒泡”未交換任何元素,直接跳出
};
}
}
bubble_sort.c
/* 泡沫排序(標誌最佳化)*/
void bubbleSortWithFlag(int nums[], int size) {
// 外迴圈:未排序區間為 [0, i]
for (int i = size - 1; i > 0; i--) {
bool flag = false;
// 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端
for (int j = 0; j < i; j++) {
if (nums[j] > nums[j + 1]) {
int temp = nums[j];
nums[j] = nums[j + 1];
nums[j + 1] = temp;
flag = true;
}
}
if (!flag)
break;
}
}
bubble_sort.kt
/* 泡沫排序(標誌最佳化) */
fun bubbleSortWithFlag(nums: IntArray) {
// 外迴圈:未排序區間為 [0, i]
for (i in nums.size - 1 downTo 1) {
var flag = false // 初始化標誌位
// 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端
for (j in 0..<i) {
if (nums[j] > nums[j + 1]) {
// 交換 nums[j] 與 nums[j + 1]
val temp = nums[j]
nums[j] = nums[j + 1]
nums[j + 1] = temp
flag = true // 記錄交換元素
}
}
if (!flag) break // 此輪“冒泡”未交換任何元素,直接跳出
}
}
bubble_sort.rb
### 泡沫排序(標誌最佳化)###
def bubble_sort_with_flag(nums)
n = nums.length
# 外迴圈:未排序區間為 [0, i]
for i in (n - 1).downto(1)
flag = false # 初始化標誌位
# 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端
for j in 0...i
if nums[j] > nums[j + 1]
# 交換 nums[j] 與 nums[j + 1]
nums[j], nums[j + 1] = nums[j + 1], nums[j]
flag = true # 記錄交換元素
end
end
break unless flag # 此輪“冒泡”未交換任何元素,直接跳出
end
end
bubble_sort.zig
// 泡沫排序(標誌最佳化)
fn bubbleSortWithFlag(nums: []i32) void {
// 外迴圈:未排序區間為 [0, i]
var i: usize = nums.len - 1;
while (i > 0) : (i -= 1) {
var flag = false; // 初始化標誌位
var j: usize = 0;
// 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端
while (j < i) : (j += 1) {
if (nums[j] > nums[j + 1]) {
// 交換 nums[j] 與 nums[j + 1]
var tmp = nums[j];
nums[j] = nums[j + 1];
nums[j + 1] = tmp;
flag = true;
}
}
if (!flag) break; // 此輪“冒泡”未交換任何元素,直接跳出
}
}
視覺化執行
11.3.3 演算法特性¶
- 時間複雜度為 \(O(n^2)\)、自適應排序:各輪“冒泡”走訪的陣列長度依次為 \(n - 1\)、\(n - 2\)、\(\dots\)、\(2\)、\(1\) ,總和為 \((n - 1) n / 2\) 。在引入
flag
最佳化後,最佳時間複雜度可達到 \(O(n)\) 。 - 空間複雜度為 \(O(1)\)、原地排序:指標 \(i\) 和 \(j\) 使用常數大小的額外空間。
- 穩定排序:由於在“冒泡”中遇到相等元素不交換。