搜索本质上也是对解空间的枚举,本文介绍搜索算法中的深度优先搜索(图论)。python
给定一个 没有重复数字的序列,返回其全部可能的全排列。例如对于数列[1, 2, 3]其全排列为[[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]。
咱们可使用n层循环,每一层循环内肯定一位数字,在最内层循环内判断该排列是否符合要求,例如对于数列nums = [1, 2, 3],能够写出以下代码。算法
for i in nums: for j in nums: for k in nums: if i != j and j != k and i != k: print i, j, k
这道题目分析到这里,其实和个人第一篇文章的问题颇有很大类似的地方,但不一样的是在于百鸡百钱问题的自变量个数是固定的,即循环层数是固定的。数组
上述代码中咱们试图经过每一层循环来肯定一个数值,但这段代码只适用于len(nums) == 3的状况,可是若是nums长度为4,5,或更高呢?咱们没法动态生成n层循环,除非是用程序编写程序,递归为咱们巧妙地解决了这个问题。bash
在递归中,咱们则经过每一层函数的嵌套来肯定一个数值,而且咱们只需给出顶层的实现就够了。app
因而咱们得出了下面的代码(涉及python中list与set的使用)。函数
def solution(nums, status): if set(nums) == set(status): print status for x in nums: solution(nums, status+[x])
上述代码中,status表示当前函数层次的状态,即一个排列结果,该递归函数能够理解为一个数学表达式:solution = for + solution
,那么该solution函数就会被展开为下面的样子。post
for ... in range(...): for ... in range(...): ... if ... : print ... # 只有在第n层,条件才会成立
这就和咱们最开始给出的代码看上去差很少了。可是如今代码虽然是有限的,可实际展开的时候依旧是无穷无尽的,这就须要咱们为solution函数加上一个终止条件,也称递归出口。spa
若是把递归的过程想象成包子馅的包子,那么若是没有递归出口,这个包子将会变成馒头。code
那么递归出口咱们如何去定义呢?blog
经过题意不难推出当对于当前层,若当前排列结果status长度大于nums数列长度,便可终止递归。
因而可得出正确代码以下。
def solution(nums, status): if len(status) > len(nums): return if set(nums) == set(status): print status for x in nums: solution(nums, status+[x]) solution([1,2,3], []) # [[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]
这段代码虽然能够正确运行,但咱们可让他更加美观。最终代码以下。
这一次,咱们将递归出口定义在进入递归函数前,而且将中间状态记录在了ans数组中。
def solution(nums, status, ans): if len(nums) == len(status): ans.append(status) for x in nums: if x not in status: solution(nums, status+[x], ans) return ans print solution([1,2,3], [], []) # [[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]
若是在solution的开始输出status的值,咱们会获得以下的输出结果。
[1] [1, 2] [1, 2, 3] [1, 3] [1, 3, 2] [2] [2, 1] [2, 1, 3] [2, 3] [2, 3, 1] [3] [3, 1] [3, 1, 2] [3, 2] [3, 2, 1]
观察程序的输出与下面的图片,体会该迭代方法被称做深度优先搜索的缘由。
本文示例题目与leecode 46.全排列一致,读者可自行尝试提交,验证本身代码的正确性。