PHP實現歸併排序的方法

來源:互聯網
上載者:User
本文主要介紹了php歸併排序的實現演算法,即把待排序序列分為若干個有序的子序列,再把有序的子序列合并為整體有序序列。有興趣的朋友可以來瞭解一下。

歸併(Merge)排序法是將兩個(或兩個以上)有序表合并成一個新的有序表。歸併排序的一個缺點是它需要儲存空間有另一個大小等於資料項目數目的數組。如果初始數組幾乎佔滿整個儲存空間,那麼歸併排序將不能工作,但是如果有足夠的空間,歸併排序會是一個很好的選擇。

假設待排序的序列:

4 3 7 9 2 8 6

先說思路,歸併排序的中心思想是將兩個已經排序好的序列,合并成一個排序的序列。

上面的序列可以分成:

4 3 7 9

2 8 6

這兩個序列,然後對這兩個序列分別排序:結果為:

設定為序列A,與序列B,

3 4 7 9
2 6 8

將上面的兩個序列 合并成一個排序好的序列:

合并的具體思路是:

設定兩個位置指標,分別指向序列A與序列B開始的位置:紅色為指標指向位置:

3 4 7 9
2 6 8

比較兩個指標所指向的元素的值,將較小的插入到一個新的數組內,例如序列C,同時將對應的指標向後移動一位:
結果為:

3 4 7 9
2 6 8

形成的序列C:已經被插入一個元素了,剛才較小的 元素2.
2

然後 再次 比較序列A與序列B中指標所指向的元素:將小的放入到序列C中,移動相應指標,結果為:

3 4 7 9
2 6 8
2 3

以此類推,迭代執行,直到序列A或者序列B中某個指標已經移到到數組末端。例如:
多次比較後,序列B已經將指標移出到序列末端(最後一個元素之後)了。
3 4 7 9
2 6 8
2 3 4 6 7 8

然後將沒有用完的序列,這裡面是序列A中其餘的元素全部插入到序列C的後邊即可,就剩下一個9 了,將其插入到序列C後即可:

序列C結果:

2 3 4 5 6 7 8 9

這樣就實現了將兩個有序序列合并成一個有序序列的操作,

下面先看這個合并的php代碼:

