[知识点] 2.2 递归与分治
总目录 > 2 算法基础 > 2.2 递归与分治
前言
递归是一种非常常用的算法思想,太多太多地方使用了。
(总目录:https://www.cnblogs.com/jinkun113/p/12528423.html)
子目录列表
1、递归
2、分治
2.2 递归与分治
1、递归
递归的基本思想是函数直接或间接地调用自身,用两张图来分辨递归与枚举的区别:
枚举是按部就班从起点到终点逐一处理或累加;递归时的求解过程就像剥洋葱皮一样一层层地深入,直到得到最终结果。所以,递归的代码往往为如下结构:
int func(当前状态) { if (终止条件) return 最终结果; return func(下一状态); }
也就是说,打一开始,我们就要知道我们求解的目的是什么,终点在何方。相比枚举,递归其实是一种逆向思维,因为它的主要步骤是从最外层先找到最里层,然后再从最里层开始处理问题。
从结构上来看,递归的三大核心:
① 当前状态
② 终止条件
③ 下一状态
明确了这三点,递归基本就成型了。
在不建立在某种算法或者某种结构的前提下,其实单说递归是空洞的,它是作为各种算法的底层思想,所以如果要去理解它,去了解用到递归的算法要比了解递归的本质要来的轻松。所以,这里只给出几个密切相关的链接和一些代码,暂且不提算法本身的作用,只是用以体现递归的常用形式。
① 深度优先搜索 DFS(请参见:3.1 DFS / BFS)
1 void DFS(int x, int y, int p) { 2 if (a[x][y] == 1) return; 3 a[x][y] = 1; 4 if (x == tx && y == ty) ans = min(p, ans); 5 else for (int i = 1; i <= 4; i++) { 6 int vx = x + cx[i], vy = y + cy[i]; 7 if (a[vx][vy] == 0) DFS(vx, vy, p + 1); 8 } 9 a[x][y] = 0; 10 }
DFS 的过程绝对是对递归思想最好的诠释,整个搜索框架完全是用递归实现的,这里给出的是 0-1 矩阵走迷宫代码只是其中一种。
② 树结构的构建与遍历(请参见 7.3 树与二叉树)
③ 归并排序(请参见 2.4 排序十讲 中的 归并排序 部分)
④ 最大公约数(请参见 6.4.1 素数与最大公约数)
1 int gcd(int x, int y) { 2 return y ? gcd(y, x % y) : x; 3 }
⑤ 线段树的区间询问操作(请参见 施工中)
1 int quem(int o, int l, int r) { 2 if (t[o].f) push(o, r - l + 1); 3 if (ql <= l && r <= qr) return t[o].v; 4 int m = (l + r) >> 1; 5 return (ql <= m ? quem(ls, l, m) : 0) + (qr >= m + 1 ? quem(rs, m + 1, r) : 0); 6 }
因为线段树本身就是树结构,所以其实它也可以归类于 ②。
2、分治
其实分治是递归中的一种,同样用一张图来体现它的特点:
相较于非分治的递归,分治的特点在于:
① 对于每一层向内并不是只递归一次,一般是两次,但更多也可以;
② 对于任何元素,其在递归过程中只会被访问一次。
在递归部分给出的若干份代码中,线段树的区间询问函数和归并排序都属于分治思想。
分治思想的核心在于:分解,解决,合并。
因为同样是一种思想,所以这里也不作更多的解释,参见各种算法对于理解可能会来得更直接一点。