递归算法实战:汉诺塔与合并两个有序链表详解
前言
很多人初学算法时,对递归、搜索与回溯感到既不解又迷茫。看到函数调用自己,往往会产生一种无力感和恐惧感,不清楚代码到底是如何执行的。这非常正常,但理解递归的关键在于转变视角。
本文旨在通过两道经典题目,帮助大家消除对递归的恐惧,建立清晰的解题思路。
递归的核心思维:宏观视角
看待递归最重要的点,是清楚这个函数的功能(作用)到底是什么。
递归的本质是自己调用自己。就像我们之前写代码,调用一个加法函数是为了利用它的功能。同理,在归并排序中,mergesort 的功能就是'让一个无序数组变成有序'。
当我们实现归并时,将数组分成左右两部分,为了让它们分别有序,我们直接调用 mergesort 即可。不要去下意识在意函数调用自己的递归细节展开图,我们要抱着相信的态度去看待递归。虽然我们还没完全实现完函数,但我们调用它本身就是相信它能帮我们实现功能。
例如调用 mergesort(nums, left, mid) 后,心里就要有底:此时左边数组已经完成了排序。至于具体怎么操作的,不要纠结。两边都排好序后,再合并即可。
最后只需注意一点:保证递归有结束条件,通常在函数开头设置终止判断。
1. 汉诺塔
题目描述
有三根柱子 A、B、C,A 柱上有 n 个大小不同的盘子,从小到大叠放。要求将所有盘子从 A 移动到 C,移动过程中大盘子不能压在小盘子上面。
算法思路
这是一道递归方法的经典题目,我们可以从最简单的情况考虑:
- n=1:只有一个盘子,直接从 A 移到 C。
- n=2:借助 B 柱。1 号(小)移到 B,2 号(大)移到 C,1 号再从 B 移到 C。
- n>2:策略同上。将 A 上面的 n-1 个盘子挪到 B 上,再将最大的盘子挪到 C 上,最后将 B 上的 n-1 个小盘子挪到 C 上。
核心逻辑拆解:
- 对于规模为 n 的问题,目标是将 A 柱上的 n 个盘子移动到 C 柱。
- 规模 n 可被拆分为规模 n-1 的子问题:
- 将 A 柱上的上面 n-1 个盘子移动到 B 柱(借助 C)。
- 将 A 柱上的最大盘子移动到 C 柱。
- 将 B 柱上的 n-1 个盘子移动到 C 柱(借助 A)。
- 当规模变为 n=1 时,直接移动。
C++ 算法代码
class Solution {
public:
void hanota(vector<int>& A, vector<int>& B, vector<int>& C) {
dfs(A, B, C, A.size());
}
void dfs(vector<int>& x, vector<int>& y, vector<>& z, n) {
(n == ) {
z.(x.());
x.();
;
}
(x, z, y, n - );
z.(x.());
x.();
(y, x, z, n - );
}
};


