原文請訪問我的技術部落格番茄技術小站 主要是關於歸併排序演算法,以及一些優化
定義(wiki)
歸併排序(英語:Merge sort,或mergesort),是建立在歸併操作上的一種有效的排序演算法,效率為 {\displaystyle O(n\log n)} {\displaystyle O(n\log n)}(大O符號)。1945年由約翰·馮·諾伊曼首次提出。該演算法是採用分治法(Divide and Conquer)的一個非常典型的應用,且各層分治遞迴可以同時進行。
演示
首先將資料分為兩部分,對這兩部分的資料進行行排序,然後將兩部分排序好的陣列進行歸併(有一種O(N)時間複雜度的方法可以實現)
具體步驟
- 首先將資料2分為兩步分,直到不可劃分為止
-
然後從下往上依次將已經排好序的左右兩邊陣列進行歸併
-
步驟3:
-
步驟4:
-
步驟5:
程式碼實現
<?php
// require('../Library/SortTestHelper.php');
require('../SortingBasic/InsertionSort.php');
/**
* [merge 合併兩個有序的陣列:arr[$l...$mid]和arr[$mid+1, $r]]
* @param [type] $arr [description]
* @param [type] $l [description]
* @param [type] $mid [description]
* @param [type] $r [description]
* @return [type] [description]
*/
function merge(&$arr, $l, $mid, $r){
$tmp = array();
$tmp = array_slice($arr, $l, $r-$l+1, true);
//tmp現在為$arr的副本,以tmp為軸,重新賦值$arr
$i = $l;
$j = $mid+1;
for ($k=$l; $k <= $r; $k++) {
if ($i > $mid) {
$arr[$k] = $tmp[$j];
$j++;
}elseif ($j > $r) {
$arr[$k] = $tmp[$i];
$i++;
}elseif($tmp[$i] < $tmp[$j]){
$arr[$k] = $tmp[$i];
$i++;
}else{
$arr[$k] = $tmp[$j];
$j++;
}
}
}
/**
* [__mergeSort 對區間為[l,r]的元素進行歸併排序]
* @param [type] $arr [description]
* @param [type] $l [description]
* @param [type] $r [description]
* @return [type] [description]
*/
function __mergeSort(&$arr, $l, $r){
//此時為一個元素,不需要進行歸併
if ($l >= $r) {
return;
}
$mid = (int)(($l + $r) / 2);
// print_r($arr);var_dump($l, $mid, $r);die;
//對[l, mid]陣列進行歸併
__mergeSort($arr, $l, $mid);
//對[mid+1, $r]陣列進行歸併
__mergeSort($arr, $mid+1, $r);
merge($arr, $l, $mid, $r);
}
function mergeSort(&$arr, $n){
//對0到n-1的元素今夕歸併
__mergeSort($arr, 0, $n-1);
}
複製程式碼
$n = 10000;
$arr = generateRandomArray($n, 0, $n);
// $arr = generateNearlyOrderedArray($n, 100);
$copy_arr = $arr;
testSort("mergeSort", "mergeSort", $arr, $n);
testSort("insertSortSeo", "insertSortSeo", $copy_arr, $n);
複製程式碼
執行時間:
mergeSort執行的時間為:0.12523293495178s
insertSortSeo執行的時間為:1.8600959777832s
複製程式碼
當資料近乎有序時候呢?
執行時間:
mergeSort執行的時間為:0.067276000976562s
insertSortSeo執行的時間為:0.048550844192505s
複製程式碼
為什麼還沒有插入排序的效果好呢?有沒有優化的點,有的。
分析:
function __mergeSort(&$arr, $l, $r){
//此時為一個元素,不需要進行歸併
if ($l >= $r) {
return;
}
$mid = (int)(($l + $r) / 2);
// print_r($arr);var_dump($l, $mid, $r);die;
//對[l, mid]陣列進行歸併
__mergeSort($arr, $l, $mid);
//對[mid+1, $r]陣列進行歸併
__mergeSort($arr, $mid+1, $r);
merge($arr, $l, $mid, $r);
}
複製程式碼
這段程式碼中,我們並沒有考慮陣列有序的情況,無論如何我們都會進行merge,而這是沒有必要的,
優化點1:
function __mergeSort(&$arr, $l, $r){
//此時為一個元素,不需要進行歸併
if ($l >= $r) {
return;
}
$mid = (int)(($l + $r) / 2);
// print_r($arr);var_dump($l, $mid, $r);die;
//對[l, mid]陣列進行歸併
__mergeSort($arr, $l, $mid);
//對[mid+1, $r]陣列進行歸併
__mergeSort($arr, $mid+1, $r);
// 優化點1
if ($arr[$mid] > $arr[$mid+1]) {
merge($arr, $l, $mid, $r);
}
}
複製程式碼
執行時間:
mergeSort執行的時間為:0.017416954040527s
insertSortSeo執行的時間為:0.05430006980896s
複製程式碼
優化點2:
我們知道,插入排序對於近乎有序的資料是非常快的,優勢很很大, 那麼可不可以應用到歸併排序上呢,答案是可以的, 當資料規模只有15個元素時候,直接採用插入排序
程式碼改進:
function insertSortCom(&$arr, $l, $r){
for ($i=$l+1; $i <= $r; $i++) {
//採用複製的方式
$tmp = $arr[$i];
for ($j=$i; $j > $l && $tmp < $arr[$j-1]; $j--) {
$arr[$j] = $arr[$j-1];
}
$arr[$j] = $tmp;
}
}
function __mergeSort(&$arr, $l, $r){
//此時為一個元素,不需要進行歸併
// if ($l >= $r) {
// return;
// }
//優化點2:對小規模陣列直接使用插入排序
if($r - $l < 15 ){
insertSortCom($arr, $l, $r);
return;
}
$mid = (int)(($l + $r) / 2);
// print_r($arr);var_dump($l, $mid, $r);die;
//對[l, mid]陣列進行歸併
__mergeSort($arr, $l, $mid);
//對[mid+1, $r]陣列進行歸併
__mergeSort($arr, $mid+1, $r);
// 優化點1
if ($arr[$mid] > $arr[$mid+1]) {
merge($arr, $l, $mid, $r);
}
}
複製程式碼
執行時間
mergeSort執行的時間為:0.012771844863892s
insertSortSeo執行的時間為:0.050396919250488s
複製程式碼
自底向上的方法(迭代法)
我們知道遞迴是可以用迭代代替的,我們嘗試改為迭代的方法
//迭代法(自底向上)
function mergeSortBU(&$arr, $n){
//先是1、1合併,然後是2、2合併,再是4、4合併
for ($sz=1; $sz < $n; $sz += $sz) {
for($i = 0; $i < $n; $i+=2*$sz){
merge($arr, $i, $i+$sz-1, min($i+2*$sz-1, $n-1));
}
}
}
複製程式碼
執行結果
// //main
$n = 1000;
$arr = generateRandomArray($n, 0, $n);
// $arr = generateNearlyOrderedArray($n, 100);
$copy_arr = $arr;
testSort("mergeSort", "mergeSort", $arr, $n);
testSort("mergeSortBU", "mergeSortBU", $arr, $n);
testSort("insertSortSeo", "insertSortSeo", $copy_arr, $n);
複製程式碼
結果對比
mergeSort執行的時間為:0.0013480186462402s
mergeSortBU執行的時間為:0.0033299922943115s
insertSortSeo執行的時間為:0.022433042526245s
複製程式碼
-------------------------華麗的分割線--------------------
看完的朋友可以點個喜歡/關注,您的支援是對我最大的鼓勵。
想了解更多,歡迎關注我的微信公眾號:番茄技術小棧