和递归很相似的回溯

1,093 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第3天,点击查看活动详情

在很久很久以前,我接触到回溯时候,我天真以为他就是递归,毕竟都需要一个终止条件,都需要重复调用某一个方法

我们都知道二叉树其实经常使用递归,而我们回溯,也可以看成一个n叉树进程递归。

那么他们到底是怎么一回事呢?

回溯算法

回溯算法,又叫试探法,就算每一次选择一种可能,进行下去获得很多很多结果。很像数学里面的排列组合。

下面我们通过几个例子,带着大家看看试探法。

test1:字母组合

输入:"abc","def"

输出 "ad","ae","af","bd","be","bf","cd","ce","cf"

在数学里面,我们需要先确定第一位是什么字母:有三种可能,第二位每次也有三种可能,那么选择图如下

image.png

如果是排列还需要交换每一次结果顺序,如果是组合,不考虑顺序,只要结果里面是这两个字母就可以了。我们示例是左的组合,不需要考虑顺序。

在这里我们可以发现,这个图很像n叉树,那么如何用代码完成呢?

我们知道二叉树有深度优先遍历和广度优先遍历。 二叉树广度优先遍历模板:

image.png

因为最多有两个子节点所以是二叉树,如果最多有n个子节点我们可以称它为n叉树,那么n叉树的子节点比较多,我们不可能一次性全部写完,可以使用for循环来遍历,代码如下

image.png 深度优先遍历模板

思路:递归函数如果访问到叶子结点,标识到底了,返回,如果没有到达叶结点,更新结果,并且递归通过当前节点左右子树调用函数

DFS 有两个要素:「访问相邻结点」和「判断 base case」

void traverse(TreeNode root) {
    // 判断 base case
    if (root == null) {
        return;
    }
    // 访问两个相邻结点:左子结点、右子结点
    traverse(root.left);
    traverse(root.right);
}

实际上这题给的并不是一棵树,这棵树只是我们想象的,那我们怎么确定走到叶子节点了呢,实际上很简单,如果有n个数字,那么叶子节点字符串的长度就应该是n

回溯模板:

image.png

关键是回溯,即选择当前获得什么结果,如果不选这个,换成另外一个,就需要回溯到上一步,重新选择。

本题回溯函数怎么书写呢? 当路径长度等于我们需要的个数长度,标识这是结果。

 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);

}
}

其实:递归是一种算法结构,递归会出现在子程序中自己调用自己或间接地自己调用自己,回溯是一种算法思想,可以用递归实现。通俗点讲回溯就是一种试探,类似于穷举,但回溯有“剪枝”功能。