详解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 相关文章推荐
Windows下利用Gvim写PHP产生中文乱码问题解决方法
Apr 20 PHP
PHP运行出现Notice : Use of undefined constant 的完美解决方案分享
Mar 05 PHP
php设计模式之命令模式的应用详解
May 21 PHP
PHP常用数组函数介绍
Jul 28 PHP
destoon实现不同会员组公司名称显示不同的颜色的方法
Aug 22 PHP
使用PHP Socket 编程模拟Http post和get请求
Nov 25 PHP
PHP清除字符串中所有无用标签的方法
Dec 01 PHP
PHP中的事务使用实例
May 26 PHP
PHP魔术方法的使用示例
Jun 23 PHP
CodeIgniter记录错误日志的方法全面总结
May 17 PHP
PHP实现数据分页显示的简单实例
May 26 PHP
Ajax+PHP实现的模拟进度条功能示例
Feb 11 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迭代器的内部执行过程详解
2013/11/12 PHP
PHP下载生成的csv文件及问题总结
2015/08/06 PHP
基于Codeigniter框架实现的student信息系统站点动态发布功能详解
2017/03/23 PHP
使用jQuery轻松实现Ajax的实例代码
2010/08/16 Javascript
Jquery下:nth-child(an+b)的使用注意
2011/05/28 Javascript
jqGrid增加时--判断开始日期与结束日期(实例解析)
2013/11/08 Javascript
javascript获取web应用根目录的方法
2014/02/12 Javascript
jQuery之字体大小的设置方法
2014/02/27 Javascript
jquery中animate的stop()方法作用实例分析
2015/01/30 Javascript
jQuery解析XML 详解及方法总结
2016/09/28 Javascript
AngularJS表单提交实例详解
2017/02/18 Javascript
Vue.js实现移动端短信验证码功能
2017/03/29 Javascript
VueJS如何引入css或者less文件的一些坑
2017/04/25 Javascript
socket.io学习教程之基础介绍(一)
2017/04/29 Javascript
js实现鼠标拖拽多选功能示例
2017/08/01 Javascript
Angular4学习教程之DOM属性绑定详解
2018/01/04 Javascript
Vue Socket.io源码解读
2018/02/07 Javascript
JS匿名函数和匿名自执行函数概念与用法分析
2018/03/16 Javascript
vuejs 动态添加input框的实例讲解
2018/08/24 Javascript
基于vue.js组件实现分页效果
2018/12/29 Javascript
[04:15]DOTA2-DPC中国联赛 正赛 Ehome vs Aster 选手采访
2021/03/11 DOTA
python实现从一组颜色中找出与给定颜色最接近颜色的方法
2015/03/19 Python
Python 创建子进程模块subprocess详解
2015/04/08 Python
把项目从Python2.x移植到Python3.x的经验总结
2015/04/20 Python
利用Django内置的认证视图实现用户密码重置功能详解
2017/11/24 Python
python实现二叉查找树实例代码
2018/02/08 Python
python实现信号时域统计特征提取代码
2020/02/26 Python
Django中FilePathField字段的用法
2020/05/21 Python
Python ckeditor富文本编辑器代码实例解析
2020/06/22 Python
python3:excel操作之读取数据并返回字典 + 写入的案例
2020/09/01 Python
CSS3实现莲花绽放的动画效果
2020/11/06 HTML / CSS
乌克兰时尚鞋子和衣服购物网站:Born2be
2018/05/24 全球购物
英国电器零售商:PRC Direct
2018/06/21 全球购物
求职自荐信范文格式
2013/11/29 职场文书
日语专业个人的求职信
2013/12/03 职场文书
2015年元旦标语大全
2014/12/09 职场文书