前言
本章我們看看 O(n log n)
級別的排序演算法
很多人就會說了,我可以用這個系列前面的文章就可以優化到 O(n)
級別
而且我在電腦執行的時候,基本都是 1ms
或者是0ms
,很快咻咻咻的
我就不用看你這個撲街博主的文章了吧
No
我給大家看看在不同資料量下的情況
資料量 | n * 2 | n log n | 速度比較 |
---|---|---|---|
10 | 100 | 33 | 3倍 |
100 | 10000 | 664 | 15倍 |
1000 | 10^6 | 9966 | 100倍 |
10000 | 10^8 | 132877 | 753倍 |
100000 | 10^10 | 1660964 | 6020倍 |
大家可以看到,在較大資料量(10萬)的時候,O(n*2)
和 O(n log n)
效率差距可以達到 6000倍
最小也是近 3倍
的差距
大家可以想象一下,就算是 1ms
也是將近 6m
的等待時間
然後大家又會說了,php
本身自帶的函式,很快呀
我為什麼不用自帶的,花時間看你這個系列幹什麼?
撲街博主:”告辭,抱歉,打擾了!”
開個玩笑,大家可以看到哈!
在資料量越來越大的時候,也就是我們現在要看的這個 O(n log n)
優勢也會越來越大
所以我們通常都會說 O(n log n)
比 O(n*2)
要快,並且隨著資料量增大,速度也會越來越明顯,也正是因為如此。我們就需要去理解演算法的思想,為什麼他會變快那麼多?怎麼做到的?在我工作中是否可以用到?
下面我們來試著理解一下 歸併排序法
歸併排序,先簡單瞭解一下思路
- 首先我們有這麼一段資料,我們需要將他們重新整合有序
- 第一次二分
level - 0
- 在歸併排序當中,他是將資料進行二分
- 第二次二分
level - 1
| - 再次二分
level - 2
| | | - 直到分成最小粒度
level - 3
| | | | | | | 最小粒度
下,它本身就是有序的,然後進行歸併,此時的資料為
| | |- 然後再次向上歸併
| - 繼續向上歸併直至有序
- 此時資料已然有序
歸併排序,小總結
歸併排序
其實就是逐層二分後,再逐層進行歸併- 按照層
log n
級別,和每次我們合併的過程使用O(n)
的複雜度處理 - 那麼我們就設計出了
n log n
複雜度的演算法 - 同時
歸併排序法
和之前的排序不一樣,我們不能夠在陣列內直接操作交換位置,而是需要建立一個新的臨時變數去儲存排序的資訊 - 這也是
歸併排序法
的缺點,需要額外申請空間去儲存資料資訊 - 但是按照目前我們計算機的儲存價格,時間的效率要比空間效率更珍貴
- 撲街博主說了那麼多,可能大家還是無法理解如何將
歸併排序
寫成程式碼。畢竟狗博主也沒有說清楚 歸併排序
涉及到了新陣列,所以我們需要有一個歸併過程
如何書寫歸併排序過程呢?
- 所以我們需要定義好我們的演算法規律,比如以下這個陣列,如何合併成新陣列
|
第一次考察
- 首先我們考察合併的陣列為下面箭頭的兩個
- 我們分別定義為座標
i
代表左側陣列座標 - 座標
j
代表右側陣列座標 m
則代表新陣列座標c
則代表中間分隔的位置不然會出現邊界情況
l
則是代表左邊邊界r
則是代表右邊邊界- 此時我們考察中的數字
1
<
4
,則將 歸併到新陣列, 同時m + 1
,i+1
,在繼續考察m
i
j
|
第二次考察
- 接下來繼續考察
i
和j
m
i
j
| - 此時我們考察中的數字
<
, 此時再將 歸併到新陣列,同時m + 1
,i+1
,再繼續考察
第三次考察
- 接下來繼續考察
i
和j
m
i
j
| - 此時我們考察中的數字
>
, 此時再將 歸併到新陣列,同時m + 1
,i+1
,再繼續考察
第四次考察
- 接下來繼續考察
i
和j
m
i
j
| - 此時我們考察中的數字
<
, 此時再將 歸併到新陣列,同時m + 1
,i+1
,再繼續考察然後再持續的以此類推
- 我們就會得到我們想要的排序
ps: 上面陣列沒有
TimAutumnWind (轉載請註明出處 learnku.com/users/48310)
實現一下程式碼
/**
* 歸併排序
* @param array $sort
* @param int $n
* @return array
*/
function get_merge(array $sort,int $n):array
{
get_merge_sort($sort,0,$n - 1);
return $sort;
}
/**
* 遞迴使用歸併排序,對 sort[l...r]的資料進行排序
* @param array $sort
* @param int $l
* @param int $r
* @return array
*/
function get_merge_sort(array &$sort,int $l,int $r)
{
/**
* 當 $l 小於 $r 的時候,當前處理的部分最低有兩個元素,需要進行排序
* 但是當 $l 大於 $r 的時候,我們只需要處理一個元素,或者一個元素都沒有
*/
if( $l >= $r){
/** 此處必須終止 */
return [];
}
/**
* 當 $l + $r 非常大的時候有可能會出現邊界問題
* 之前二分查詢就出現過這個bug
* 本次主要是 歸併排序 所以暫不做處理,之後會有 二分查詢法 的文章
*/
$mid = floor(($l + $r) / 2);
/** 處理左邊 */
get_merge_sort($sort,$l,$mid);
/** 處理右邊 */
get_merge_sort($sort,$mid + 1,$r);
/** 處理合並 */
get_merge_sort_help($sort,$l,$mid,$r);
}
/**
* 將 sort[l...mid] 和 sort[mid+1...r] 兩部分進行合併
* @param array $sort
* @param int $l
* @param int $mid
* @param int $r
*/
function get_merge_sort_help(array &$sort,int $l,int $mid,int $r)
{
$merge = array();
for ($i = $l; $i <= $r;$i++){
$merge[$i-$l] = $sort[$i];
}
$i = $l;
$j = $mid + 1;
for ($m = $l; $m <= $r; $m++){
if($i > $mid){
$sort[$m] = $merge[$j-$l];
$j++;
}else if($j > $r){
$sort[$m] = $merge[$i-$l];
$i++;
}else if($merge[$i-$l] < $merge[$j-$l]){
$sort[$m] = $merge[$i-$l];
$i++;
}else{
$sort[$m] = $merge[$j-$l];
$j++;
}
}
}
大概效能測算
- 本演算法基於
php
中的for
迴圈,可能效能要比while
差一點 - 物理機
Mac 記憶體:16GB / 處理器:2.3 GHz Intel Core i5
- 執行環境為
docker
- 本次測算效能因環境不同和機器情況不同,不具備實際意義,勿槓
- 資料量:
100
大約為1ms - 5ms
- 資料量:
1000
大約為45ms - 50ms
- 資料量:
10000
大約為490ms - 495ms
- 資料量:
100000
大約為5100ms - 5300ms
本作品採用《CC 協議》,轉載必須註明作者和本文連結
TimAutumnWind (轉載請註明出處 https://learnku.com/articles/39442)