歸併排序屬於 分治法:將大問題分解為相似的小問題 (分),遞迴解決小問題 (治),再合併結果 (併)。時間複雜度穩定 O(n log n),為穩定排序。
/**
* 合併兩個已排序陣列為一個有序陣列
* @param array $left 左半有序陣列
* @param array $right 右半有序陣列
* @return array 合併後有序陣列
*/
function merge(array $left, array $right): array {
$result = [];
$i = $j = 0;
$lenL = count($left);
$lenR = count($right);
// 雙指標比較合併
while ($i < $lenL && $j < $lenR) {
if ($left[$i] <= $right[$j]) {
$result[] = $left[$i++];
} else {
$result[] = $right[$j++];
}
}
// 附加剩餘元素
while ($i < $lenL) $result[] = $left[$i++];
while ($j < $lenR) $result[] = $right[$j++];
return $result;
}
/**
* 歸併排序遞迴版
* @param array $arr 待排序陣列 (傳址修改)
* @return void
*/
function mergeSortRecursive(array &$arr): void {
$n = count($arr);
if ($n <= 1) return; // 基礎情況
$mid = (int)($n / 2);
$left = array_slice($arr, 0, $mid);
$right = array_slice($arr, $mid);
mergeSortRecursive($left);
mergeSortRecursive($right);
// 合併回原陣列
$merged = merge($left, $right);
for ($i = 0; $i < $n; $i++) {
$arr[$i] = $merged[$i];
}
}
// 測試
$arr = [4, 7, 6, 3, 9, 5, 8];
mergeSortRecursive($arr);
print_r($arr); // [3, 4, 5, 6, 7, 8, 9]
/**
* 歸併排序索引版:在原陣列上操作,使用暫存陣列
* @param array &$arr
* @param int $left 左邊界 (含)
* @param int $right 右邊界 (含)
* @param array &$temp 共用暫存陣列 (避免重複分配)
*/
function mergeSortIndexed(array &$arr, int $left, int $right, array &$temp): void {
if ($left >= $right) return;
$mid = ($left + $right) >> 1; // 等同 floor(($left+$right)/2)
mergeSortIndexed($arr, $left, $mid, $temp);
mergeSortIndexed($arr, $mid + 1, $right, $temp);
// 合併 [left..mid] 和 [mid+1..right] 到 $temp
$i = $left;
$j = $mid + 1;
$k = $left;
while ($i <= $mid && $j <= $right) {
if ($arr[$i] <= $arr[$j]) {
$temp[$k++] = $arr[$i++];
} else {
$temp[$k++] = $arr[$j++];
}
}
while ($i <= $mid) $temp[$k++] = $arr[$i++];
while ($j <= $right) $temp[$k++] = $arr[$j++];
// 複製回原陣列
for ($i = $left; $i <= $right; $i++) {
$arr[$i] = $temp[$i];
}
}
/** 入口函式 */
function mergeSort(array &$arr): void {
$temp = []; // 預分配暫存陣列
mergeSortIndexed($arr, 0, count($arr) - 1, $temp);
}
/**
* 歸併排序迭代版
* @param array &$arr
*/
function mergeSortIterative(array &$arr): void {
$n = count($arr);
$temp = [];
// 寬度從 1, 2, 4, 8... 直到 >= n
for ($width = 1; $width < $n; $width <<= 1) {
// 兩兩合併相鄰區塊
for ($left = 0; $left < $n; $left += 2 * $width) {
$mid = min($left + $width - 1, $n - 1);
$right = min($left + 2 * $width - 1, $n - 1);
// 合併 [left..mid] 和 [mid+1..right]
$i = $left;
$j = $mid + 1;
$k = $left;
while ($i <= $mid && $j <= $right) {
$temp[$k++] = ($arr[$i] <= $arr[$j]) ? $arr[$i++] : $arr[$j++];
}
while ($i <= $mid) $temp[$k++] = $arr[$i++];
while ($j <= $right) $temp[$k++] = $arr[$j++];
// 複製回原陣列
for ($i = $left; $i <= $right; $i++) {
$arr[$i] = $temp[$i];
}
}
}
}
| 版本 | 時間複雜度 | 空間複雜度 | 遞迴深度 | PHP 適用場景 |
|---|---|---|---|---|
| 遞迴 array_slice | O(n log n) | O(n log n) (大量陣列複製) | O(log n) | 教學、小資料量 |
| 索引遞迴 + 共用 temp | O(n log n) | O(n) (單一暫存陣列) | O(log n) | 生產環境推薦 |
| 迭代底向上 | O(n log n) | O(n) | 無 (避免棧溢位) | 極大陣列、嵌入式環境 |
&$arr:避免函式呼叫時複製大陣列。$temp:只在入口分配一次,遞迴/迴圈重複使用,大幅減少記憶體分配/GC 壓力。>> 1 代替除法:($left + $right) >> 1 比 floor(($left+$right)/2) 略快。array_slice:每次切片建立新陣列,改用索引邊界操作原陣列。<= 保證相等元素相對順序不變。function benchmark(string $name, callable $sort, array $data): void {
$arr = $data;
$start = microtime(true);
$sort($arr);
$elapsed = (microtime(true) - $start) * 1000;
echo "$name: ${elapsed} ms\n";
// 驗證正確性
if ($arr !== range(1, count($data))) {
echo " ⚠️ 排序結果錯誤!\n";
}
}
$size = 100000;
$data = range(1, $size);
shuffle($data);
benchmark('PHP sort()', 'sort', $data);
benchmark('mergeSortRecursive', 'mergeSortRecursive', $data);
benchmark('mergeSort (indexed)', 'mergeSort', $data);
benchmark('mergeSortIterative', 'mergeSortIterative', $data);
/*
典型輸出 (PHP 8.2, 10萬隨機整數):
PHP sort() : 12.3 ms (內建 C 實作,最快)
mergeSort (indexed) : 85.6 ms (生產可用)
mergeSortIterative : 92.1 ms
mergeSortRecursive : 245.3 ms (array_slice 開銷大)
*/
$left[$i] > 2 * $right[$j] 等條件 (LeetCode 493)。PHP 中實作歸併排序,索引遞迴版配合共用暫存陣列 是效能與可讀性的最佳平衡。理解分治法核心邏輯,並掌握傳址、索引邊界、記憶體複用等 PHP 特有優化技巧,能將演算法效能發揮到極致。生產環境大量資料排序仍建議優先使用內建 sort() / usort() (C 實作 Timsort),僅在需要自訂比較邏輯、穩定排序、外部排序等特殊場景時手寫演算法。