本文主要介紹了php歸併排序的實作演算法,也就是把待排序序列分成若干個有序的子序列,再把有序的子序列合併為整體有序序列。有興趣的朋友可以來了解一下。
歸併(Merge)排序法是將兩個(或兩個以上)有序表合併成一個新的有序表。歸併排序的一個缺點是它需要記憶體有另一個大小等於資料項數目的陣列。如果初始陣列幾乎佔滿整個記憶體,那麼歸併排序將無法運作,但是如果有足夠的空間,歸併排序會是一個很好的選擇。
假設待排序的序列:
4 3 7 9 2 8 6
先說思路,歸併排序的中心思想是將兩個已經排序好的序列,合併成一個已排序的序列。
上面的序列可以分成:
4 3 7 9
和
2 8 6
2 6 8
##設定兩個位置指示器,分別指向序列A與序列B開始的位置:紅色為指示器指向位置:
4 7 9#2
6 8
比較兩個指標所指向的元素的值,將較小的插入到一個新的陣列內,例如序列C,同時將對應的指示器向後移動一位:
4 7 92 6
8
形成的序列C:已經被插入一個元素了,剛才較小的元素2.
#然後再次比較序列A與序列B中指示器所指向的元素:將小的放入到序列C中,移動對應指針,結果為:
3
7 92 6
# 82 3
以此類推,迭代執行,直到序列A或序列B中某個指示器已經移到陣列末端。例如:
3 4 7
9
2 6 8
2 3 4 6 7 8
然後沒有用完的序列,這裡面是序列A中其餘的元素全部插入到序列C的後邊即可,就剩下一個9 了,將其插入到序列C後即可:
##序列C結果:
這樣就實現了將兩個有序序列合併成一個有序序列的操作,
下面先看這個合併的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部分:
2 8 6
將前半部與後半部分別看成一個序列,再次進行歸併(就是拆分,排序,合併)操作
前:
4 3
#後:
2 8 6
#同樣 再對每個自序列進行歸併排序,再次(拆分,排序,合併)。
當拆分的子序列內只存在一個元素(長度為1)時,那麼這個序列就不必再拆分了,就是一個排序好的陣列了。然後將這個序列,與其他的序列再合併到一起即可,最終就將所有的都合併好了,成為一個完整的排序好的數組。
程式實作:
透過上面的描述大家應該想到,可以用遞迴程式來實現這個程式設計:
想要實作這個程序,可能需要解決如下問題:怎麼將陣列做拆分:
然 后判断 $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,因此会浪费一些资源。因此在实际的排序中还是 相对的较少使用。
总结:以上就是本篇文的全部内容,希望能对大家的学习有所帮助。
相关推荐:
以上是PHP實作歸併排序的方法的詳細內容。更多資訊請關注PHP中文網其他相關文章!