當數據量遠超內存(如 1G 文件,內存 128M),外部排序採用「分階段內部排序 → 多輪多路歸並」策略:
朴素 K 路歸並每次比較 K-1 次,總複雜度 O(N·K)。敗者樹(完全二叉樹,葉子為 K 路當前記錄,內節點記錄「失者」索引)可在 O(log K) 取得最小值,總複雜度降為 O(N log K),對海量數據極其關鍵。
# 生成 512MB 隨機二進制文件(128M 個 int32)
dd if=/dev/urandom of=random.dat bs=1M count=512
typedef struct run_t {
int *buf; // 輸入緩衝區
int length; // 緩衝區當前有效記錄數
int offset; // 已讀文件偏移
int idx; // 緩衝區讀指針
} run_t;
static int ls[MAX_WAYS]; // 敗者樹數組,ls[0] 存最終勝者索引
main()./sort random.dat 64 → 64 路歸並。run_t。foo_*.dat。bar_*.dat,雙前綴交替避免文件名衝突。num_runs == 1,輸出耗時統計。k_merge()void k_merge(run_t** runs, char* input_prefix, int num_runs, int base, int n_merge) {
create_loser_tree(runs, num_runs); // 建樹
while (live_runs > 0) {
mr = runs[ls[0]]; // 取勝者
buffer[bp++] = mr->buf[mr->idx++];
if (output_buffer_full) flush_to_disk();
if (mr_buffer_exhausted) {
if (base) { mr->buf[mr->idx] = MAX_INT; live_runs--; } // 首輪:用哨兵
else { read_next_chunk_from_disk(); } // 後輪:續讀
}
adjust(runs, num_runs, ls[0]); // 調整敗者樹
}
flush_remaining_buffer();
}
adjust() / create_loser_tree()自下而上比較,失者上移,勝者繼續向上比較,最終 ls[0] 為全局最小記錄所在路索引。代碼極其緊湊高效。
| 文件大小 | 耗時 | 環境 |
|---|---|---|
| 128 MB | 14.72 秒 | i5 / 4G RAM / 64路歸並 |
| 256 MB | 30.89 秒 | |
| 512 MB | 71.65 秒 | |
| 1 GB | 169.18 秒 |
時間近似線性增長,驗證 O(N log K) 效率。
附帶 check.c 讀取二進制文件按 10 個/行打印,便於人工抽查有序性。