一、基本思想
歸並排序算法是將兩個(或兩個以上)有序表合並成一個新的有序表,即把待排序序列分為若干個子序列,使每個子序列有序,再將已有序的子序列合並,得到完全有序的序列。該算法是采用分治法(Divide and Conquer)的一個非常典型的應用。
二、算法過程
歸並主要做兩件事:
1)“分解”——將序列每次折半划分。
2)“合並”——將划分后的序列段兩兩合並后排序。
三、算法圖解及PHP代碼實現
1、遞歸——自頂向下
遞歸過程是將待排序數組一分為二,直至排序數組就剩下一個元素為止,然后不斷的合並兩個排好序的數組
其中,合並過程如下(以第2趟歸並為例):
PHP代碼如下:
<?php // 遞歸 function mergeSort(&$arr, $left, $right) { if ($left < $right) { // 找出中間索引 $mid = floor(($left + $right) / 2); // 對左邊數組進行遞歸 mergeSort($arr, $left, $mid); // 對右邊數組進行遞歸 mergeSort($arr, $mid + 1, $right); // 合並 merge($arr, $left, $mid, $right); } } // 將兩個有序數組合並成一個有序數組 function merge(&$arr, $left, $mid, $right) { $i = $left; // 左數組的下標 $j = $mid + 1; // 右數組的下標 $temp = array();// 臨時合並數組 // 掃描第一段和第二段序列,直到有一個掃描結束 while ($i <= $mid && $j <= $right) { // 判斷第一段和第二段取出的數哪個更小,將其存入合並序列,並繼續向下掃描 if ($arr[$i] < $arr[$j]) { $temp[] = $arr[$i]; $i++; } else { $temp[] = $arr[$j]; $j++; } } // 比完之后,假如左數組仍有剩余,則直接全部復制到 temp 數組 while ($i <= $mid) { $temp[] = $arr[$i]; $i++; } // 比完之后,假如右數組仍有剩余,則直接全部復制到 temp 數組 while ($j <= $right) { $temp[] = $arr[$j]; $j++; } // 將合並序列復制到原始序列中 for($k = 0; $k < count($temp); $k++) { $arr[$left + $k] = $temp[$k]; } } // 測試 $arr = [85, 24, 63, 45, 17, 31, 96]; mergeSort($arr, 0, count($arr) - 1); print_r($arr);
2、非遞歸——自底向上
非遞歸過程是將待排序數組看成n個長度為1的子序列,數組中的相鄰元素兩兩配對,將他它們排序后,構成n/2組長度為2的排序好的子數組段,然后再將他們排序成長度為4的子數組段,如此繼續下去,直至整個數組排好序。
不管遞歸還是非遞歸,其合並的邏輯都是一樣的。
PHP代碼如下:
<?php //非遞歸 function mSort(&$arr) { $len = count($arr); $size = 1; while ($size <= $len - 1) { // 從第一個元素開始掃描,left 代表第一個分割的序列的第一個元素 $left = 0; while ($left + $size <= $len - 1) { // mid 代表第一個分割的序列的最后一個元素 $mid = $left + $size - 1; //right 代表第二個分割的序列的最后一個元素 $right = $mid + $size; if ($right > $len - 1) { // 如果第二個序列個數不足size個 //調整 right 為最后一個元素的下標即可 $right = $len - 1; } // 調用歸並函數,進行分割的序列的分段排序 merge($arr, $left, $mid, $right); $left = $right + 1; } $size *= 2; // 范圍擴大一倍 } } // 將兩個有序數組合並成一個有序數組 function merge(&$arr, $left, $mid, $right) { $i = $left; // 左數組的下標 $j = $mid + 1; // 右數組的下標 $temp = array();// 臨時合並數組 // 掃描第一段和第二段序列,直到有一個掃描結束 while ($i <= $mid && $j <= $right) { // 判斷第一段和第二段取出的數哪個更小,將其存入合並序列,並繼續向下掃描 if ($arr[$i] < $arr[$j]) { $temp[] = $arr[$i]; $i++; } else { $temp[] = $arr[$j]; $j++; } } // 比完之后,假如左數組仍有剩余,則直接全部復制到 temp 數組 while ($i <= $mid) { $temp[] = $arr[$i]; $i++; } // 比完之后,假如右數組仍有剩余,則直接全部復制到 temp 數組 while ($j <= $right) { $temp[] = $arr[$j]; $j++; } // 將合並序列復制到原始序列中 /*for($k = 0; $k < count($temp); $k++) { $arr[$left + $k] = $temp[$k]; }*/ for ($i = $left, $k = 0; $i <= $right; $i++, $k++) { $arr[$i] = $temp[$k]; } } // 測試 $arr = [85, 24, 63, 45, 17, 31, 96, 1]; mSort($arr); print_r($arr);
四、效率分析
1、時間復雜度:O(nlogn)
最好情況、最壞情況和平均時間復雜度均為O(nlogn);
2、空間復雜度:O(n)
算法處理過程中,需要一個大小為 n 的臨時存儲空間保存合並序列,所以空間復雜度為O(n)。
3、穩定性:穩定
在歸並排序中,相等的元素的順序不會改變,所以它是穩定排序。