首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C++ >> 關於C++

排序算法全解析:從基礎比較排序到線性時間非比較排序

     編輯:關於C++

在計算機科學領域,排序被公認為算法研究中最基礎、也是最關鍵的問題之一。許多學者指出,對待解決的問題先進行排序,往往能簡化問題分析並啟發求解思路。本文將系統梳理主流排序算法,涵蓋比較排序與非比較排序兩大類別,並附帶 C++ 代碼實現與複雜度分析。

一、排序算法分類與理論基礎

根據是否需要比較元素大小,排序算法分為比較排序與非比較排序。

1.1 比較排序

比較排序通過元素間的大小比較確定相對順序。根據決策樹模型可證明:任意比較排序對 n 個元素排序的最壞情況時間下界為 Ω(n log n)。因此,堆排序與歸並排序達到 O(n log n) 最壞情況複雜度,屬於漸近最優比較排序。

常見比較排序包括:

  • 插入排序:原址排序,穩定,O(n²)
  • 歸並排序:非原址,穩定,O(n log n)
  • 堆排序:原址,不穩定,O(n log n)
  • 快速排序:原址,不穩定,平均 O(n log n),最壞 O(n²)

原址排序(In-place)優勢在於不需額外大量空間,適合記憶體受限環境。快速排序雖最壞情況為 O(n²),但期望時間 O(n log n) 且隱含常數極小,是大數組排序的首選。

1.2 非比較排序

打破 Ω(n log n) 下界,利用數據特殊性質實現線性時間:

  • 計數排序:元素為 0~k 間整數,O(k+n)
  • 基數排序:d 位數字、每位 k 種取值,O(d(n+k))
  • 桶排序:輸入服從均勻分布於 [0,1),平均 O(n)
算法最壞情況平均/期望時間
插入排序O(n²)O(n²)
歸並排序O(n log n)O(n log n)
堆排序O(n log n)—
快速排序O(n²)O(n log n)
計數排序O(k+n)O(k+n)
基數排序O(d(n+k))O(d(n+k))
桶排序O(n²)O(n)

二、核心算法詳解與 C++ 實現

2.1 插入排序

核心思想:將數組分為「已排序」與「未排序」兩部分,每次從未排序區取出元素,在已排序區從後向前比較並移動元素,找到插入位置。

特點:穩定、原址、代碼緊湊、適合小規模或近乎有序數據。

void insert_sort(vector<int>& A) {
    for (int i = 1; i < A.size(); ++i) {
        int key = A[i];
        int j = i - 1;
        while (j >= 0 && A[j] > key) {
            A[j + 1] = A[j];
            --j;
        }
        A[j + 1] = key;
    }
}

插入排序運行結果

2.2 歸並排序

核心思想:分治法。遞歸將數組二分至單元素,再通過 merge 操作將兩個有序子數組合併。

特點:穩定、時間複雜度固定 O(n log n)、需 O(n) 輔助空間。

void merge(vector<int>& nums, int left, int mid, int right) {
    vector<int> L(nums.begin() + left, nums.begin() + mid + 1);
    vector<int> R(nums.begin() + mid + 1, nums.begin() + right + 1);
    L.push_back(INT_MAX);
    R.push_back(INT_MAX);
    int i = 0, j = 0;
    for (int k = left; k <= right; ++k) {
        if (L[i] <= R[j]) nums[k] = L[i++];
        else nums[k] = R[j++];
    }
}

void merge_sort(vector<int>& nums, int left, int right) {
    if (left < right) {
        int mid = (left + right) / 2;
        merge_sort(nums, left, mid);
        merge_sort(nums, mid + 1, right);
        merge(nums, left, mid, right);
    }
}

歸並排序運行結果

2.3 堆排序

堆排序基於二叉堆數據結構,分為最大堆(父節點 ≥ 子節點)與最小堆(父節點 ≤ 子節點)。

2.3.1 堆的性質維護 —— max_heapify

