算法——回溯

今天不说废话了,回溯法之前好像没有听说过,直接进入主题

参考:https://blog.csdn.net/JarvisChu/article/details/16067319

概念

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

(百度百科)

将问题的解空间转化为树或者图的结构进行表示,然后使用深度优先算法进行遍历,遍历中记录最优解。

与递归的区别:在考虑计算所有可能性的题目中,经常使用递归算法,得出所有解,递归并不在每一步进行判断是否为最优解,而进行舍去。而回溯法是需要进行判断的是否为最优或者能否达到最优的。

基本思想

回溯法从根节点出发,按照深度优先策略遍历解空间树,搜索满足条件的解。

在回溯法中,每次扩大当前部分解时,都面临一个可选的状态集合,新的部分解就通过在该集合中选择构造而成。这样的状态集合,其结构是一棵多叉树,每个树结点代表一个可能的部分解,它的儿子是在它的基础上生成的其他部分解。树根为初始状态,这样的状态集合称为状态空间树。

初始时,根节点是活节点,也称为当前扩展节点。在当前扩展节点处,搜索向纵深方向移至另一新节点,使新节点成为活结点以及当前扩展节点。当算法移至某一新节点时,使用剪枝函数判断当前节点是否可行。若不可行则跳过该节点,直接回溯,否则进入子树继续搜索。当前节点不能再向纵深方向移动时,当前节点就成为了死节点。此时,往回移动至最近活节点,并使之成为当前扩展节点。

剪枝函数包括两类,使用约束条件减去不满足条件的路径、使用限界函数减去不满足最优的路径。

回溯法的关键在于如何定义解空间树,解空间树分为两种:

子集树:所给问题是从n个元素的集合中找到满足某种性质的子集时,解空间为子集树。

排列树:所给问题是从n个元素的集合中找到满足某种性质的排列时,解空间为排列树。

求解步骤

1)定义问题的解空间;

2)确定易于搜索的解空间结构;

3)以深度优先策略搜索解空间,并在搜索过程中用剪枝函数避免无效搜索;

例子

装载问题

问题描述:

有n个集装箱要装上2艘载重量分别为c1和c2的轮船,其中集装箱i的重量为wi,且∑wi <= c1 + c2。

问是否有一个合理的装载方案,可将这n个集装箱装上这2艘轮船。如果有,找出一种装载方案。

分析:

所谓最优装载方案为:(1)首先将第一艘轮船尽可能装满;(2)将剩余的集装箱装上第二艘轮船。

看到这个题怎么想得到用回溯法呢?如何使用解空间树?

问题的解是得到一个子集,因此使用子集树作为解空间。确定约束条件和限界函数

子集树的构建:使用怎样的方式进行子集树的建立?每一个物品都需要进行决定是否需要,可以建立n层数结构。

参考:https://blog.csdn.net/m0_38015368/article/details/80196634

代码实现:

n  集装箱数; w[]集装箱重量数组; c第一艘轮船载重量;
cw  在遍历结点处的当前载重量   bsetw 当前最优载重量

void backtrack (int i) {
    if (i > n){
        if(wc > wm) wm = wc; return;
    }
    wr -= w[i];
    if (wc + w[i] <= c){  // x[i] = 1; 搜索左子树
        wc += w[i];
        backtrack(i+1);
        wc -= w[i];
    }
    if (wc + wr > wm){  // x[i] = 0; 搜索右子树
        backtrack(i+1);
    }
    wr += w[i];
}

 

N皇后问题

问题描述:在n×n格的国际象棋上摆放n个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。

n皇后是由八皇后问题演变而来的。该问题是国际西洋棋棋手马克斯·贝瑟尔于1848年提出:在8×8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。 高斯认为有76种方案。1854年在柏林的象棋杂志上不同的作者发表了40种不同的解,后来有人用图论的方法解出92种结果。
 

分析:

•    依次在棋盘的每一行上摆放一个皇后。
•    每次摆放都要检查当前摆放是否可行。如果当前的摆放引发冲突,则把当前皇后摆放到当前行的下一列上,并重新检查冲突。
•    如果当前皇后在当前行的每一列上都不可摆放,则回溯到上一个皇后并且将其摆放到下一列上,并重新检查冲突。
•    如果所有皇后都被摆放成功,则表明成功找到一个解,记录下该解并且回溯到上一个皇后。

解空间为排列树。约束条件为不在同列,同斜线

并不需要一个n*n的数组,我们只需要一个n长度的数组来存位置。

arr[i] = k; 表示: 第i行的第k个位置放一个皇后。这样一个arr[n]的数组就可以表示一个可行解, 由于回溯,我们就可以求所有解。

参考:https://blog.csdn.net/qq908821304/article/details/80903114

代码实现:

bool Bound(int k){
    for (int i = 1; i < k; i++){
        if ((abs(k-i)==abs(x[i]-x[k]))||(x[i]==x[k])) 
            return false;
    }
    return true;
} 

void Backtrack(int t){
    if (t>n) output(x);
    else {
        for (int i = 1; i <= n; i++) {
            x[t] = i;
            if (Bound(t)) Backtrack(t+1);
        }
    }
 }

 

 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值