干货版《算法导论》07:递归视角下的选择排序与归并排序

干货版《算法导论》07:递归视角下的选择排序与归并排序

  • [Bilibili 同步视频](#Bilibili 同步视频)
  • [一、🌿 选择排序:每一步都「选最大」,朴素却深刻](#一、🌿 选择排序:每一步都「选最大」,朴素却深刻)
    • [1.1 核心直觉:一句话讲懂](#1.1 核心直觉:一句话讲懂)
    • [1.2 递归实现:理论版写法(非工程最优)](#1.2 递归实现:理论版写法(非工程最优))
    • [① 辅助函数:prefix_max(找 0~i 最大值下标)](#① 辅助函数:prefix_max(找 0~i 最大值下标))
    • [② 选择排序主递归](#② 选择排序主递归)
    • [1.3 正确性证明:数学归纳法](#1.3 正确性证明:数学归纳法)
    • [1.4 时间复杂度:为什么是 Θ(n²)?](#1.4 时间复杂度:为什么是 Θ(n²)?)
    • [① prefix_max 复杂度](#① prefix_max 复杂度)
    • [② 选择排序总复杂度](#② 选择排序总复杂度)
  • [二、🚀 归并排序:分治的艺术,从 O (n²) 飞跃到 O (n log n)](#二、🚀 归并排序:分治的艺术,从 O (n²) 飞跃到 O (n log n))
    • [2.1 核心直觉:分而治之,两两合并](#2.1 核心直觉:分而治之,两两合并)
    • [2.2 双指合并(Two-Finger Algorithm)](#2.2 双指合并(Two-Finger Algorithm))
    • [2.3 递归伪代码(清晰版)](#2.3 递归伪代码(清晰版))
    • [2.4 复杂度证明:为什么是 Θ(n log n)?](#2.4 复杂度证明:为什么是 Θ(n log n)?)
  • [三、📊 两大算法硬核对比(一看就懂)](#三、📊 两大算法硬核对比(一看就懂))
  • [四、💡 结语:算法之美,在于「思想」而非「代码」](#四、💡 结语:算法之美,在于「思想」而非「代码」)

Bilibili 同步视频

干货版《算法导论》07:递归视角下的选择排序与归并排序

在算法的世界里,排序是最朴素、最经典,也最能体现「思想之美」的基石问题。我们每天都在和有序序列打交道,而背后支撑这一切的,是一套套精巧的排序逻辑。

今天,我们不聊花哨的工程优化,只从递归 + 数学证明的视角,拆解两种极具代表性的排序:

  • 🌱 选择排序(Selection Sort):直观、简单,但藏着 O (n²) 的宿命

  • ⚡ 归并排序(Merge Sort):分治之神,稳定 O (n log n) 的效率王者

全文会用符号、公式、伪代码、复杂度推导,把原理讲透,把证明写清,带你看懂「为什么排序能从平方级跃迁至对数级」。


一、🌿 选择排序:每一步都「选最大」,朴素却深刻

1.1 核心直觉:一句话讲懂

把数组看成「未排序区」和「已排序区」:

  1. 从未排序区里找到最大值

  2. 把它交换到未排序区的末尾

  3. 未排序区缩小一位,重复直到全部有序

举个栗子🌰:

数组:[8, 2, 4, 9, 3]

  • 第一轮:找最大 9 ↔ 末尾 3 交换 → [8,2,4,3, | 9]

  • 第二轮:左边找最大 83 交换 → [3,2,4, | 8,9]

  • ......

  • 最终:[2,3,4,8,9]

红线右侧就是已排序区 ,单元素天然有序,这就是递归的归纳基础

1.2 递归实现:理论版写法(非工程最优)

我们用递归重写选择排序,只为正确性证明 + 复杂度分析

① 辅助函数:prefix_max(找 0~i 最大值下标)

Plain 复制代码
// 递归求 arr[0...i] 中的最大值下标
prefix_max(arr, i):
    if i == 0:
        return 0  //  base case:只有一个元素
    j = prefix_max(arr, i-1)
    if arr[i] > arr[j]:
        return i
    else:
        return j

✅ 思想:最大值要么在 i 位置,要么在 0~i-1 里------ 这就是「递归的信仰」。

② 选择排序主递归

Plain 复制代码
// 递归选择排序:排序 arr[0...i]
selection_sort(arr, i):
    if i <= 0:
        return
    // 1. 找 0~i 最大元素下标
    max_idx = prefix_max(arr, i)
    // 2. 交换到末尾
    swap(arr[max_idx], arr[i])
    // 3. 递归排序左边 0~i-1
    selection_sort(arr, i-1)

工程上我们用双层 for 循环,但递归版更易证明正确性与复杂度

1.3 正确性证明:数学归纳法

  • Base Case:i=0,单元素有序,成立。

  • Inductive Step :假设 prefix_max(arr, i-1) 正确找到 0i-1 最大值,则只需再比较 arr[i],即可得到 0i 最大值。

  • 结论:算法总能把当前最大元素放到正确位置,递归收缩后整体有序。

1.4 时间复杂度:为什么是 Θ(n²)?

① prefix_max 复杂度

Plain 复制代码
s(n) = s(n-1) + Θ(1)
s(1) = Θ(1)

展开得:s(n) = Θ(n)

② 选择排序总复杂度

Plain 复制代码
T(n) = T(n-1) + Θ(n)
T(1) = Θ(1)

展开:

T (n) = Θ(n) + Θ(n-1) + ... + Θ(1) = Θ(n²)

代入法验证

假设 T (n) ≤ c・n²

c・n² ≤ c・(n-1)² + Θ(n)

c・n² ≤ c・n² - 2cn + c + Θ(n)

0 ≤ -2cn + c + Θ(n)

成立 → T(n) = Θ(n²)


二、🚀 归并排序:分治的艺术,从 O (n²) 飞跃到 O (n log n)

2.1 核心直觉:分而治之,两两合并

归并排序是分治思想的完美示范:

  1. 分解:把数组从中间切两半

  2. 解决:递归排序左半、右半

  3. 合并:用「双指算法」把两个有序数组合成一个大有序数组

关键洞察:单个元素天然有序 → 递归到底层全是最小有序单元。

2.2 双指合并(Two-Finger Algorithm)

合并两个有序数组 A=[1,5,6,7]B=[2,3,4,9]

  • 两指针分别指向末尾

  • 每次取更大的放到结果末尾

  • 指针左移,直到全部合并

优点:只遍历一遍,Θ(n) 完成合并

2.3 递归伪代码(清晰版)

Plain 复制代码
// 归并排序主函数
merge_sort(arr, l, r):
    if l >= r:
        return
    mid = (l + r) // 2
    merge_sort(arr, l, mid)    // 左有序
    merge_sort(arr, mid+1, r)  // 右有序
    merge(arr, l, mid, r)     // 双指合并

// 双指合并:合并 arr[l..mid] 与 arr[mid+1..r]
merge(arr, l, mid, r):
    新建临时数组 temp
    i = l, j = mid+1, k = 0
    while i ≤ mid && j ≤ r:
        if arr[i] ≤ arr[j]:
            temp[k++] = arr[i++]
        else:
            temp[k++] = arr[j++]
    // 复制剩余部分
    while i ≤ mid: temp[k++] = arr[i++]
    while j ≤ r: temp[k++] = arr[j++]
    // 拷回原数组
    for t from 0 to r-l:
        arr[l+t] = temp[t]

2.4 复杂度证明:为什么是 Θ(n log n)?

递归式:

Plain 复制代码
T(n) = 2·T(n/2) + Θ(n)
T(1) = Θ(1)

代入法证明

假设 T (n) ≤ c・n log n

左边:c・n log n

右边:2・c・(n/2)・log (n/2) + Θ(n)

= c・n・(log n - 1) + Θ(n)

= c・n log n - c・n + Θ(n)

两边抵消 c・n log n,得:

0 ≤ -c・n + Θ(n)

成立 → T(n) = Θ(n log n)


三、📊 两大算法硬核对比(一看就懂)

维度 选择排序 Selection Sort 归并排序 Merge Sort
思想 贪心 + 选择极值 分治 + 递归合并
时间复杂度 Θ(n²) Θ(n log n)
空间复杂度 Θ(1) 原地排序 Θ(n) 需临时空间
稳定性 不稳定 稳定
适用场景 小数据、教学演示 大数据、稳定排序
最优 / 最坏 始终 Θ(n²) 始终 Θ(n log n)

一句话总结:

  • 选择排序:简单但慢,适合理解递归与归纳

  • 归并排序:高效稳定,是真正工业级的排序基石


四、💡 结语:算法之美,在于「思想」而非「代码」

从选择排序到归并排序,我们看到的不只是效率提升,更是思维方式的跃迁

  • 选择排序:用「选择」解决问题,简单直观,却逃不开 O (n²)

  • 归并排序:用「分治」拆解问题,把复杂变简单,直达 O (n log n)

算法从来不是枯燥的循环与递归,而是数学之美、逻辑之美、工程之美的结合。

下次当你面对无序数据时,不妨想

你是要「一步步选到有序」,还是「分而治之,快速归并」?

相关推荐
csdn_aspnet1 小时前
javascript 算法 LeetCode 编号 70 - 爬楼梯
开发语言·javascript·算法·leetcode·ecmascript
掉鱼的猫1 小时前
Solon Server 启动模式深度解析:从 0.3MB 内核到 10+ Server 插件
java·http
shehuiyuelaiyuehao1 小时前
多线程入门
java·python·算法
星夜夏空991 小时前
FreeRTOS学习(7)——任务列表
java·前端·学习
Navigator_Z1 小时前
LeetCode //C - 1073. Adding Two Negabinary Numbers
c语言·算法·leetcode
han_hanker2 小时前
BeanUtils.copyProperties 和序列化的问题
java·开发语言·spring boot
醇氧2 小时前
【OpenClaw】更换阿里百炼完整配置指南
算法·ai
野生技术架构师2 小时前
牛客网2026互联网大厂Java面试题汇总,附官方级答案解析
java·开发语言
Tina学编程2 小时前
[HOT100]每日一练------最长连续序列
算法·hot 100