LEVEL 2 · 算法范式实验室
DIVIDE · CONQUER · COMBINE

分治原理
动画实验台

同一组 8 个数,两条不同路线:快排把工作放在“分”,归并把工作放在“合”。拖慢课堂的不是递归,而是没看见递归中维护的信息。

一个大问题
同类小问题
组合出答案

先定位:分治到底压缩了什么

先看对象、维护量与易卡点,再进入动画。

建模对象

数组区间

每次只处理一个闭区间 [left, right]

算法范式

分治 + 递归

拆成同类、规模更小的子问题,直到到达出口。

维护信息

边界与不变量

快排维护 pivot / i / j;归并维护两个队首与临时数组。

压缩信息

舍弃过程历史

不保存子区间经历过的所有比较,只保留组合答案所需的信息。

学生易卡点

“分”与“合”混淆

会背递归代码,却说不清快排为何不用合并、归并为何只看两个队首。

一组数据,两种分治顺序

不要先背代码,先判断主要工作发生在哪一步。

快速排序

先建立左右关系,再各自排序

分区左递归+右递归

分区结束后,左侧都不大于 pivot,右侧都不小于 pivot。左右内部排好,整个区间自然有序。

归并排序

先让两边有序,再线性合并

拆半两边递归合并

切半本身不改变元素顺序。真正产生当前区间答案的是“两个有序队首竞争下一个位置”。

互动实验:看见关键状态

切换模式,逐步播放或自动播放;每一步都显示算法真正维护的状态。

分区实验:[5, 3, 8, 4, 9, 7, 1, 6]

关键:pivot 保存的是数值 4 的副本,不是固定位置。黄色框会跟随元素 4 从下标 3 移到下标 2。

准备
pivot = 4(值副本)
步骤0 / 5
指针 i0
指针 j7
下一动作扫描
从两端开始。中间区域还没检查,左右确认区暂时为空。
i 左边:都 ≤ pivot
尚未确认区间
j 右边:都 ≥ pivot

复杂度动画:为什么会出现 n log n

区间越来越小,但同一层所有区间的长度加起来仍然是 n。

第 0 层
n
总工作量 n
第 1 层
n/2n/2
总工作量 n
第 2 层
n/4n/4 n/4n/4
总工作量 n
第 3 层
11 11 11 11
总工作量 n
每层 O(n) × log n = O(n log n)
当前只显示第 0 层:一个长度为 n 的区间,处理一次需要 O(n)。
归并排序

每次按中点拆半

递归树始终约有 log n 层,所以最坏时间也是 O(n log n)

快速排序

平衡时快,失衡时退化

若每次大致分半,平均为 O(n log n);若长期只缩小一点,最坏会形成 n 层,退化为 O(n²)

从动画状态回到 C++14

代码不是另一个世界:每个变量都对应动画里看得见的状态。

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

void quickSort(vector<int>& a, int left, int right) {
    if (left >= right) return;

    int pivot = a[left + (right - left) / 2];
    int i = left, j = right;

    while (i <= j) {
        while (a[i] < pivot) ++i;
        while (a[j] > pivot) --j;

        if (i <= j) {
            swap(a[i], a[j]);
            ++i;
            --j;
        }
    }

    if (left < j) quickSort(a, left, j);
    if (i < right) quickSort(a, i, right);
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n;
    cin >> n;
    vector<int> a(n);
    for (int& x : a) cin >> x;

    quickSort(a, 0, n - 1);

    for (int x : a) cout << x << ' ';
    cout << '\n';
    return 0;
}
出口

left >= right

长度为 0 或 1 的区间天然有序,递归在这里停止。

快排状态

pivot / i / j

动画中的黄色基准、蓝色左指针和橙色右指针直接映射到这三个变量。

归并状态

i / j / k / temp

两个队首竞争,胜者写入 temp[k],最后再复制回原数组。

自己判断:真的看懂了吗

点击选项立即获得与当前模型相关的反馈。

Recognition

快排一次分区结束后,已经保证什么?

请选择一个答案。

Decompression

归并时为什么只比较两个队首?

请选择一个答案。

Transfer

求第 k 小的数,分区后最值得压缩什么?

请选择一个答案。

迁移练习:改变“保留什么”

从完整排序出发,逐步减少工作或增加维护信息。

收束

一句话讲清分治

大问题拆成同类小问题,只保留组合答案所需的信息,并确保每层总工作量可控。

回到 Markdown 讲义 →
教师提示:这份动画应怎样使用

先只播放快排到“分区完成”,暂停并追问:哪些关系已经确定,哪些仍未知?再切换归并,只播放到两个四元素有序区间,让学生预测下一次进入临时数组的元素。

复杂度动画不要直接给结论。每展开一层,先让学生把该层所有小块长度相加,再说出“每层 n、约 log n 层”。