归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。
首先考虑下如何将将二个有序数列合并。这个非常简单,只要从比较二个数列的第一个数,谁小就先取谁,取了后就在对应数列中删除这个数。然后再进行比较,如果有数列为空,那直接将另一个数列的数据依次取出即可。
<?php
function mergearray($a,$b){
$a_point = 0;//可以理解为指向a的第一个位置
$b_point = 0;
$a_length = count($a);//a的长度
$b_length = count($b);
$tmp = array();//一个临时数组来合并这两个数组
$t_point = 0;
while($a_point < $a_length && $b_point < $b_length){
if($a[$a_point] < $b[$b_point]){
$tmp[$t_point++] = $a[$a_point++];
}else{
$tmp[$t_point++] = $b[$b_point++];
}
}
//哪一个有序列还有剩余,直接追加到tmp数组后面
while($a_point < $a_length){
$tmp[$t_point++] = $a[$a_point++];
}
while($b_point < $b_length){
$tmp[$t_point++] = $b[$b_point++];
}
return $tmp;
}
$a = array(12,36,43,44,59,62);
$b = array(7,35,52,85,94);
$merge = mergearray($a,$b);
var_dump($merge)
?>
再换一个问题,如果有序列b追加到有序列a的后面,变成只有一个序列$a = array(12,36,43,44,59,62,7,35,52,85,94);
那怎么进行归并排序?
我们仍可以用下标来将它分成两个有序列,第一个有序列从下标$a[0]
到$a[5]
,第二个有序列从$a[6]
到$a[10]
所以我们需要找到分成这两个有序列的中点的下边标,即下标5或者是下标6.
先不急,先假设这个中点下标是已知的:
<?php
function mergearray($a,$first,$mid,$last){
$a_point = $first;//指向有序列的第一个下标
$a_end = $mid;//该有序列的终点下标
$b_point = $mid+1;
$b_end = $last;
$tmp = array();//一个临时数组来合并这两个数组
$t_point = 0;
while($a_point <= $a_end && $b_point <= $b_end){//注意判断条件的等于号
if($a[$a_point] < $a[$b_point]){
$tmp[$t_point++] = $a[$a_point++];
}else{
$tmp[$t_point++] = $a[$b_point++];
}
}
while($a_point <= $a_end){
$tmp[$t_point++] = $a[$a_point++];
}
while($b_point <= $b_end){
$tmp[$t_point++] = $a[$b_point++];
}
return $tmp;
}
$a = array(12,36,43,44,59,62,7,35,52,85,94);
$merge = mergearray($a,0,5,count($a)-1);
var_dump($merge)
?>
?>
到目前为止,所做的操作只是归并中的并操作(合并),那什么归操作呢
归操作就是递归地把一个序列分成两组,再将每一组再分成两组,依次类推,当分出来的小组只有一个数据时,可以认为这个小组组内已经达到了有序,然后再合并相邻的二个小组就可以了。这样通过先递归的分解数列,再合并数列就完成了归并排序。
php实现:
<?php
function mergearray(&$arr,&$tmp,$first,$mid,$last){
$first_start = $first;
$first_end = $mid;
$last_start = $mid+1;
$last_end = $last;
$t = $first;
while($first_start<=$first_end&&$last_start<=$last_end){
if($arr[$first_start] < $arr[$last_start]){
$tmp[$t++] = $arr[$first_start++];
}else{
$tmp[$t++] = $arr[$last_start++];
}
}
while($first_start<=$first_end){
$tmp[$t++] = $arr[$first_start++];
}
while($last_start <= $last_end){
$tmp[$t++] = $arr[$last_start++];
}
for($i = $first; $i <= $last; $i++){
$arr[$i] = $tmp[$i];
}
}
function mergesort(&$arr,&$tmp,$first,$last){
if($first < $last){
$mid = (int)(($first+$last)/2);
mergesort($arr,$tmp,$first,$mid);
mergesort($arr,$tmp,$mid+1,$last);
mergearray($arr,$tmp,$first,$mid,$last);
}
}
$arr = array(44,12,59,36,62,43,94,7,35,52,85);
$tmp = array();
mergesort($arr,$tmp,0,count($arr)-1);
unset($tmp);//释放tmp
var_dump($arr);
归并排序的效率是比较高的,设数列长为N,将数列分开成小数列一共要logN步,每步都是一个合并有序数列的过程,时间复杂度可以记为O(N),故一共为O(N*logN)。因为归并排序每次都是在相邻的数据中进行操作,所以归并排序在O(N*logN)的几种排序方法(快速排序,归并排序,希尔排序,堆排序)也是效率比较高的。
参考资料:
白话经典算法系列之五 归并排序的实现