算法设计与分析——回溯算法

1. 回溯算法

1.1 基本思想

回溯法的基本做法是搜索,在问题的解空间树中,按深度优先策略,从根结点出发搜索解空间树。算法搜索至解空间树的任意一点时,先判断该结点是否包含问题的解。如果肯定不包含,则跳过对该结点为根的子树的搜索,逐层向其祖先结点回溯;否则,进入该子树,继续按深度优先策略搜索。

1.2 回溯法与分支界限法的异同

  • 相同点:都是一种在问题的解空间树T中搜索问题解的算法。

  • 不同点:

(1)求解目标不同;(2)搜索方式不同;(3)对扩展结点的扩展方式不同;(4)存储空间的要求不同。

2. 典型案例

2.1 装载问题

2.1.1 问题描述

给定n个集装箱要装上一艘载重量为c的轮船,其中集装箱i的重量为wi。集装箱装载问题要求确定在不超过轮船载重量的前提下,将尽可能多的集装箱装上轮船。

由于装载问题是从n个集装箱里选择一部分集装箱,假设解向量为X(x1, x2, …, xn),其中xi∈{0, 1}, xi =1表示集装箱 i 装上轮船, xi =0表示集装箱 i 不装上轮船。

数据案例为 c=34 n=3 重量分别为 21、20、5 时的子集树如下:

image-20210609113355589

2.2 .2 约束函数与上界函数

  • 约束函数:cw+w[i] < c

  • 上界函数:cw+r <= bestw

    cw:当前轮船的载重量 w[i]:第 i 个集装箱的重量

    c:轮船的载重量 bestw:当前最有载重量

r :剩余集装箱的重量(注意,不是轮船的剩余载重量),比如说上图中处于结点E时,第一个集装箱装入,第二个不装入,那么 r 的值就等于剩余最后一个集装箱的载重量5;

那么已经确定装入的集装箱重量cw加上剩下所欲集装箱的重量为:cw + r = 21 + 5 = 26。

约束函数表示,处于某个当前结点时,当前的cw值加上下一集装箱的重量要小于轮船的载重量,否则就不能装入轮船,进行剪枝。

上界函数表示,当前结点的cw值加上剩余轮船载重量r所能得到的重量要大于当前bestw值,否则即使再从右子树向下搜索,也得不到大于当前bestw的值。

2.2.3 举例解释

n=4, c=12, w=[8, 6, 2, 3],bestw初值=0。

​ 重点解释约束和上界函数剪枝:

约束函数:对于结点B,扩展结点D时,D的cw=14,此时大于轮船载重量c,因此不在进行左子树的扩展,进行剪枝。

上界函数:对于结点C,在进行右子树G扩展时,G结点的cw=0,bestw = 11,r =2+3 = 5(第一个和第二个集装箱不装入,后面的全部装入),所以cw+r < bestw,则不在进行右子树的扩展,剪去结点G。

image-20210609114156270

2.2.4 复杂度

  • 时间复杂度:O(2^n)

2.2 n皇后问题

2.2.1 思路总结

在对n个皇后向nxn棋盘放置的过程,就相当于对1,2,3…,n个皇后针对棋盘每一行的序列进行全排列,在这些全排列中只有一部分符合约束调件,这些符合约束条件的排列就是可行方案。

2.2.2 代码实现

/*
    @author cc
    @date 2021/4/26
    @Time 10:03
    To change this template use File | Settings | File Templates.
    回溯算法解决n后问题
*/
#include "iostream"
using namespace std;

#define NUM 20
int n;          // n为皇后的个数,以及棋盘为nxn大小
int x[NUM];     // 第NUM列皇后所在x[NUM]行
int sum;        // 当前已找到的可行方案数

// 约束函数,实现对皇后位置的检查
// 形参t是回溯的深度
inline bool Place(int t){
    int i;
    for (i = 1; i < t; ++i)
        // abs(t-i) == abs(x[i]-x[t]) 用来判断皇后是否在同一条对角线上
        // x[i] == x[t] 用来判断皇后是否在同一行上
        if ( ((abs(t-i)) == abs(x[i]-x[t])) || (x[i] == x[t]) )
             return false;   // 如果两个皇后在同一对角线或同一行则返回false
    return true;    // 如果都不在就放回true
}

// 形参t是回溯的深度,从1开始
void Backtrack(int t){
    int i;
    // 回溯深度大于皇后的个数时,获得一个可行方案
    if (t>n){
        sum++;
        for (i = 1; i <= n ; ++i) {
            cout << x[i] << " ";
        }
        cout << endl;
    } else{
        // 将皇后从棋盘每行的第一个位置开始防止,放置完成对其位置进行检查,
        // 如果与之前的皇后不冲突,则进行下一行皇后位置的放置,即另t+1跳转到下一行,继续从第一个位置开始,直到回溯的深度大于n,就获得到一个可行方案
        // 然后进行回退,返回到上一层,另i继续增加,即皇后的位置在上一行中继续向后移动,探寻下一个可行方案,直到回退到顶层
        for (i = 1; i <= n; ++i) {
            x[t] = i;
            if (Place(t))   Backtrack(t+1);
        }
    }
}

int main(){
    cout << "请输入皇后的个数:" << endl;
    cin >> n;
    Backtrack(1);
    cout << "总的可行方案为:" << sum <<  "个" << endl;
    return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

krain.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值