基础算法-归并排序,快速排序

本文图片来在极客时间-王争老师的《数据结构与算法之美》

本文源码地址>>>GitHub

归并排序(MergeSort)

算法说明

先把数组从中间分成前后两部分,然后对前后两部分分别排序,再将排好序的两部分合并在一起,这样整个数组就都有序了。

例如要对一组元素11 8 3 9 7 1 2 5进行归并排序,步骤如下:
归并排序分解图

步骤说明

递推公式:
merge_sort(p…r) = merge(merge_sort(p…q), merge_sort(q+1…r))

终止条件:
p >= r 不用再继续分解

JAVA代码

package cn.hgy.data.algorithm.lesson2;

/**
 * 归并排序
 * <pre>
 *     算法介绍:
 *     1. 先把数组从中间分成前后两部分{@link #sort(int[], int, int)}
 *     2. 对前后两部分分别排序,再将排好序的两部分合并在一起{@link #merge(int[], int, int, int)}}
 *     3. 当数组只有1个元素时,表示该数组已经排好序
 * </pre>
 *
 * @author guoyu.huang
 * @version 1.0.0
 */
public class MergeSort {

    public static void main(String[] args) {
        int[] arrays = {11, 8, 3, 9, 7, 1, 2, 5};
        sort(arrays, 0, arrays.length - 1);
        for (int i = 0; i < arrays.length; i++) {
            System.out.print(arrays[i] + " ");
        }
    }

    /**
     * 排序,适用递归方式来实现
     *
     * @param content
     * @param p
     * @param r
     */
    public static void sort(int[] content, int p, int r) {
        // 递归停止条件: p>=r,表示这时候元素集合最小单位是2个
        if (p < r) {
            int mid = (p + r) / 2;
            sort(content, p, mid);
            sort(content, mid + 1, r);

            // 开始合并元素
            merge(content, p, mid, r);
        }
    }

    /**
     * 合并元素,保证每个元素集合是有序的
     *
     * @param content
     * @param p
     * @param mid
     * @param r
     */
    public static void merge(int[] content, int p, int mid, int r) {
        // 因为归并排序合并时,是两个元素集合进行合并,所以需要两个指针下标
        // p2指针为第二个元素的下标,所以是mid+1
        // 例如元素集合(11 8),那么第一个元素集合下标就是0,第二个就是1,以此类推
        int p1 = p, p2 = mid + 1;
        // 申请一个临时数组,用于暂存有序集合
        int[] temp = new int[r - p + 1];
        int i = 0;
        while (p1 <= mid && p2 <= r) {
            if (content[p1] < content[p2]) {
                temp[i++] = content[p1++];
            } else {
                temp[i++] = content[p2++];
            }
        }

        // 当存在其中一个元素集合为空的时候,直接将另一个元素集合的元素追加在尾部
        while (p1 <= mid) {
            temp[i++] = content[p1++];
        }

        while (p2 <= r) {
            temp[i++] = content[p2++];
        }

        // 将有序部分进行替换
        for (int j = p; j <= r; j++) {
            content[j] = temp[j - p];
        }
    }
}

快速排序(QuickSort)

算法说明

如果要排序数组中下标从 p 到 r 之间的一组数据,我们选择 p 到 r 之间的任意一个数据作为 pivot(分区点)。

我们遍历 p 到 r 之间的数据,将小于 pivot 的放到左边,将大于 pivot 的放到右边,将 pivot 放到中间。经过这一步骤之后,数组 p 到 r 之间的数据就被分成了三个部分,前面 p 到 q-1 之间都是小于 pivot 的,中间是 pivot,后面的 q+1 到 r 之间是大于 pivot 的。

在这里插入图片描述

优化方案-原地排序
在这里插入图片描述

步骤说明

递推公式:

quick_sort(p…r) = quick_sort(p…q-1) + quick_sort(q+1… r)

终止条件:

p >= r

JAVA代码

package cn.hgy.data.algorithm.lesson2;

/**
 * 快速排序
 * <pre>
 *     算法介绍:
 *     1. 取一个中心点,将数组进行分区{@link #partition(int[], int, int)},并返回中心点位置,数组为小于中心点,中心点,大于等于中心点
 *     2. 再分别对小于中心点,中心点和中心点,大于等于中心点进行分区{@link #sort(int[], int, int)}
 *     3. 当数组只有1个元素时,表示该数组已经排好序
 * </pre>
 *
 * @author guoyu.huang
 * @version 1.0.0
 */
public class QuickSort {

    public static void main(String[] args) {
        int[] arrays = {11, 8, 3, 9, 7, 1, 2, 5};
        sort(arrays, 0, arrays.length - 1);
        for (int i = 0; i < arrays.length; i++) {
            System.out.print(arrays[i] + " ");
        }
    }

    public static void sort(int[] content, int p, int r) {
        // 递归停止条件:p>=r,表示这时候元素集合最小单位是2个
        if (p < r) {
            // 开始分区排序
            int mid = partition(content, p, r);

            sort(content, p, mid - 1);
            sort(content, mid + 1, r);
        }
    }

    /**
     * 分区,取分区中的最后一个元素作为pivot,将元素集合分为三个部分:小于pivot,pivot, 大于pivot
     *
     * @param content
     * @param p
     * @param r
     * @return 返回pivot的位置
     */
    public static int partition(int[] content, int p, int r) {
        int pivot = content[r];
        int i = p, j = p;
        for (; i < r; i++) {
            // 如果当前元素小于pivot,则需要将当前元素和j指向的元素交换位置
            if (content[i] < pivot) {
                int tmp = content[i];
                content[i] = content[j];
                content[j] = tmp;
                j++;
            }
        }
        // 最后交换pivot和j下标的位置
        content[r] = content[j];
        content[j] = pivot;

        return j;
    }

}

总结

分析维度一:时间复杂度

  • 归并排序

我们假设对 n 个元素进行归并排序需要的时间是 T(n),那分解成两个子数组排序的时间都是 T(n/2)。我们知道,merge() 函数合并两个有序子数组的时间复杂度是 O(n)。所以,套用前面的公式,归并排序的时间复杂度的计算公式就是:

T(1) = C;   n=1时,只需要常量级的执行时间,所以表示为C。
T(n) = 2*T(n/2) + n; n>1

再进一步分解一下计算过程

T(n) = 2*T(n/2) + n
     = 2*(2*T(n/4) + n/2) + n = 4*T(n/4) + 2*n
     = 4*(2*T(n/8) + n/4) + 2*n = 8*T(n/8) + 3*n
     = 8*(2*T(n/16) + n/8) + 3*n = 16*T(n/16) + 4*n
     ......
     = 2^k * T(n/2^k) + k * n
     ......

当 T(n/2^k)=T(1) 时,也就是 n/2^k=1,我们得到 k=log2n 。我们将 k 值代入上面的公式,得到 T(n)=Cn+nlog2n 。如果我们用大 O 标记法来表示的话,T(n) 就等于 O(nlogn)。所以归并排序的时间复杂度是 O(nlogn)。

  • 快速排序

大部分情况时间复杂度为O(nlogn),当中心值为元素集合中的极值时,时间复杂度会为O(n^2)

分析维度二:空间复杂度

  • 归并排序:O(n)
  • 快速排序:O(1)

分析维度三:是否稳定

  • 归并排序:稳定
  • 快速排序:不稳定

关注我

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

瑾析编程

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

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

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

打赏作者

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

抵扣说明:

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

余额充值