详解PHP归并排序的实现


Posted in PHP onOctober 18, 2016

归并(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
6  8

形成的序列C:已经被插入一个元素了,刚才较小的 元素2.
2

然后 再次 比较序列A与序列B中指示器所指向的元素:将小的放入到序列C中,移动相应指针,结果为:

3 4 7 9
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,因此会浪费一些资源。因此在实际的排序中还是 相对的较少使用。

PHP 相关文章推荐
一个更简单的无限级分类菜单代码
Jan 16 PHP
使用apache模块rewrite_module (转)
Feb 14 PHP
php中防止恶意刷新页面的代码小结
Oct 31 PHP
深入探讨<br />和 \r\n两者有什么区别??
Jun 05 PHP
在PHP上显示JFreechart画的统计图方法
Nov 03 PHP
php将字符串转换成16进制的方法
Mar 17 PHP
PHP微信红包API接口
Dec 05 PHP
thinkphp框架下实现登录、注册、找回密码功能
Apr 06 PHP
form表单传递数组数据、php脚本接收的实例
Feb 09 PHP
php简单生成一组与多组随机字符串的方法
May 09 PHP
Laravel如何使用Redis共享Session
Feb 23 PHP
PHP+MySQL实现模糊查询员工信息功能示例
Jun 01 PHP
Yii框架中sphinx索引配置方法解析
Oct 18 #PHP
Yii框架结合sphinx,Ajax实现搜索分页功能示例
Oct 18 #PHP
php文件上传、下载和删除示例
Aug 28 #PHP
Yii框架防止sql注入,xss攻击与csrf攻击的方法
Oct 18 #PHP
php中array_slice和array_splice函数解析
Oct 18 #PHP
Yii框架中jquery表单验证插件用法示例
Oct 18 #PHP
Yii框架实现邮箱激活的方法【数字签名】
Oct 18 #PHP
You might like
PHP防注入安全代码
2008/04/09 PHP
phpQuery占用内存过多的处理方法
2013/11/13 PHP
PHP中ID设置自增后不连续的原因分析及解决办法
2016/08/21 PHP
Laravel框架实现超简单的分页效果示例
2019/02/08 PHP
pjblog中的UBBCode.js
2007/04/25 Javascript
js 禁用只读文本框获得焦点时的退格键
2010/04/25 Javascript
JQuery-tableDnD 拖拽的基本使用介绍
2013/07/04 Javascript
Jquery在指定DIV加载HTML示例代码
2014/02/17 Javascript
Javascript中Array.prototype.map()详解
2014/10/22 Javascript
jQuery实现列表的全选功能
2015/03/18 Javascript
jQuery自定义滚动条完整实例
2016/01/08 Javascript
JavaScript基础知识之方法汇总结
2016/01/24 Javascript
IScroll5 中文API参数说明和调用方法
2016/05/21 Javascript
AngularJS ng-style中使用filter
2016/09/21 Javascript
Vue.js基础知识小结
2017/01/13 Javascript
基于JSON数据格式详解
2017/08/31 Javascript
JS开发中基本数据类型具体有哪几种
2017/10/19 Javascript
从vue基础开始创建一个简单的增删改查的实例代码(推荐)
2018/02/11 Javascript
在小程序中使用canvas的方法示例
2018/09/17 Javascript
webpack + vue 打包生成公共配置文件(域名) 方便动态修改
2019/08/29 Javascript
[07:39]第一届亚洲邀请赛回顾视频
2017/02/14 DOTA
深入分析python中整型不会溢出问题
2018/06/18 Python
Django基础知识与基本应用入门教程
2018/07/20 Python
CentOS 7下安装Python3.6 及遇到的问题小结
2018/11/08 Python
python matplotlib画图库学习绘制常用的图
2019/03/19 Python
Python一行代码实现快速排序的方法
2019/04/30 Python
Python模块、包(Package)概念与用法分析
2019/05/31 Python
Python 堆叠柱状图绘制方法
2019/07/29 Python
python+opencv边缘提取与各函数参数解析
2020/03/09 Python
python实现批处理文件
2020/07/28 Python
跑鞋、网球鞋、网球拍、服装及装备:Holabird Sports
2016/09/19 全球购物
师德师风整改措施
2014/10/24 职场文书
大学生党课感想
2015/08/11 职场文书
严以律己学习心得体会
2016/01/13 职场文书
十大最强岩石系宝可梦,怪颚龙实力最强,第七破坏力很强
2022/03/18 日漫
Java使用HttpClient实现文件下载
2022/08/14 Java/Android