【算法基础】排序之归并排序 Sort_MergeSort

本文详细介绍了归并排序算法,包括其基于分治法的工作原理,递归过程,以及分割和合并操作。重点阐述了算法的时间复杂度(O(nlogn))、空间复杂度(O(n))和稳定性特点,以及在处理大数据集和外部排序中的应用场景。
摘要由CSDN通过智能技术生成

归并排序(Merge Sort)算法

归并排序是一种高效、稳定的排序算法,广泛应用于计算机科学中。它基于分治法(Divide and Conquer)的原理。以下是归并排序的主要步骤和特点:

基本步骤

分割(Divide):

将原始数组分成若干子数组,最理想的情况是将它们分成只有一个元素的子数组。通常这是通过递归实现的,每次递归将数组分为两半,直到每个子数组只包含一个元素或没有元素。

合并(Conquer):

将分割后的子数组重新合并成一个排序好的数组。合并过程是归并排序的核心,它将两个已排序的子数组合并成一个。
在合并过程中,我们通常需要一个临时数组来存储合并后的元素,以确保在合并时不会覆盖原数组中尚未处理的元素。

详细过程

假设有一个数组 A 需要排序:

递归地将数组分成两半:

1.如果数组有多于一个元素,则将它分为两个子数组。
2.对每个子数组递归地执行相同的分割过程,直到每个子数组只有一个元素或为空。

合并排序后的子数组:

1.在合并的过程中,我们按顺序比较两个子数组的元素,将较小的元素先放入临时数组中。
2.如果一个子数组的所有元素都已经被移动到临时数组中,则将另一个子数组的剩余部分复制过来。
3.最后,将临时数组中排序好的元素复制回原数组相应的位置。

特点和复杂度

时间复杂度:

归并排序在最好、最坏和平均情况下都有 O(n log n) 的时间复杂度,其中 n 是数组中的元素数量。这使得归并排序非常高效,特别是对于大数据集。

空间复杂度:

归并排序不是原地排序算法,因为它需要与原数组相同大小的额外空间来存储临时数组,所以其空间复杂度为 O(n)。

稳定性:

归并排序是稳定的排序算法,这意味着相同的元素在排序后会保持它们原始的顺序。

应用场景:

归并排序特别适用于不适合在内存中一次性处理的大数据集,如外部排序。它也是许多复杂排序算法的基础,例如 Timsort。

public class Solution {
    /**
     * 归并排序的主方法
     * @param A 待排序的整数数组
     */
    public void sortIntegers(int[] A) {
        if (A == null || A.length == 0) {
            return; // 检查数组是否为空或无元素
        }

        int[] temp = new int[A.length]; // 创建一个临时数组用于合并
        mergeSort(A, 0, A.length - 1, temp); // 调用归并排序的递归函数
    }

    /**
     * 递归地对数组进行归并排序
     * @param A 原始数组
     * @param start 开始索引
     * @param end 结束索引
     * @param temp 临时存储数组
     */
    private void mergeSort(int[] A, int start, int end, int[] temp) {
        if (start >= end) {
            return; // 递归结束条件
        }

        int middle = (start + end) / 2; // 找到中间索引
        mergeSort(A, start, middle, temp); // 递归排序左半部分
        mergeSort(A, middle + 1, end, temp); // 递归排序右半部分
        merge(A, start, end, temp); // 合并两个已排序的部分
    }

    /**
     * 将两个已排序的子数组合并成一个排序数组
     * @param A 原始数组
     * @param start 开始索引
     * @param end 结束索引
     * @param temp 临时存储数组
     */
    private void merge(int[] A, int start, int end, int[] temp) {
        int middle = (start + end) / 2;
        int leftIndex = start; // 左子数组的开始索引
        int rightIndex = middle + 1; // 右子数组的开始索引
        int index = start; // 临时数组的索引

        // 合并两个子数组
        while (leftIndex <= middle && rightIndex <= end) {
            if (A[leftIndex] < A[rightIndex]) {
                temp[index++] = A[leftIndex++];
            } else {
                temp[index++] = A[rightIndex++];
            }
        }

        // 将剩余的左子数组元素复制到临时数组
        while (leftIndex <= middle) {
            temp[index++] = A[leftIndex++];
        }

        // 将剩余的右子数组元素复制到临时数组
        while (rightIndex <= end) {
            temp[index++] = A[rightIndex++];
        }

        // 将排序后的临时数组复制回原数组
        for (int i = start; i <= end; i++) {
            A[i] = temp[i];
        }
    }
}

代码讲解

sortIntegers 方法

这是归并排序的主方法,用于对外提供排序功能。它接受一个整数数组 A 作为参数。

参数检查:首先检查数组 A 是否为空或者长度为零。如果是,就直接返回,因为没有需要排序的元素。
创建临时数组: 创建一个与 A 同等长度的临时数组 temp。这个临时数组用于在合并过程中存储合并后的元素。
调用递归排序方法: 调用 mergeSort 方法,传入数组 A、起始索引 0、结束索引 A.length - 1 和临时数组 temp,以开始排序过程。

mergeSort 方法

这是归并排序的递归方法,用于实际执行排序。

递归结束条件: 如果 start 索引大于或等于 end 索引,说明当前处理的子数组不能再分割,方法就会返回。
找到中间索引: 通过 (start + end) / 2 计算出当前子数组的中间索引 middle。
递归排序左半部分: 对数组 A 中从 start 到 middle 的部分进行递归排序。
递归排序右半部分: 对数组 A 中从 middle + 1 到 end 的部分进行递归排序。
合并两个已排序的部分: 调用 merge 方法合并两个已排序的子数组。

merge 方法

这个方法用于合并两个已排序的子数组。

初始化指针: leftIndex 指向左子数组的开始位置,rightIndex 指向右子数组的开始位置,index 用于跟踪临时数组 temp 中的当前位置。
合并过程:当 leftIndex 和 rightIndex 都没有超过各自子数组的界限时,比较两个子数组当前元素的大小,将较小的元素复制到 temp 数组中,并移动相应的索引。如果一个子数组的元素都已经被复制到 temp 中,而另一个子数组还有剩余元素,就将这些剩余元素直接复制到 temp 中。
复制回原数组: 将临时数组 temp 中的元素复制回原数组 A 的相应位置,完成合并。

总结

归并排序通过递归地将数组分成越来越小的子数组,直到每个子数组只包含一个元素或为空,然后再将这些有序的子数组合并成一个完整的有序数组。这种“分而治之”的策略使得归并排序非常有效,尤其是在处理大型数组时。由于需要额外的临时数组来存储合并后的元素,所以归并排序的空间复杂度为 O(n)。

  • 10
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值