张三木教你理解回溯法

回溯法

回溯法(搜索与回溯法)是一种选优搜索法,又称为试探法,按选优条件向前搜索,以达到目标。但当搜索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择。这种走不通就退回再走的技术为回溯法。而满足回溯条件的某个状态的点称为“回溯点”。

回溯法问题的框架

问题的解空间

复杂问题常常有很多的可能解,这些可能的解构成了问题的解空间。解空间就是进行穷举的搜索空间,所以解空间中应该包含所有的可能解,且至少应该包含问题的一个最优解。例如:对于有n个物品的0/1背包问题,当n=3时,其解空间是:{(0, 0, 0), (0, 0, 1), (0, 1, 0), (1, 0, 0), (0, 1, 1), (1, 0, 1), (1, 1, 0), (1, 1, 1) }。

回溯法的基本思想
基本步骤
  1. 针对所给的问题,定义问题的解空间
  2. 确定易于搜索的解空间结构
  3. 以深度优先方式搜索解空间,并且在搜索过程中用剪枝函数避免无效搜索
常用剪枝函数
  1. 约束函数:减去不满足条件的子树
  2. 限界函数:减去得不到最优解的子树
递归和迭代回溯
递归回溯

模板:

void Backtrack( int t ){
    if ( f > n ) {
        Output(x);
    }else{
        for( int i = f(n,t), i<=g(n,t);i++){
            x[t] = h(i);
            if(Constraint(t)&&Bound(t)){
                Backtrack(t+1);
           }
        }
    }
}       
迭代回溯

模板:

