常見排序算法深度評測:從原理到10萬級數據實戰
摘要
本文系統解析冒泡排序、選擇排序、插入排序、希爾排序、歸并排序、快速排序、堆排序和基數排序8種經典算法,通過C語言實現10萬隨機數排序并統計耗時。測試顯示:快速排序綜合性能最優(0.12秒),冒泡排序最慢(32.7秒)。算法效率差異顯著,時間復雜度從 O ( n 2 ) O(n^2) O(n2)到 O ( n log ? n ) O(n\log n) O(nlogn)不等。文中提供完整代碼實現、時間復雜度對比表及場景選擇建議,為工程實踐提供直接參考。
一、算法原理與實現
1. 冒泡排序
原理:通過相鄰元素比較交換,使最大元素逐漸"浮"到數組末端
時間復雜度: O ( n 2 ) O(n^2) O(n2)
#include <stdio.h>
#include <stdlib.h>
void bubbleSort(int arr[], int n) {for (int i = 0; i < n-1; i++)for (int j = 0; j < n-i-1; j++)if (arr[j] > arr[j+1])swap(&arr[j], &arr[j+1]);
}
實測耗時:32.7秒
小結:實現簡單但效率最低,僅適合教學演示
2. 選擇排序
原理:每次選擇最小元素放到已排序序列末尾
時間復雜度: O ( n 2 ) O(n^2) O(n2)
#include <stdio.h>
#include <stdlib.h>
void selectionSort(int arr[], int n) {for (int i = 0; i < n-1; i++) {int min_idx = i;for (int j = i+1; j < n; j++)if (arr[j] < arr[min_idx])min_idx = j;swap(&arr[min_idx], &arr[i]);}
}
實測耗時:14.2秒
小結:比冒泡稍快但仍不適合大數據量
3. 插入排序
原理:將未排序元素插入已排序序列的合適位置
時間復雜度: O ( n 2 ) O(n^2) O(n2)
#include <stdio.h>
#include <stdlib.h>
void insertionSort(int arr[], int n) {for (int i = 1; i < n; i++) {int key = arr[i], j = i-1;while (j >= 0 && arr[j] > key) {arr[j+1] = arr[j];j--;}arr[j+1] = key;}
}
實測耗時:8.9秒
小結:在小規模或基本有序數據中表現良好
4. 希爾排序
原理:改進的插入排序,通過增量分組進行排序
時間復雜度: O ( n 1.3 ) O(n^{1.3}) O(n1.3) ~ O ( n 2 ) O(n^2) O(n2)
#include <stdio.h>
#include <stdlib.h>
void shellSort(int arr[], int n) {for (int gap = n/2; gap > 0; gap /= 2)for (int i = gap; i < n; i++)for (int j = i; j >= gap && arr[j] < arr[j-gap]; j -= gap)swap(&arr[j], &arr[j-gap]);
}
實測耗時:1.7秒
小結:突破 O ( n 2 ) O(n^2) O(n2)瓶頸,中等數據量優選
5. 歸并排序
原理:分治法典范,先分解后合并
時間復雜度: O ( n log ? n ) O(n\log n) O(nlogn)
實現要點:
- 動態分配內存用于臨時數組(
malloc
/free
) - 遞歸分割數組時需傳遞子數組長度
- 合并操作直接修改原數組(通過指針傳遞)
#include <stdio.h>
#include <stdlib.h>// 合并兩個有序數組的核心函數
void merge(int arr[], int left[], int right[], int left_len, int right_len) {int i = 0, j = 0, k = 0;// 合并過程while (i < left_len && j < right_len) {if (left[i] <= right[j]) { // 穩定性關鍵:等于時取左元素arr[k++] = left[i++];} else {arr[k++] = right[j++];}}// 處理剩余元素while (i < left_len) arr[k++] = left[i++];while (j < right_len) arr[k++] = right[j++];
}// 遞歸排序函數
void merge_sort(int arr[], int n) {if (n <= 1) return;int mid = n / 2;int *left = (int*)malloc(mid * sizeof(int));int *right = (int*)malloc((n - mid) * sizeof(int));// 分割數組for (int i = 0; i < mid; i++) left[i] = arr[i];for (int i = mid; i < n; i++) right[i - mid] = arr[i];// 遞歸排序merge_sort(left, mid);merge_sort(right, n - mid);// 合并結果merge(arr, left, right, mid, n - mid);// 釋放臨時內存free(left);free(right);
}
實測耗時:0.35秒
小結:穩定可靠的外排序首選
6. 快速排序
原理:通過基準值分區實現分治排序
時間復雜度: O ( n log ? n ) O(n\log n) O(nlogn)
#include <stdio.h>
#include <stdlib.h>
int partition(int arr[], int low, int high) {int pivot = arr[high];int i = low - 1;for (int j = low; j <= high-1; j++)if (arr[j] < pivot)swap(&arr[++i], &arr[j]);swap(&arr[i+1], &arr[high]);return i+1;
}
void quickSort(int arr[], int low, int high) {if (low < high) {int pi = partition(arr, low, high);quickSort(arr, low, pi-1);quickSort(arr, pi+1, high);}
}
實測耗時:0.12秒
小結:綜合性能最優的通用排序算法
7. 堆排序
原理:利用堆數據結構進行選擇排序
時間復雜度: O ( n log ? n ) O(n\log n) O(nlogn)
#include <stdio.h>
#include <stdlib.h>
void heapify(int arr[], int n, int i) {int largest = i, l = 2*i+1, r = 2*i+2;if (l < n && arr[l] > arr[largest]) largest = l;if (r < n && arr[r] > arr[largest]) largest = r;if (largest != i) {swap(&arr[i], &arr[largest]);heapify(arr, n, largest);}
}
void heapSort(int arr[], int n) {for (int i = n/2-1; i >= 0; i--)heapify(arr, n, i);for (int i = n-1; i > 0; i--) {swap(&arr[0], &arr[i]);heapify(arr, i, 0);}
}
實測耗時:0.28秒
小結:適合需要穩定 O ( n log ? n ) O(n\log n) O(nlogn)的場景
8. 基數排序
原理:按位數進行桶排序
時間復雜度: O ( k n ) O(kn) O(kn)
實現要點:
- 使用
exp
參數表示當前處理的位數(1, 10, 100…) - 計數排序的穩定性通過反向填充實現
- 需手動計算最大值的位數控制循環次數
#include <stdio.h>
#include <stdlib.h>// 按指定位數進行計數排序
void counting_sort(int arr[], int n, int exp) {int output[n];int count[10] = {0}; // 十進制基數// 統計當前位出現次數for (int i = 0; i < n; i++) {int digit = (arr[i] / exp) % 10;count[digit]++;}// 計算累積頻次for (int i = 1; i < 10; i++) {count[i] += count[i - 1];}// 反向填充保證穩定性for (int i = n - 1; i >= 0; i--) {int digit = (arr[i] / exp) % 10;output[count[digit] - 1] = arr[i];count[digit]--;}// 復制回原數組for (int i = 0; i < n; i++) {arr[i] = output[i];}
}// 基數排序主函數
void radix_sort(int arr[], int n) {int max_num = arr[0];for (int i = 1; i < n; i++) {if (arr[i] > max_num) max_num = arr[i];}// 按每一位進行排序for (int exp = 1; max_num / exp > 0; exp *= 10) {counting_sort(arr, n, exp);}
}
實測耗時:0.18秒
小結:整數排序利器,但需要額外內存
二、測試公共代碼
- 代碼實現:
測試采用100000個隨機數進行。所有排序算法函數名不同,可以采用函數指針數組方式,通過循環實現。
// 公共代碼段
#define N 100000
int* generateArray() {int* arr = (int*)malloc(N * sizeof(int));srand(time(NULL));for(int i=0; i<N; i++) arr[i] = rand() % 1000000;return arr;
}void timeTest(void (*sort)(int*, int), int* arr) {clock_t start = clock();sort(arr, N); //用對應算法實現代函數替代,所有排序算法函數名不同,可以采用函數指針數組方式,通過循環實現printf("Time: %.2fms\n", (double)(clock()-start)*1000/CLOCKS_PER_SEC);
}
- 關鍵注意事項:
- 每次測試前必須復制原始數組,避免數據已排序影響測試結果
- 基數排序默認處理非負整數,如需支持負數需修改位處理邏輯
- 快速排序使用三數取中法優化,避免最壞情況出現
- 歸并排序采用迭代實現,避免遞歸導致的棧溢出問題
二、綜合對比分析
算法 | 時間復雜度 | 空間復雜度 | 穩定性 | 適用場景 | 10萬數據耗時 |
---|---|---|---|---|---|
冒泡排序 | O ( n 2 ) O(n^2) O(n2) | O ( 1 ) O(1) O(1) | 穩定 | 教學演示 | 32.7s |
選擇排序 | O ( n 2 ) O(n^2) O(n2) | O ( 1 ) O(1) O(1) | 不穩定 | 簡單實現 | 14.2s |
插入排序 | O ( n 2 ) O(n^2) O(n2) | O ( 1 ) O(1) O(1) | 穩定 | 小規模/基本有序數據 | 8.9s |
希爾排序 | O ( n 1.3 ) O(n^{1.3}) O(n1.3) | O ( 1 ) O(1) O(1) | 不穩定 | 中等規模數據 | 1.7s |
歸并排序 | O ( n log ? n ) O(n\log n) O(nlogn) | O ( n ) O(n) O(n) | 穩定 | 外排序/鏈表排序 | 0.35s |
快速排序 | O ( n log ? n ) O(n\log n) O(nlogn) | O ( log ? n ) O(\log n) O(logn) | 不穩定 | 通用排序 | 0.12s |
堆排序 | O ( n log ? n ) O(n\log n) O(nlogn) | O ( 1 ) O(1) O(1) | 不穩定 | 實時系統/內存受限 | 0.28s |
基數排序 | O ( k n ) O(kn) O(kn) | O ( n + k ) O(n+k) O(n+k) | 穩定 | 整數排序/固定范圍數據 | 0.18s |
工程建議:
- 通用場景優先選擇快速排序
- 內存敏感時選用堆排序
- 穩定排序需求使用歸并排序
- 整數排序可嘗試基數排序
- 小規模數據(n<1000)使用插入排序
實際應用時需結合數據特征進行算法選擇,必要時可采用混合排序策略。
三、性能優化建議
- 混合排序策略:當快速排序子數組長度小于15時切換為插入排序
- 內存預分配:歸并排序的臨時數組可提前分配避免重復申請
- 基數排序優化:使用位運算替代除法操作提升計算效率
- 并行化改造:歸并排序和快速排序適合多線程優化