「逐層下沉」策略:假設左右子樹已是最大堆,若根節點小於某子節點,則交換並遞歸向下調整,時間 O(log n)。

void max_heapify(vector<int>& a, int i, int heap_size) {
    int largest = i;
    int left = 2 * i + 1;
    int right = 2 * i + 2;
    if (left < heap_size && a[left] > a[largest]) largest = left;
    if (right < heap_size && a[right] > a[largest]) largest = right;
    if (largest != i) {
        swap(a[i], a[largest]);
        max_heapify(a, largest, heap_size);
    }
}

堆維護演示

2.3.2 建堆 —— build_max_heap

從最後一個非葉節點(索引 n/2 - 1)逆序向前調用 max_heapify。雖單次 O(log n),但總複雜度經證明為 O(n)(非漸近緊確的 O(n log n))。

void build_max_heap(vector<int>& a, int heap_size) {
    for (int i = heap_size / 2 - 1; i >= 0; --i)
        max_heapify(a, i, heap_size);
}

建堆演示

2.3.3 堆排序流程

  1. 建最大堆:O(n)
  2. 交換堆頂(最大值)與堆尾元素,堆大小 -1
  3. 對新堆頂調用 max_heapify 恢復堆性質
  4. 重複步驟 2-3 直到堆大小為 1

總複雜度 O(n log n),原址、不穩定。

void heapsort(vector<int>& a) {
    int n = a.size();
    build_max_heap(a, n);
    for (int i = n - 1; i >= 1; --i) {
        swap(a[0], a[i]);
        max_heapify(a, 0, i);
    }
}

堆排序結果

2.4 快速排序

核心思想:選取基準值,通過 partition 將數組劃分為「小於基準」與「大於基準」兩區,遞歸排序兩區。

int partition(vector<int>& a, int p, int r) {
    int x = a[r];
    int i = p - 1;
    for (int j = p; j < r; ++j) {
        if (a[j] < x) {
            ++i;
            swap(a[i], a[j]);
        }
    }
    swap(a[i + 1], a[r]);
    return i + 1;
}

void quicksort(vector<int>& a, int p, int r) {
    if (p < r) {
        int q = partition(a, p, r);
        quicksort(a, p, q - 1);
        quicksort(a, q + 1, r);
    }
}

隨機化快排:隨機選取基準,避免有序數組導致最壞情況,期望複雜度 O(n log n)。

int randomized_partition(vector<int>& a, int p, int r) {
    int i = rand() % (r - p + 1) + p;
    swap(a[i], a[r]);
    return partition(a, p, r);
}

快速排序結果 隨機快排結果

2.5 計數排序

適用於 0~k 間整數。統計每個值出現次數,累積計數即得排序後位置。穩定、O(k+n)。

void counting_sort(vector<int>& A, vector<int>& B, int k) {
    vector<int> C(k + 1, 0);
    for (int x : A) ++C[x];
    for (int i = 1; i <= k; ++i) C[i] += C[i - 1];
    for (int i = A.size() - 1; i >= 0; --i) {
        B[C[A[i]] - 1] = A[i];
        --C[A[i]];
    }
}

計數排序結果

2.6 基數排序與桶排序

  • 基數排序:按位數從低到高(或高到低)穩定排序,常用計數排序作子程序。適合定長整數/字符串。硬體緩存友好度不如快排。
  • 桶排序:假設輸入均勻分布於 [0,1),劃分 n 個桶,分桶後各桶內插入排序,最後串聯。平均 O(n),最壞 O(n²)。

三、總結與工程建議

  • 小規模/近乎有序:插入排序
  • 通用穩定排序:歸並排序
  • 記憶體受限、需保證 O(n log n):堆排序
  • 大數組平均最快:快速排序(隨機化版本)
  • 整數範圍小:計數/基數排序
  • 浮點數均勻分布:桶排序

算法實現需熟練掌握代碼編寫,並能嚴謹證明時間複雜度,這是工程師的基本功。參考經典教材《算法導論》可深化理解。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved