开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第3天,点击查看活动详情
在很久很久以前,我接触到回溯时候,我天真以为他就是递归,毕竟都需要一个终止条件,都需要重复调用某一个方法
我们都知道二叉树其实经常使用递归,而我们回溯,也可以看成一个n叉树进程递归。
那么他们到底是怎么一回事呢?
回溯算法
回溯算法,又叫试探法,就算每一次选择一种可能,进行下去获得很多很多结果。很像数学里面的排列组合。
下面我们通过几个例子,带着大家看看试探法。
test1:字母组合
输入:"abc","def"
输出 "ad","ae","af","bd","be","bf","cd","ce","cf"
在数学里面,我们需要先确定第一位是什么字母:有三种可能,第二位每次也有三种可能,那么选择图如下
如果是排列还需要交换每一次结果顺序,如果是组合,不考虑顺序,只要结果里面是这两个字母就可以了。我们示例是左的组合,不需要考虑顺序。
在这里我们可以发现,这个图很像n叉树,那么如何用代码完成呢?
我们知道二叉树有深度优先遍历和广度优先遍历。 二叉树广度优先遍历模板:
因为最多有两个子节点所以是二叉树,如果最多有n个子节点我们可以称它为n叉树,那么n叉树的子节点比较多,我们不可能一次性全部写完,可以使用for循环来遍历,代码如下
深度优先遍历模板
思路:递归函数如果访问到叶子结点,标识到底了,返回,如果没有到达叶结点,更新结果,并且递归通过当前节点左右子树调用函数
DFS 有两个要素:「访问相邻结点」和「判断 base case」。
void traverse(TreeNode root) {
// 判断 base case
if (root == null) {
return;
}
// 访问两个相邻结点:左子结点、右子结点
traverse(root.left);
traverse(root.right);
}
实际上这题给的并不是一棵树,这棵树只是我们想象的,那我们怎么确定走到叶子节点了呢,实际上很简单,如果有n个数字,那么叶子节点字符串的长度就应该是n
回溯模板:
关键是回溯,即选择当前获得什么结果,如果不选这个,换成另外一个,就需要回溯到上一步,重新选择。
本题回溯函数怎么书写呢? 当路径长度等于我们需要的个数长度,标识这是结果。
private void backTrack(StringBuffer path, int num, Map<Character,String> map){
if(path.length()== digitsTarget.length()){
ans.add(path.toString());
return ;
}
char digit = digitsTarget.charAt(num);
char[] digitCur = map.get(digit).toCharArray();
for(char s:digitCur){
path.append(s);
backTrack(path,num+1,map);
path.deleteCharAt(path.length()-1);
}
}
test2 全排列
输入:nums = [1,2,3] 输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
如果第一个选择1,那么第二个有两种可能,第三个只有一种,且得到结果后,需要回退,并且删除当前值。
我们可以把他当作树来做。
public void huisu(List<List<Integer>> list,List<Integer> temList,int[] num){
//如果到达叶子结点,就回退
if(temList.size()==num.length){
list=new ArrayList<>(temList);
return;
}
for(int i=0;i<num.length;i++){
if(temList.contains(num[i])){
continue;
}
temList.add(num[i]);
//递归(可以把它看做遍历子节点的子节点)
huidu(list, temList, num);
//撤销选择,把最后一次添加的值给移除
temList.remove(temList.size() - 1);
}
}
其实:递归是一种算法结构,递归会出现在子程序中自己调用自己或间接地自己调用自己,回溯是一种算法思想,可以用递归实现。通俗点讲回溯就是一种试探,类似于穷举,但回溯有“剪枝”功能。