void IterativeBacktrack(void){
    int t=1;
    while(t>0){
        if(f(n,t)<=g(n,t){
            for(int i=f(n,t);i<=g(n,t);i++){
                x[t]=h(i);
                if(Constraint(t)&&Bound(t)){
                    if(Solution(t))
                        Output(x);
                   else
                       t++;
                  }
          else
            t--;
          }
      }
  }         
子集树和排列树
  • 子集树:当所给的问题是从n个元素的集合S中找出满足某种性质的子集时,相应的解空间为子集树。如0-1背包问题。
  • 排列树:当所给问题是确定n个元素的满足某种性质的排列时,相应的解空间为排列树。如旅行售货员问题。

经典例题

子集和问题

题目:

解析:
该题的解空间是一个子集树,其中解集为S={s1,s2,··,sn},s1到sn的和为给定的C。同样,为了避免无效搜索,加快算法效率,需要剪枝函数。该题的剪枝函数限制条件为当前已加的值sum加上当前结点的值value大于给定的C,则不往下遍历。(这里没有做剪枝,丢人)
代码如下:

#include<iostream>
using namespace std;
#define MAXLENGTH 10005
int arr[MAXLENGTH]; // 存储数组
bool haveUsed[MAXLENGTH]; // 存储数组各个元素当前状态
int value; // 存储当前值
bool isPrint = false;

void count(int i,int num,int sum) {
    if (i > num||isPrint) {
        return;
    };
    haveUsed[i] = true;
    value += arr[i];

    if (value == sum&&!isPrint) {
        for (int j = 0; j < num; j++) {
            if (haveUsed[j]==true) {
                cout << arr[j]<<" ";
            }
        };
        isPrint = true;
        return;
    }
    else if(value<sum){
        count(i + 1, num, sum);
    }

    if (isPrint) { // 已经输出,直接返回(这实现有点傻)
        return;
    }

    haveUsed[i] = false;
    value -= arr[i];

    count(i + 1, num, sum);
    return;
}
int main() {
    int num, sum;
    int tmp = 0;
    cin >> num >> sum;
    for (int i = 0; i < num; i++) {
        cin >> arr[i];
        tmp += arr[i];
    }
    if (tmp < sum) {
        cout << "No Solution!" << endl;
        return 0;
    }
    count(0, num, sum);
    if (!isPrint) {
        cout << "No Solution!" << endl;
    }
    system("pause");
    return 0;
}

总结

  1. 当问题是满足某种性质(约束条件)取得最优解时,往往采用回溯法。
  2. 回溯法的实现方法有两种:递归和迭代。一个问题两种方法都可以实现,只是算法效率会有不一样。
  3. 因为有剪枝函数,回溯法的效率远远大于穷举法。
结对编程状况

结对编程的时候我一般负责代码实现和提供思路,队友每次都能纠正我的思维盲点,以及提出高效的剪枝函数的条件。合作愉快。(反正就是被带飞了)

原文地址:https://www.cnblogs.com/MarcusJr19/p/12070442.html

时间: 2024-10-09 09:04:44

张三木教你理解回溯法的相关文章

从Leetcode的Combination Sum系列谈起回溯法

在LeetCode上面有一组非常经典的题型--Combination Sum,从1到4.其实就是类似于给定一个数组和一个整数,然后求数组里面哪几个数的组合相加结果为给定的整数.在这个题型系列中,1.2.3都可以通过回溯法来解决,其实4也可以,不过由于递归地比较深,采用回溯法会出现TLE.因此本文只讨论前三题. 什么是回溯法?回溯法是一种选优搜索法,按选优条件向前搜索以达到目标.当探索到某一步时,发现原先的选择并不优或达不到目标,就退回异步重新选择.回溯法是深度优先搜索的一种,但回溯法在求解过程不

南邮算法分析与设计实验3 回溯法

回溯法 实验目的: 学习编程实现深度优先搜索状态空间树求解实际问题的方法,着重体会求解第一个可行解和求解所有可行解之间的差别.加深理解回溯法通过搜索状态空间树.同时用约束函数剪去不含答案状态子树的算法思想,会用蒙特卡罗方法估计算法实际生成的状态空间树的结点数. 实验内容: 1.求24点问题 给定四个1-9之间的自然数,其中每个数字只能使用一次,用算术运算符+,-,*,/构造出一个表达式,将这4个正整数连接起来(可以使用括号),使最终的得数为24.要求根据问题的特征设计具体算法并编程实现,输入数据

回溯法之八皇后问题简单理解

回溯法,简单理解就是有源可溯.基本思想要借鉴穷举法,但是它不是一味地穷举,当发现某一步不符合条件时,这一步后面的穷举操作就不进行了(俗称“剪枝”),我自己把它叫做动态穷举法.假设第一个步骤可行,那么执行第二个步骤,第三个......如果其中第三个步骤不行,那么我们再回过来(回溯),第二个步骤换一种方法尝试,然后再重新第三个步骤,第四个......直到完成任务要求为止. 这里,以八皇后问题为例.试图把回溯法讲清楚. 注意:递归应该是一种算法结构,回溯法是一种算法思想. 何为八皇后问题? (百度百科

回溯法理解

回溯法 基本思想: 构建问题的解空间树,在其解空间树中,从根节点出发,进行深度优先搜索.在搜索过程中,对解空间 树的每个结点进行判断,判断该结点是否包含问题的解,若肯定不包含,则跳过对以该结点为根的子树的 搜索,逐层向其祖先结点回溯.否则,则进入该子树,继续按深度优先策略搜索. 步骤: 1.针对所给问题,定义其解空间 2.确定易于搜索的解空间结构 3.深度优先搜索其解空间,并在搜索过程中用剪枝函数避免无效搜索 剪枝: 回溯法搜索空间树时,常用限界函数和约束函数避免无效搜索,其中约束函数将不满足约

【基础算法】回溯法与八皇后问题

在国际象棋中,皇后是最强大的一枚棋子,可以吃掉与其在同一行.列和斜线的敌方棋子.比中国象棋里的车强几百倍,比她那没用的老公更是强的飞起(国王只能前后左右斜线走一格).上图右边高大的棋子即为皇后. 八皇后问题是这样一个问题:将八个皇后摆在一张8*8的国际象棋棋盘上,使每个皇后都无法吃掉别的皇后,一共有多少种摆法?此问题在1848年由棋手马克斯·贝瑟尔提出,岂止是有年头,简直就是有年头,82年的拉菲分分钟被秒的渣都不剩. 八皇后问题是典型的回溯法解决的问题,我们以这个问题为例介绍回溯法. 所谓回溯法

回溯法-01背包问题之一:递归模式

一.回溯法 回溯法是一个既带有系统性又带有跳跃性的搜索算法.它在包含问题的所有解的解空间树中按照深度优先的策略,从根节点出发搜索解空间树.算法搜索至解空间树的任一节点时,总是先判断该节点是否肯定不包含问题的解.如果肯定不包含,则跳过对以该节点为根的子树的系统搜索,逐层向其原先节点回溯.否则,进入该子树,继续按深度优先的策略进行搜索. 运用回溯法解题通常包含以下三个步骤: · 针对所给问题,定义问题的解空间: · 确定易于搜索的解空间结构: · 以深度优先的方式搜索解空间,并且在搜索过程中用剪枝函

基于回溯法寻找哈密顿回路

回溯法是一种选优搜索法,又称为试探法,按选优条件向前搜索,以达到目标.但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”. 在包含问题的所有解的解空间树中,按照深度优先搜索的策略,从根结点出发深度探索解空间树.当探索到某一结点时,要先判断该结点是否包含问题的解,如果包含,就从该结点出发继续探索下去,如果该结点不包含问题的解,则逐层向其祖先结点回溯(其实回溯法就是对隐式图的深度优先搜索算法). 若用

回溯法、数独与N阶可达问题(一)

回溯法是剪了枝的穷举,这是字面上的说法,不太好理解,不如讲解实例来的酸爽,于是引出了N阶可达问题: 有N个国家,每个国家有若干城市,小明要从中国(任意一个城市)出发,遍历所有国家(假设这个遍历顺序已经定了),最终到达美利坚(任意一个城市).而城市之间有可能不可达,只有小明尝试过才知道(就是后面的check()函数),求满足要求的一条路径? 从上面的表述中我们已经嗅到了浓浓的穷举屌丝气质——遍历所有组合,但是我们的回溯思想总是基于这样一个简单的事实:如果当前选择导致你走进了死胡同,那么这个选择一定

回溯法第3题—n皇后问题

[问题描述] 在一个国际棋盘上,放置n个皇后(n<10),使她们相互之间不能进攻.求出所有布局. 输入:n 输出:每行输出一种方案,每种方案顺序输出皇后所在的列号,每个数之间用空格隔开. [样例输入] 4 [样例输出] 2 4 1 3 2 1 4 2 [问题分析] 我想这大概是回溯法最经典的一个问题了吧,开一个函数判断一下当前位置是否能放即可. 这里有一个小技巧,就是对皇后进行编号,对应着数组的下标,就可以很容易的满足第一个条件——所有皇后不能在同一行上. 然后数组元素则代表皇后所在的列数,满足