一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第11天,点击查看活动详情。
从循环到回溯
我最初接触回溯算法源自于一个简单的题目。
生成 4 的排列还是笛卡尔积(具体是哪个我记不清了)。
姑且当作当时处理的是笛卡尔积问题。
当时想到的方法是硬来,直接四重循环走起。在最内层循环处理四个循环的下标,得到一个四元组就是结果中的一项。
但是一旦 4 变成了不定值 n ,这个问题就变了。除非我们能根据给定的 n ,然而跑 n 重的循环。通过写不同的判断,对应不同的 n 重循环,或者如果输入的值能够让代码自动变成 n 重循环就好了。
后来我了解到函数是可以嵌套的概念。假设有一个这样的函数:
def f(n):
for i in range(n):
# xxxxxxxxxxxx
而且这个函数的循环内部调用的也是一个类似于f 的循环函数,那不就可以实现双重循环了吗。
比如说:
def f(n):
for i in range(n):
g(n)
def g(n):
for i in range(n):
# xxxx
这个就是一个双重循环。
这个时候冒出来一个大胆的想法,如果f调用的是自己,那会是啥呢?
可以预见的是它一直在循环,而且是无数个循环。这铁定不行。这就好比C语言的结构体不能包含自身一样。 但是结构体是语法层面就杜绝了可能性。
而这个函数调用并不会被禁止,这种方式被称作递归。
它实现了无穷多层循环的可能性,如果我能让它在某一层不再调用自己,而是调用具体的逻辑。那么就实现了有限层的循环。
这是可以实现的,比如传递调用次数,当调用次数达到要求就不再调用而是做具体的逻辑。
这就是递归基。
有了 n 重循环了,但是一些新的问题又随之而来,事实上的 n 重循环中,外层的变量可以被任意读取。然而现在递归却做不到。
他需要一些别的手段,比如在 C 中常用的方式是全局变量,Java中使用成员变量,JS使用闭包,这种使用大家都能访问的变量的方式。另一种方式是使用参数传递。
解决了变量访问问题,这个生成笛卡尔积的问题就算解决了。具体代码就不贴了。
借助递归,这里实现了任意层的循环。
后续,依靠这个将解决更多的问题,在后序将更多的利用回溯意义上的特点。
一些题外话
笛卡尔积是有一种这样的解法,把将被生成的序列看作一个不同进制的数,从这个角度把笛卡尔积的生成变成了数字的枚举或者说进位。只要把区间内的所有数枚举出来,就得到了整个笛卡尔积的结果集合。