Python 排序算法的穩定性及其彙總

排序算法的穩定性是指:在排序過程中,對於序列中相等元素,其原始相對順序是否保持不變。若保持不變則為穩定排序,否則為不穩定排序。

穩定性在實際開發中至關重要(如多關鍵字排序、保留原始關聯信息等場景)。本文將系統梳理 Python 中常用排序算法的穩定性、原理、實現及應用場景,幫你快速理清各類算法的核心差異。

一、先明確:穩定性的核心意義

1. 穩定排序的價值

假設要對「學生列表」排序:先按「班級」升序排序,再按「成績」降序排序。若兩次排序均使用穩定算法,可保證:

  • 同一班級的學生,在按成績排序後,仍保持原班級內的相對順序(如同一班級中成績相同的學生,原始排名不變);
  • 無需額外記錄關聯信息,直接通過多輪穩定排序即可實現複雜需求。

2. 穩定性的直觀示例

原始序列(括號內為原始索引):[(2, 0), (1, 1), (2, 2), (3, 3)]

  • 穩定排序後(相等元素 2 的索引 0 < 2 保持不變):[(1, 1), (2, 0), (2, 2), (3, 3)]
  • 不穩定排序後(相等元素 2 的順序被打亂):[(1, 1), (2, 2), (2, 0), (3, 3)]

二、Python 常用排序算法穩定性彙總表

排序算法

穩定性

時間複雜度(平均)

時間複雜度(最壞)

空間複雜度

核心原理

Python 內置支持

冒泡排序

穩定

O(n²)

O(n²)

O(1)

相鄰元素兩兩比較,逆序則交換

無(需手動實現)

插入排序

穩定

O(n²)

O(n²)

O(1)

逐個插入到已排序序列的正確位置

無(需手動實現)

歸併排序

穩定

O(n log n)

O(n log n)

O(n)

分治思想,合併兩個有序子序列

無(需手動實現)

基數排序

穩定

O (d・n)(d 為位數)

O(d·n)

O (n + k)(k 為基數)

按位分組排序(從低到高 / 高到低)

無(需手動實現)

選擇排序

不穩定

O(n²)

O(n²)

O(1)

每次選最小元素放到已排序區末尾

無(需手動實現)

快速排序

不穩定

O(n log n)

O (n²)(最壞)

O (log n)(遞歸棧)

分治思想,以基準元素分區

無(需手動實現)

堆排序

不穩定

O(n log n)

O(n log n)

O(1)

構建大頂堆 / 小頂堆,依次提取堆頂

無(需手動實現)

Timsort(Python 內置)

穩定

O(n log n)

O(n log n)

O(n)

歸併排序 + 插入排序(混合優化)

list.sort()sorted()

關鍵結論:

  1. Python 官方推薦使用 sorted() 或 list.sort()(底層為 Timsort),穩定且高效,覆蓋 99% 的業務場景;
  2. 簡單排序(冒泡、插入)穩定但效率低,僅適用於小規模數據(n < 1000);
  3. 高效排序中,僅歸併、基數排序穩定,快排、堆排不穩定;
  4. 空間敏感場景可選擇堆排(O (1) 空間),但需犧牲穩定性。

三、核心排序算法實現與穩定性驗證

1. 穩定排序算法(實現 + 驗證)

(1)冒泡排序(穩定)

核心邏輯:相鄰元素兩兩比較,逆序則交換,每輪將最大元素 “冒泡” 到末尾。

穩定性保證:僅當元素嚴格逆序時才交換,相等元素不交換,保留原始順序。

python

運行

def bubble_sort(arr):
    n = len(arr)
    for i in range(n - 1):
        swapped = False
        # 每輪遍歷後,末尾 i 個元素已排序,無需再比較
        for j in range(n - 1 - i):
            # 僅當 arr[j] > arr[j+1] 時交換(相等不交換)
            if arr[j] > arr[j + 1]:
                arr[j], arr[j + 1] = arr[j + 1], arr[j]
                swapped = True
        if not swapped:  # 無交換説明已有序,提前退出
            break
    return arr

