归并排序、快速排序

本文介绍了冒泡排序、插入排序和选择排序的基本概念,重点比较了归并排序和快速排序,包括它们的时间复杂度(O(nlogn)),空间复杂度(归并排序需要O(n),快速排序原地),以及稳定性(归并排序稳定,快速排序不稳定)。
摘要由CSDN通过智能技术生成

冒泡排序、插入排序、选择排序这三种排序算法,它们的时间复杂度都是 O(n²),比较高,适合小规模数据的排序。归并排序和快速排序时间复杂度为 O(nlogn),适合大规模的数据排序。

归并排序和快速排序都用到了分治思想。

归并排序

归并排序的原理

如果要排序一个数组,先从中间的位置将数组分割成两个子数组,然后再对这两个子数组从中间位置分割,如此直到分割之后的子数组只有一个元素。然后将最终分割的数组按大小顺序合并到一个新的数组中,如此递归地合并直到所有子数组合并完毕,就可以得到一个有序的数组了。

例子

假设要对下面这组数据利用归并排序方法进行排序:

$arr = [3, 5, 100, 6, 78];

第一次分割成下面两个数组:

$arr1 = [3, 5, 100]; $arr2 = [6, 78];

第二次分割:

// $arr1 划分成
[3] [5, 100]
// $arr2 划分成
[6] [78]

按递归的思想会先处理 $arr1 分割出来的两个数组:

[3] 里面只有一个元素,无需再分割,[5, 100] 还需要再次分割成 [5][100],分割完成就开始合并了,合并的时候,需要创建一个大小等于两个需要合并的数组大小总和的数组 temp,然后将小的元素先放入 temp,如此直到所有元素都放进了 temp 之后,这次合并操作就完成了,就得到了一个有序的数组 temp

所以 $arr1 最终合并得到:

[3, 5, 100]

$arr2 最终合并得到:

[6, 78]

再将 $arr1$arr2 处理的结果合并,也就是将 [3, 5, 100][6, 78] 合并,即可得到:

[3, 5, 6, 78, 100]

PHP 实现

function merge_sort($arr, $p, $r)
{
    if ($p >= $r) return [$arr[$r]];

    $q = intval(($p + $r) / 2);

    $arr1 = merge_sort($arr, $p, $q);
    $arr2 = merge_sort($arr, $q + 1, $r);

    return merge($arr1, $arr2);
}

function merge($arr1, $arr2)
{
    $arr = [];
    $n1 = count($arr1);
    $n2 = count($arr2);

    $k = 0;
    $i = 0;
    $j = 0;
    while ($i < $n1 && $j < $n2) {
        if ($arr1[$i] <= $arr2[$j]) {
            $arr[$k++] = $arr1[$i++];
        } else {
            $arr[$k++] = $arr2[$j++];
        }
    }

    while ($i < $n1) {
        $arr[$k++] = $arr1[$i++];
    }

    while ($j < $n2) {
        $arr[$k++] = $arr2[$j++];
    }

    return $arr;
}

算法分析

  1. 归并排序是否稳定?

merge 实现里面对相等的元素,我们可以将 $arr1 的元素先合并到 $arr,这样即可实现排序算法的稳定性。

  1. 时间复杂度是多少?

归并排序的排序过程中不会受原始数据的有序度影响,都是固定的不断拆分成两个数组,直到最终拆分到只有一个元素的时候进行合并,小的在前面,大的在后面。也就是说,归并排序的时间复杂度是固定的 O(nlogn),非常稳定。

  1. 空间复杂度是多少?

在将原始数据一分为二的时候,我们不需要开辟额外的空间来存储数据,但是在合并的过程中,我们需要开辟一个临时数组来存储排序后的数据,然后将这个数组覆盖掉原始数组的那一段范围的数据。

但由于某一时刻 CPU 中最多只有一个合并的操作在进行,所以最大的空间复杂度为 O(n)

快速排序

快速排序原理

快速排序的过程中,我们会先从原始数组中随机选一个数 pivot,以这个数为中点,将剩余的其他 n-1 个数划分为两个部分,小于 pivot 的交换到 pivot 的左边,大于 pivot 的交换到 pivot 的右边。然后再对左边和右边两部分的子数组递归进行此操作,直到最后只有一个元素为止,这个时候我们的整个数组就排序完毕了。

例子

利用快速排序对以下数组进行排序:

$arr = [5, 1, 7, 8, 6];
  1. 选择最后一个数(6)作为中点,小于 6 的交换到 $arr 的左边,中间为 6,大于 6 的交换到 $arr 的右边。

  2. 经过上一步的交换后,$arr[5, 1, 6, 8, 7],这个时候 6 其实就是最终排序完的位置了。

  3. 再对 6 左边和右边的子数组 [5, 1][8, 7] 执行第一步操作,如此直到子数组只有一个元素,结束排序。

我们可以发现,快速排序的过程,其实就是一次次地确定最终排序数组中间元素的过程。

PHP 实现

function quick_sort(&$arr, $p, $r)
{
    if ($p >= $r) return;

    $q = partition($arr, $p, $r);

    quick_sort($arr, $p, $q - 1);
    quick_sort($arr, $q + 1, $r);
}

$array = [1, 5, 3, 7, 9, 4, 20, 100, 55, 67, 66];

// 分区+排序
function partition(&$arr, $p, $r)
{
    // 返回分区点
    $pivot = $arr[$r];

    $i = $p;
    for ($j = $p; $j < $r - 1; $j++) { // $r - 1 的值为 pivot,中间值
        if ($arr[$j] < $pivot) {
            $temp = $arr[$i];
            $arr[$i] = $arr[$j];
            $arr[$j] = $temp;
            $i++;
        }
    }

    // $i 永远不大于 $j,$i <= $j
    // $i == $j 的情况下不交换位置
    // 情况一:只有一个数,不影响
    // 情况二:数组里面全部都是小于 $pivot 的元素,这种情况不需要再互换了
    if ($i < $j) {
        $arr[$r] = $arr[$i];
        $arr[$i] = $pivot;
    }

    return $i;
}

quick_sort($array, 0, count($array) - 1);
var_export($array);

算法分析

  1. 快速排序是否是稳定的排序算法?

由于快速排序的过程中,会发生元素前后的交换,所以不能保证稳定性,所以是不稳定的排序算法。

  1. 快速排序的时间复杂度是多少?

平均时间复杂度是 O(nlogn),但可能比这个大

  1. 快速排序的空间复杂度是多少?

快速排序是原地排序算法,排序过程不需要开辟新的空间来处理。

归并排序和快速排序对比

  1. 归并排序是稳定的排序算法,快速排序不是稳定的排序算法

  2. 归并排序的过程需要开辟临时的空间来进行合并操作,快速排序是原地排序算法,归并排序的空间复杂度太高

  3. 归并排序的时间复杂度是固定的,快速排序的时间复杂度会受实际数据有序度影响

  4. 实际使用中,快速排序用得更多,因为是原地排序算法,但是不能保证排序结果的稳定性

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

张无忌打怪兽

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值