回溯算法的通用流程(重点为剪枝去重)

回溯算法基本流程

说明:

        将回溯算法解决的问题表示为二叉树形式,for表示树层的循环,即横向;backtracking表示树枝的循环,即纵向

        每次进入backtracking时,都相当于进入了一个节点。需要判断是否符合最后需要求的结果。if用来判断处理结果。然后开始进行横向遍历(树层遍历)。

        在处理完本层的节点后,进入了backtracking(即进入了下一层),从下一层返回到上一层时,需要对从下一层返回来的结果进行回溯,撤销在下一层backtracking处理的结果。即图中蓝色线条的路径。

void backtracking(参数) {
    if (终止条件) {
        存放结果;
        return;
    }

    for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
        处理节点;
        backtracking(路径,选择列表); // 递归
        回溯,撤销处理结果
    }
}

startIndex的用法

        树层的处理逻辑:backtracking的参数不是一次性确定的,可以慢慢确定,随写随用。这里只分析重点参数startIndex的用法。

        1、当每次进入到一个树层,都需要重头开始遍历时,此时在树层的遍历中,应该是置 i = 0,如下:此时用不上startIndex。每次进入树层都会从 0 开始遍历。比如全排列问题,每次进去都需要从头开始处理(不过会跳过已经处理过的节点,这个后面再说)

for (int i =0; i<=n; i++)

         2、当我们处理组合问题时,例如如下问题,每次进入一个树层,都需要从下一个开始遍历。举个例子,比如我上一个取了1(上一个树层),进入下一个树层时(backtracking)我就不能再取1了,我从2开始取(下一个树层)。说人话就是,1、2、3、4 这四个数我用过了就不能再用了,只能用一次。 

        因此如下问题的树层代码处理为:这里的 startIndex 表示从树层的哪里开始遍历。在进入下一层时,将 i + 1 ,即实现了从下一个开始取(不重复)。

        如果单纯是 i 呢?那么表示直接从当前这个数开始取,可以重复,比如我上一层取了 2 ,下一层我依然可以取 2 。这里只需要将 backtracking 的递归层的 i + 1 改成 i 。

        其实说到这里提一嘴,这是纵向的去重,即用过的不再重复用了,称为树枝去重。下面的used数组进行的实际上是树层的去重

//backtracking(int n,int k,int startIndex)
for (int i = startIndex; i <= n; i++){
    path.add(i);
    backtracking(n,k,i+1);
    path.removeLast();
}

去重的逻辑

1、used数组去重的用法

        考虑问题比如如下问题。candidates 中实际上有重复元素。找出组合需要去重,比如我前两个 2 组成的组合 和 后两个 2 组成的组合就一样。如果用 map 或者 set去重。可能会超时 。

                这里提供的去重思路如下:先进行 sort 排列。(为什么要sort呢?为了把一样的数弄到一起)当我们前面使用过 2 这个数之后,我们后面的 2 就不再考虑了。为什么呢?因为这里是求组合问题。比如在这里排序后是  1 2 2 2 5 ,第一个 2 和后面的每个数进行组合所得出的结果集 A ,一定包含第二个 2 和后面的每个数进行组合所得出的集合 B 。即如下代码可以表示重复的数,重复即跳过。

i > 0 && candidates[i] == candidates[i - 1]

        但是这么去重确定没有问题吗?这么去重实际上是把树层和树枝的重复全部去掉了。这么得出来的结果如下:

        实际上他是把树枝的重复也去掉了,即 我在选定了一个 2 后,后面就不允许再选 2 了。为什么会这样呢?看看下面的图上:

        黄色的圈内是上一层和下一层的取数,实际上这里就candidates[i] == candidates[i - 1]。即上一层我取了1,下一层我取的还是1。

        粉色的圈内部也同样是candidates[i] == candidates[i - 1]。

        那么一个是树层一个是树枝,怎么区分呢?用used数组表示使用过了的数。其实我们要去除的只是 上一个 2 没有用但是下一个 2 用了 的这种情况。因此上一个 2 的 used 数组的对应位置表示 0, 下一个 2 的对应位置表示为 1 (即中间那个1的情况的used数组)。

        这是树层的used数组。和下一层有什么关系呢?还记得我在第一个图标出的蓝色箭头吗?表示的就是回溯方向。同一个树层的used数组是回溯来的,因此used[ i - 1 ] 肯定是没有用过的。即used[ i - 1 ] = 0 表示是树层的重复。

        而上下层关系中,下一层的used数组是通过backtracking递归下去的,不存在回溯,因此used[ i - 1 ]肯定是用过了的。即used[ i - 1 ] = 1 表示是树枝的重复。

2、用set去重

        set去重的逻辑:set存储是否使用过。在当前树层判断当前元素是否存在set中(即是否使用过)。如果没有使用过,那么本层使用后就直接加入set。如果使过用直接continue即可。去重逻辑比起used数组要简单一些,但是不够通用,在处理一些问题的时候比较复杂,这里不详细去说了。

        基本上掌握了used数组的去重,能做出大部分回溯的题目了。后续更新二叉树的遍历方式。包括前中后序、层序的通用方法。根回溯差不多,基本上套用模板想明白改进的地方就能AC一些题目了。

  • 25
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值