/** * 將兩個有序數組合并成一個有序數組 * @param $arrA, * @param $arrB, * @reutrn array合并好的數組 */function mergeArray($arrA, $arrB) {  $a_i = $b_i = 0;//設定兩個起始位置標記  $a_len = count($arrA);  $b_len = count($arrB);  while($a_i<$a_len && $b_i<$b_len) {    //當數組A和數組B都沒有越界時    if($arrA[$a_i] < $arrB[$b_i]) {      $arrC[] = $arrA[$a_i++];    } else {      $arrC[] = $arrB[$b_i++];    }  }  //判斷 數組A內的元素是否都用完了,沒有的話將其全部插入到C數組內:  while($a_i < $a_len) {    $arrC[] = $arrA[$a_i++];  }  //判斷 數組B內的元素是否都用完了,沒有的話將其全部插入到C數組內:  while($b_i < $b_len) {    $arrC[] = $arrB[$b_i++];  }  return $arrC;}

經過上面的分析和程式的實現,我們不難發現,合并已排序的序列的時間應該是線性,就是說,最多會發生N-1次比較,其中N是所有元素之和。

通過上面的描述,我們實現了將兩個排序好的數組進行和並的過程。

此時,大家可能會有疑問,這個和歸併排序整個序列有什麼關係?或者你是如何能夠得到最開始的兩個排序好的子序列的呢?
下面,我們就來描述以下什麼是歸併排序,然後再看,上面的合并與歸併排序的關係是如何的:

大家不妨去想,當我們需要排序如下的數組時,我們是否可以先將數組的前半部分與數組的後半部分分別進行歸併排序,然後將排序的結果合并起來呢?

例如:待排序的數組:
4 3 7 9 2 8 6

先分成2部分:

4 3 7 9
2 8 6

將前半部分 與 後半部分 分別看成一個序列,再次進行歸併(就是拆分,排序,合并)操作
就會變成:

前:
4 3
7 9

後:
2 8
6

同樣 再對每個自序列進行 歸併排序,再次(拆分,排序,合并)。

當拆分的子序列內只存在一個元素(長度為1)時,那麼這個序列就不必再拆分了,就是一個排序好的數組了。然後將這個序列,與其他的序列再合并到一起即可,最終就將所有的都合并好了,成為一個完整的排序好的數組。

程式實現:

通過上面的描述 大家應該想到,可以使用遞迴程式來實現這個程式設計吧:

想要實現這個程式,可能需要解決如下問題:

怎麼將數組做拆分:

設定兩個指標,一個指向數組開始假定為$left,一個指向數組最後一個元素$right:
4 3 7 9 2 8 6

然 後判斷 $left 是否小於$right,如果小於,說明這個序列內元素個數大於一個,就將其拆分成兩個數組,拆分的方式是產生一個中間的指標$center,值 為$left + $right /2 整除。結果為:3,然後將$left 到$center 分成一組,$center+1到$right分成一組:
4 3 7 9
2 8 6
接下來,遞迴的 利用$left, $center, $center+1, $right分別做為 兩個序列的 左右指標,進行操作。知道數組內有一個元素$left==$right .然後按照上面的合并數組即可:

/*** mergeSort 歸併排序* 是開始遞迴函式的一個驅動函數* @param &$arr array 待排序的數組*/function mergeSort(&$arr) {  $len = count($arr);//求得數組長度   mSort($arr, 0, $len-1);}/*** 實際實現歸併排序的程式* @param &$arr array 需要排序的數組* @param $left int 子序列的左下標值* @param $right int 子序列的右下標值*/function mSort(&$arr, $left, $right) {   if($left < $right) {    //說明子序列記憶體在多餘1個的元素,那麼需要拆分,分別排序,合并    //計算拆分的位置,長度/2 去整    $center = floor(($left+$right) / 2);    //遞迴調用對左邊進行再次排序:    mSort($arr, $left, $center);    //遞迴調用對右邊進行再次排序    mSort($arr, $center+1, $right);    //合并排序結果    mergeArray($arr, $left, $center, $right);  }} /*** 將兩個有序數組合并成一個有序數組* @param &$arr, 待排序的所有元素* @param $left, 排序子數組A的開始下標* @param $center, 排序子數組A與排序子數組B的中間下標,也就是數組A的結束下標* @param $right, 排序子數組B的結束下標(開始為$center+1)*/function mergeArray(&$arr, $left, $center, $right) {  //設定兩個起始位置標記  $a_i = $left;  $b_i = $center+1;  while($a_i<=$center && $b_i<=$right) {    //當數組A和數組B都沒有越界時    if($arr[$a_i] < $arr[$b_i]) {      $temp[] = $arr[$a_i++];    } else {      $temp[] = $arr[$b_i++];    }  }  //判斷 數組A內的元素是否都用完了,沒有的話將其全部插入到C數組內:  while($a_i <= $center) {    $temp[] = $arr[$a_i++];  }  //判斷 數組B內的元素是否都用完了,沒有的話將其全部插入到C數組內:  while($b_i <= $right) {    $temp[] = $arr[$b_i++];  }   //將$arrC內排序好的部分,寫入到$arr內:  for($i=0, $len=count($temp); $i<$len; $i++) {    $arr[$left+$i] = $temp[$i];  } } //do some test:$arr = array(4, 7, 6, 3, 9, 5, 8);mergeSort($arr);print_r($arr);

注意上面的代碼帶排序的數組都使用的是 引用傳遞,為了節約空間。

而且,其中的合并數組的方式也為了節約空間做了相對的修改,把所有的操作都放到了$arr上完成,引用傳遞節約資源。

好了 上面的代碼就完成了歸併排序,歸併排序的時間複雜度為O(N*LogN) 效率還是相當客觀的。

再說,歸併排序演算法,中心思想是 將一個複雜問題分解成相似的小問題,再把小問題分解成更小的問題,直到分解到可以馬上求解為止,然後將分解得到的結果再合并起來的一種方法。這個思想用個 成語形容叫化整為零。 放到電腦科學中有個專業屬於叫分治策略(分治發)。分就是大問題變小問題,治就是小結果合并成大結果。

分治策略是很多搞笑演算法的基礎,我們在討論快速排序時,也會用到分治策略的。

最後簡單的說一下這個演算法,雖然這個演算法在時間複雜度上達到了O(NLogN)。但是還是會有一個小問題,就是在合并兩個數組時,如果數組的總元素個數為 N,那麼我們需要再開闢一個同樣大小的空間來儲存合并時的資料(就是mergeArray中的$temp數組),而且還需要將資料有$temp拷貝 會$arr,因此會浪費一些資源。因此在實際的排序中還是 相對的較少使用。

總結:以上就是本篇文的全部內容,希望能對大家的學習有所協助。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.