# 穩定性驗證(元素為元組,第一列為排序鍵,第二列為原始索引)
test_arr = [(2, 0), (1, 1), (2, 2), (3, 3)]
sorted_arr = bubble_sort(test_arr.copy())
print("冒泡排序結果(穩定):", sorted_arr)
# 輸出:[(1, 1), (2, 0), (2, 2), (3, 3)](相等元素 2 的索引順序不變)

(2)歸併排序(穩定)

核心邏輯:分治思想,將數組拆分為兩個子數組,遞歸排序後合併兩個有序子數組。

穩定性保證:合併時,若兩個子數組的元素相等,優先取左子數組的元素,保留原始順序。

python

運行

def merge_sort(arr):
    if len(arr) <= 1:
        return arr
    # 分治:拆分為左右兩個子數組
    mid = len(arr) // 2
    left = merge_sort(arr[:mid])
    right = merge_sort(arr[mid:])
    # 合併兩個有序子數組
    return merge(left, right)

def merge(left, right):
    res = []
    i = j = 0
    while i < len(left) and j < len(right):
        # 相等時優先取左子數組元素(保證穩定性)
        if left[i] <= right[j]:
            res.append(left[i])
            i += 1
        else:
            res.append(right[j])
            j += 1
    # 拼接剩餘元素
    res.extend(left[i:])
    res.extend(right[j:])
    return res

# 穩定性驗證
test_arr = [(2, 0), (1, 1), (2, 2), (3, 3)]
sorted_arr = merge_sort(test_arr)
print("歸併排序結果(穩定):", sorted_arr)
# 輸出:[(1, 1), (2, 0), (2, 2), (3, 3)]

(3)Timsort(Python 內置,穩定)

Python 的 sorted() 和 list.sort() 底層實現為 Timsort,結合了歸併排序和插入排序的優點:

  • 對小規模子數組(默認長度 ≤ 64)使用插入排序(高效且穩定);
  • 對大規模子數組使用歸併排序(穩定且適合大數據量);
  • 自動識別數組中的 “有序段”(run),減少排序次數。

python

運行

# Timsort 穩定性驗證
test_arr = [(2, 0), (1, 1), (2, 2), (3, 3)]
sorted_arr = sorted(test_arr)  # 或 test_arr.sort()
print("Timsort 結果(穩定):", sorted_arr)
# 輸出:[(1, 1), (2, 0), (2, 2), (3, 3)]

2. 不穩定排序算法(實現 + 穩定性破壞示例)

(1)選擇排序(不穩定)

核心邏輯:每次從無序區選擇最小元素,與無序區第一個元素交換。

穩定性破壞:交換過程可能導致相等元素的相對順序改變。

python

運行

def selection_sort(arr):
    n = len(arr)
    for i in range(n - 1):
        # 找到無序區最小元素的索引
        min_idx = i
        for j in range(i + 1, n):
            if arr[j] < arr[min_idx]:
                min_idx = j
        # 交換最小元素與無序區第一個元素(可能破壞穩定性)
        arr[i], arr[min_idx] = arr[min_idx], arr[i]
    return arr

# 穩定性破壞示例
test_arr = [(2, 0), (1, 1), (2, 2), (3, 3)]
sorted_arr = selection_sort(test_arr.copy())
print("選擇排序結果(不穩定):", sorted_arr)
# 輸出:[(1, 1), (2, 2), (2, 0), (3, 3)](相等元素 2 的索引順序被打亂)

(2)快速排序(不穩定)

核心邏輯:選擇基準元素,將數組分為 “小於基準” 和 “大於基準” 兩部分,遞歸排序。

穩定性破壞:基準元素與其他元素交換時,可能導致相等元素的相對順序改變。

python

運行

def quick_sort(arr):
    if len(arr) <= 1:
        return arr
    # 選擇基準元素(此處選第一個元素,可優化為隨機選擇)
    pivot = arr[0]
    # 分區:小於基準、等於基準、大於基準
    left = [x for x in arr[1:] if x < pivot]
    middle = [x for x in arr if x == pivot]
    right = [x for x in arr[1:] if x > pivot]
    # 遞歸排序併合並(middle 保留相等元素順序,但分區交換可能破壞整體穩定)
    return quick_sort(left) + middle + quick_sort(right)

