归并排序(Merge Sort)

什么是归并排序

归并排序(Merge Sort)是一种经典的排序算法,它采用分治法(Divide and Conquer)策略,将一个大数组分为两个小数组,分别进行排序,然后将这两个已排序的小数组合并成一个有序的大数组。归并排序在最坏情况下的时间复杂度为 O(nlog⁡n)O(nlogn),并且是一种稳定的排序算法。

核心思想

归并排序的核心思想可以分为以下几个步骤:

  1. 分解(Divide):将待排序的数组分为两个子数组,直到每个子数组只包含一个元素。因为一个元素的数组是有序的。
  2. 解决(Conquer):递归地对这两个子数组进行归并排序。
  3. 合并(Merge):将两个已排序的子数组合并成一个有序的数组。

快速排序与归并排序的比较

快速排序和归并排序都是经典的排序算法,均采用分治法(Divide and Conquer)的思想。尽管它们在基本原理上相似,但在实现细节、性能特征及适用场景上存在显著差异。

1. 基本思路

  • 快速排序
    • 选择一个基准元素(Pivot),通常是数组的第一个元素或随机选择。
    • 将数组分为两个部分:左侧部分包含小于基准的元素,右侧部分包含大于基准的元素。
    • 递归地对这两个部分进行快速排序,直到整个数组有序。
  • 归并排序
    • 将数组递归分为两个子数组,直到每个子数组只包含一个元素。
    • 然后将这两个已排序的子数组合并成一个有序的数组。
    • 合并过程是关键,它需要额外的空间来存放临时数组。

2. 稳定性

  • 快速排序:不稳定排序。在排序过程中,相等元素的相对顺序可能会被改变。
  • 归并排序:稳定排序。相等元素的相对顺序在排序后保持不变,这在某些应用中非常重要。

示例一

题目描述

代码实现

java 复制代码
import java.util.Scanner;

public class Main {
    final static int N = 1000000;
    private static int[] temp = new int[N];
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        int arr[] = new int[n];

        for (int i = 0; i < n; i++) {
            arr[i] = scanner.nextInt();
        }
        mergeSort(arr, 0, n - 1);
        for (int i = 0; i < n; i++) {
            System.out.print(arr[i] + " ");
        }
    }
    public static void mergeSort(int[] arr, int l, int r) {
        if (l >= r) return;
        int mid = (l + r) >> 1;

        mergeSort(arr, l, mid);
        mergeSort(arr, mid + 1, r);

        int k = 0, i = l, j = mid + 1;
        while (i <= mid && j <= r) {
            if (arr[i] <= arr[j])
                temp[k++] = arr[i++];
            else
                temp[k++] = arr[j++];
        }
        //将剩余元素复制到temp数组中
        while (i <= mid) temp[k++] = arr[i++];
        while (j <= r) temp[k++] = arr[j++];

        //将temp数组中的元素复制回arr数组
        for (int t = l; t <= r; t++)
            arr[t] = temp[t - l];
    }
}

示例二

题目描述

求逆序对的数量

代码实现

java 复制代码
import java.util.Scanner;

public class Main {
    final static int N = 1000000;
    private static int[] temp = new int[N];
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        int arr[] = new int[n];

        for (int i = 0; i < n; i++) {
            arr[i] = scanner.nextInt();
        }
        scanner.close();
        System.out.println(mergeSort(arr, 0, n - 1));

    }
    public static long mergeSort(int[] arr, int l, int r) {
        if (l >= r) return 0;
        int mid = (l + r) >> 1;

        long res =  mergeSort(arr, l, mid)+mergeSort(arr, mid + 1, r);

        int k = 0, i = l, j = mid + 1;
        while (i <= mid && j <= r) {
            if (arr[i] <= arr[j])
                temp[k++] = arr[i++];
            else{
                res += (mid - i + 1);
                temp[k++] = arr[j++];
            }

        }
        while (i <= mid)
            temp[k++] = arr[i++];
        while (j <= r)
            temp[k++] = arr[j++];
        for(i=l,j=0;i<=r;i++,j++)
            arr[i]=temp[j];
        return res;
    }
}

逆序对数量算法思路与实现

求逆序对的算法是利用了归并排序的思想,在归并排序的过程中会将序列分为两部分,此时逆序对可以分为三种情况:两个数都在左边的(设为s1)。两个数都在右边的(s2),一个数在左边一个数在右边的(s3)。现在假设我们在归并排序的时候写的函数mergeSort(int[] arr, int l, int r)可以返回l到r区间中逆序对数量。那么s1=mergeSort(a, l, mid),s2=mergeSort(a, mid + 1, r);,s3很显然没有直观的答案。

那么核心问题就在于怎么求s3,以及怎么使我们的mergeSort(int[] arr, int l, int r)可以返回l到r区间中逆序对数量。

在归并排序中,左右两边都是排序好的数列。因此,对于右图中的B我们只需要找到左边第一个大于B的数A,那么A后面的数都是大于B的。假设A的下标为i,不难得到我们要统计的数目为:**mid-i+1,**else中的语句即代表左边数列中的数大于右边中的数了,我们可以在此时将mid-i+1加到总答案中去。那么s3的问题就解决了。经过递归之后,res即我们所求。

时间复杂度

  • 最佳情况:O(nlog⁡n)O(nlogn)
  • 平均情况:O(nlog⁡n)O(nlogn)
  • 最坏情况:O(nlog⁡n)O(nlogn)

归并排序在所有情况下的时间复杂度都是 O(nlog⁡n)O(nlogn),这使得它在处理大规模数据时非常高效。

空间复杂度

归并排序的空间复杂度为 O(n)O(n),因为在合并过程中需要额外的存储空间来存放临时数组。

优点

  • 稳定排序,能够保持相等元素的相对顺序。
  • 在处理大规模数据时,性能稳定。

缺点

  • 需要额外的存储空间,不适合内存受限的环境。

应用场景

归并排序广泛用于需要稳定排序的场合,如外部排序(处理无法完全放入内存的数据集)和链表排序。由于其稳定性和性能,归并排序在许多编程语言的标准库中被实现为排序算法。

总结

归并排序是一种高效且稳定的排序算法,适用于大规模数据的排序。通过分治法的思想,归并排序能够在最坏情况下保持 O(nlog⁡n)O(nlogn) 的时间复杂度。虽然它需要额外的空间,但在许多实际应用中,归并排序仍然是一种非常有用的算法。

相关推荐
vx-程序开发3 小时前
springboot在线装修管理系统-计算机毕业设计源码56278
java·c语言·spring boot·python·spring·django·php
大傻^4 小时前
Spring AI Alibaba 可观测性实践:AI应用监控与链路追踪
java·人工智能·后端·spring·springaialibaba
云烟成雨TD4 小时前
Spring AI Alibaba 1.x 系列【1】阿里巴巴 AI 生态
java·人工智能·spring
佑白雪乐4 小时前
LCR 175. 计算二叉树的深度
算法·深度优先
诗人不写诗4 小时前
spring是如何组织切面的
java·后端·spring
阿Y加油吧4 小时前
力扣打卡day07——最大子数组和、合并区间
算法
想吃火锅10054 小时前
【leetcode】105. 从前序与中序遍历序列构造二叉树
算法·leetcode·职场和发展
2401_831824964 小时前
嵌入式C++驱动开发
开发语言·c++·算法
靠沿4 小时前
【优选算法】专题十八——BFS解决拓扑排序问题
算法·宽度优先
cui_ruicheng4 小时前
C++数据结构进阶:哈希表实现
数据结构·c++·算法·哈希算法·散列表