数据结构与算法之PHP排序算法(归并排序)

一、基本思想
归并排序算法是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,使每个子序列有序,再将已有序的子序列合并,得到完全有序的序列。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。
 
二、算法过程
归并主要做两件事:
1)“分解”——将序列每次折半划分。
2)“合并”——将划分后的序列段两两合并后排序。
 
三、算法图解及PHP代码实现
1、递归——自顶向下
递归过程是将待排序数组一分为二,直至排序数组就剩下一个元素为止,然后不断的合并两个排好序的数组
其中,合并过程如下(以第2趟归并为例):
PHP代码如下:
复制代码
<?php
// 递归
function mergeSort(&$arr, $left, $right) {
    if ($left < $right) {
        // 找出中间索引
        $mid = floor(($left + $right) / 2);
        // 对左边数组进行递归
        mergeSort($arr, $left, $mid);
        // 对右边数组进行递归
        mergeSort($arr, $mid + 1, $right);
        // 合并
        merge($arr, $left, $mid, $right);
    }
}
 
// 将两个有序数组合并成一个有序数组
function merge(&$arr, $left, $mid, $right) {
    $i = $left;     // 左数组的下标
    $j = $mid + 1;  // 右数组的下标
    $temp = array();// 临时合并数组
    // 扫描第一段和第二段序列,直到有一个扫描结束
    while ($i <= $mid && $j <= $right) {
        // 判断第一段和第二段取出的数哪个更小,将其存入合并序列,并继续向下扫描
        if ($arr[$i] < $arr[$j]) {
            $temp[] = $arr[$i];
            $i++;
        } else {
            $temp[] = $arr[$j];
            $j++;
        }
    }
    // 比完之后,假如左数组仍有剩余,则直接全部复制到 temp 数组
    while ($i <= $mid) {
        $temp[] = $arr[$i];
        $i++;
    }
    // 比完之后,假如右数组仍有剩余,则直接全部复制到 temp 数组
    while ($j <= $right) {
        $temp[] = $arr[$j];
        $j++;
    }
    // 将合并序列复制到原始序列中
    for($k = 0; $k < count($temp); $k++) {
        $arr[$left + $k] = $temp[$k];
    }
}
 
// 测试
$arr = [85, 24, 63, 45, 17, 31, 96];
mergeSort($arr, 0, count($arr) - 1);
print_r($arr);
复制代码
2、非递归——自底向上
非递归过程是将待排序数组看成n个长度为1的子序列,数组中的相邻元素两两配对,将他它们排序后,构成n/2组长度为2的排序好的子数组段,然后再将他们排序成长度为4的子数组段,如此继续下去,直至整个数组排好序。
不管递归还是非递归,其合并的逻辑都是一样的。
 
PHP代码如下:
复制代码
<?php
//非递归
function mSort(&$arr) {
    $len = count($arr);
    $size = 1;
    while ($size <= $len - 1) {
        // 从第一个元素开始扫描,left 代表第一个分割的序列的第一个元素
        $left = 0;
        while ($left + $size <= $len - 1) {
            // mid 代表第一个分割的序列的最后一个元素
            $mid = $left + $size - 1;
            //right 代表第二个分割的序列的最后一个元素
            $right = $mid + $size;
            if ($right > $len - 1) { // 如果第二个序列个数不足size个
                //调整 right 为最后一个元素的下标即可
                $right = $len - 1;
            }
            // 调用归并函数,进行分割的序列的分段排序
            merge($arr, $left, $mid, $right);
            $left = $right + 1;
        }
        $size *= 2; // 范围扩大一倍
    }
}
 
// 将两个有序数组合并成一个有序数组
function merge(&$arr, $left, $mid, $right) {
    $i = $left;     // 左数组的下标
    $j = $mid + 1;  // 右数组的下标
    $temp = array();// 临时合并数组
    // 扫描第一段和第二段序列,直到有一个扫描结束
    while ($i <= $mid && $j <= $right) {
        // 判断第一段和第二段取出的数哪个更小,将其存入合并序列,并继续向下扫描
        if ($arr[$i] < $arr[$j]) {
            $temp[] = $arr[$i];
            $i++;
        } else {
            $temp[] = $arr[$j];
            $j++;
        }
    }
    // 比完之后,假如左数组仍有剩余,则直接全部复制到 temp 数组
    while ($i <= $mid) {
        $temp[] = $arr[$i];
        $i++;
    }
    // 比完之后,假如右数组仍有剩余,则直接全部复制到 temp 数组
    while ($j <= $right) {
        $temp[] = $arr[$j];
        $j++;
    }
    // 将合并序列复制到原始序列中
    /*for($k = 0; $k < count($temp); $k++) {
        $arr[$left + $k] = $temp[$k];
    }*/
    for ($i = $left, $k = 0; $i <= $right; $i++, $k++) {
        $arr[$i] = $temp[$k];
    }
}
 
// 测试
$arr = [85, 24, 63, 45, 17, 31, 96, 1];
mSort($arr);
print_r($arr);
复制代码

 

四、效率分析

1、时间复杂度:O(nlogn)
最好情况、最坏情况和平均时间复杂度均为O(nlogn);
2、空间复杂度:O(n)
算法处理过程中,需要一个大小为 n 的临时存储空间保存合并序列,所以空间复杂度为O(n)。
3、稳定性:稳定
在归并排序中,相等的元素的顺序不会改变,所以它是稳定排序。
posted @ 2019-07-04 11:10  zbs666  阅读(123)  评论(0编辑  收藏  举报