# 穩定性破壞示例(複雜場景下更明顯)
test_arr = [(3, 0), (2, 1), (3, 2), (1, 3)]
sorted_arr = quick_sort(test_arr)
print("快速排序結果(不穩定):", sorted_arr)
# 可能輸出:[(1, 3), (2, 1), (3, 2), (3, 0)](相等元素 3 的順序被打亂)

四、穩定性的實際應用場景

1. 多關鍵字排序

例如:先按 “日期” 升序排序,再按 “金額” 降序排序。利用穩定排序的特性,可分兩步實現:

python

運行

# 原始數據:(日期, 金額, 原始索引)
data = [(20250101, 100, 0), (20250102, 200, 1), (20250101, 150, 2), (20250102, 200, 3)]

# 第一步:按金額降序排序(穩定排序)
sorted_by_amount = sorted(data, key=lambda x: -x[1], reverse=False)
# 第二步:按日期升序排序(穩定排序,保留金額排序結果)
final_sorted = sorted(sorted_by_amount, key=lambda x: x[0])

print("多關鍵字排序結果:", final_sorted)
# 輸出:[(20250101, 150, 2), (20250101, 100, 0), (20250102, 200, 1), (20250102, 200, 3)]
# 説明:同一日期內,金額降序;金額相同時,原始索引順序不變

2. 保留原始關聯信息

例如:對學生成績排序時,需保留學生的原始學號(即使成績相同,學號順序不變):

python

運行

# 學生數據:(成績, 學號)
students = [(85, "001"), (92, "002"), (85, "003"), (78, "004")]

# 使用穩定排序(sorted())
sorted_students = sorted(students, key=lambda x: -x[0])
print("成績排序(保留學號順序):", sorted_students)
# 輸出:[(92, "002"), (85, "001"), (85, "003"), (78, "004")]
# 説明:成績相同的 001 和 003,原始順序不變

3. 避免數據關聯丟失

在數據庫查詢結果排序、日誌分析等場景中,穩定排序可避免因排序導致的隱性關聯信息丟失(如日誌的時間順序、數據的插入順序)。

五、關鍵注意事項

1. 如何判斷排序算法是否穩定?

  • 核心原則:相等元素是否會被交換位置
  • 快速判斷技巧:
  • 基於 “交換” 的排序(冒泡、插入):穩定(僅逆序交換);
  • 基於 “選擇” 的排序(選擇、快排、堆排):不穩定(直接交換最小 / 最大元素);
  • 基於 “合併” 的排序(歸併、Timsort):穩定(合併時保留順序)。

2. Python 中排序的 key 函數與穩定性

sorted() 和 list.sort() 的 key 參數不影響穩定性:無論 key 如何定義,相等 key 對應的元素,原始相對順序始終保持不變。

python

運行

# key 函數不影響穩定性
data = [(2, 0), (1, 1), (2, 2)]
sorted_data = sorted(data, key=lambda x: x[0])  # key 為第一個元素
print(sorted_data)  # 輸出:[(1, 1), (2, 0), (2, 2)](相等 key 保留原始順序)

3. 何時可以忽略穩定性?

  • 排序的元素是 “獨立個體”(無關聯信息需要保留);
  • 排序鍵是唯一的(無相等元素,穩定性無意義);
  • 對排序效率要求極高,且空間敏感(可選擇堆排等不穩定算法)。

六、總結

  1. 穩定性核心:相等元素的原始相對順序是否保留;
  2. 首選方案:Python 中優先使用 sorted() 或 list.sort()(Timsort),穩定且高效;
  3. 場景匹配
  • 多關鍵字排序、保留關聯信息 → 穩定排序(歸併、Timsort、冒泡 / 插入(小規模));
  • 空間敏感、無關聯信息 → 不穩定排序(堆排、快排);
  1. 避坑點:選擇排序、快排、堆排不穩定,需避免在需要保留順序的場景中使用。

掌握排序算法的穩定性,能幫你在實際開發中避免隱性 Bug,尤其是複雜排序場景下的邏輯錯誤。如果需要針對特定場景(如大規模數據排序、自定義排序規則)進一步優化,可隨時補充需求!