题解:AcWing 787 归并排序

【题目来源】

AcWing:787 归并排序 - AcWing题库

【题目描述】

给定你一个长度为 \(n\) 的整数数列。

请你使用归并排序对这个数列按照从小到大进行排序。

并将排好序的数列按顺序输出。

【输入】

输入共两行,第一行包含整数 \(n\)

第二行包含 \(n\) 个整数(所有整数均在 \(1\sim 10^9\) 范围内),表示整个数列。

【输出】

输出共一行,包含 \(n\) 个整数,表示排好序的数列。

【输入样例】

5
3 1 2 4 5

【输出样例】

1 2 3 4 5

【核心思想】

  1. 问题分析:给定长度为 \(n\) 的整数序列,需要将其按从小到大排序。归并排序是一种基于分治思想的稳定排序算法,通过递归将序列二分,分别排序后再合并两个有序子序列。

  2. 算法选择

    • 归并排序(Merge Sort):采用分治策略,时间复杂度稳定为 \(O(n \log n)\)
    • 双路归并:使用临时数组将两个已排序的子数组合并为一个有序数组
    • 递归分解:不断将当前区间二分,直到区间长度为 \(1\)(天然有序),再逐层合并
  3. 关键步骤

    • 递归分解
      • 若区间长度 \(\leq 1\)\(l \geq r\)),直接返回
      • 计算中点 \(mid = \lfloor \frac{l + r}{2} \rfloor\)
      • 递归排序左半区间 \([l, mid]\) 和右半区间 \([mid+1, r]\)
    • 合并两个有序子数组
      • 初始化指针:\(i = l\)(左半部分),\(j = mid + 1\)(右半部分),\(k = 0\)(临时数组)
      • \(i \leq mid\)\(j \leq r\) 时:
        • \(q[i] \leq q[j]\)\(\text{tmp}[k++] = q[i++]\)
        • 否则 \(\text{tmp}[k++] = q[j++]\)
      • 将左半部分或右半部分的剩余元素依次放入临时数组
      • 将临时数组中的元素复制回原数组对应位置
  4. 时间/空间复杂度

    • 时间复杂度:\(O(n \log n)\),每层递归合并 \(O(n)\),共 \(\log n\)
    • 空间复杂度:\(O(n)\),需要额外临时数组存储合并结果
  5. 归并排序的核心思想

    • 分治策略:将大问题分解为两个规模减半的子问题,递归求解后通过合并得到原问题的解。分解过程形成一棵高度为 \(\log n\) 的递归树
    • 有序子数组的线性合并:两个已排序的子数组合并时,只需双指针线性扫描,每次选择较小者放入临时数组,合并过程 \(O(n)\) 且稳定(相等元素保持原有相对顺序)
    • 稳定性保证:当 \(q[i] = q[j]\) 时优先取左半部分元素,确保相等元素的原始相对顺序不变,这是归并排序相较于快速排序的重要优势
    • 时间复杂度稳定性:无论输入数据是否有序,归并排序始终进行 \(\log n\) 层分解和每层 \(O(n)\) 的合并,最坏、平均、最好时间复杂度均为 \(O(n \log n)\)
    • 适用于需要稳定排序的场景、大规模数据排序、以及作为求解逆序对等问题的基础算法

【解题思路】

【算法标签】

归并排序

【代码详解】

#include <bits/stdc++.h>
using namespace std;

const int N = 1000010;  // 定义数组最大容量

int n;         // 数组元素个数
int q[N];      // 存储待排序的数组
int tmp[N];    // 归并排序使用的临时数组

// 归并排序函数
void merge_sort(int q[], int l, int r) {
    // 递归终止条件:区间长度小于1时不需要排序
    if (l >= r) return;

    // 计算中点,分界点
    int mid = (l + r) >> 1;  // 等价于(l + r)/2

    // 递归排序左右两半
    merge_sort(q, l, mid);      // 排序左半部分
    merge_sort(q, mid + 1, r);  // 排序右半部分

    // 合并两个有序数组
    int k = 0;          // 临时数组的指针
    int i = l;          // 左半部分的指针
    int j = mid + 1;    // 右半部分的指针

    // 比较左右两部分的元素,按顺序放入临时数组
    while (i <= mid && j <= r) {
        if (q[i] <= q[j]) tmp[k++] = q[i++];  // 左边元素较小
        else tmp[k++] = q[j++];               // 右边元素较小
    }

    // 处理剩余元素(左半部分或右半部分可能还有剩余)
    while (i <= mid) tmp[k++] = q[i++];  // 左半部分剩余元素
    while (j <= r) tmp[k++] = q[j++];    // 右半部分剩余元素

    // 将临时数组中的有序元素复制回原数组
    for (i = l, j = 0; i <= r; i++, j++) {
        q[i] = tmp[j];
    }
}

int main() {
    // 输入数据
    scanf("%d", &n);  // 读取元素个数
    for (int i = 0; i < n; i++) scanf("%d", &q[i]);  // 读取数组元素

    // 调用归并排序
    merge_sort(q, 0, n - 1);

    // 输出排序结果
    for (int i = 0; i < n; i++) printf("%d ", q[i]);

    return 0;
}

【运行结果】

5
3 1 2 4 5
1 2 3 4 5
posted @ 2026-07-02 22:46  团爸讲算法  阅读(6)  评论(0)    收藏  举报