算法设计与分析复习--回溯(一)

本文详细介绍了回溯法的工作原理,包括深度优先搜索策略、子集树和排列树算法框架,以及左/右剪枝策略在子集和问题、装载问题中的应用。通过实例演示了如何在贪心算法背景下理解和解决0-1背包问题和旅行商问题(TSP)。
摘要由CSDN通过智能技术生成

上一篇

算法设计与分析复习–贪心(二)

回溯法性质

类似穷举的搜索尝试过程,在搜索尝试过程中寻找问题的解,组织得井井有条(避免遗漏), 高效(剪裁避免不必要搜索)

使用深度优先的搜索策略(DFS + 剪枝)

回溯法的阶梯框架:

  1. 子集树算法框架
  2. 排序树算法框架

在这里插入图片描述
在这里插入图片描述
结点类型:

  1. 活结点:自身已生成但是其儿子还没有全部生成
  2. 拓展结点:正在产生儿子的结点
  3. 死结点:不满足约束或所有儿子已经产生,不能向纵深方向移动

为了避免生成那些不可能产生最优解的问题状态,要不断利用限界函数,处死结点=>剪枝

剪枝策略:

  1. 可行性剪枝,左剪枝
  2. 最优性剪枝,右剪枝
  3. 交换搜索顺序,排序方式变化

解空间类型:

  1. 子集树:所给的问题是从n个元素集合S中找出满足某种性质的子集。
    在这里插入图片描述

  2. 排列数:所给问题是确定n个元素满足某种性质的排列。
    在这里插入图片描述
    采用交换方式实现的全排列(顺序有点问题)

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 10;

int a[N], n;
int path[N];

void dfs(int* ans, int k)
{
    if (k == n - 1){
        for (int i = 0; i < n; i ++)
            printf("%d ", ans[i]);
        puts("");
    }
    
    for (int i = k; i < n; i ++){
        swap(ans[k], ans[i]);
        dfs(ans, k + 1);
        swap(ans[k], ans[i]);
    }
}

int main()
{
    scanf("%d", &n);
    for (int i = 0; i < n; i ++) path[i] = i + 1;
    
    dfs(path, 0);
    return 0;
}

在这里插入图片描述
在这里插入图片描述

基于子集树框架的问题求解:

  1. 子集和问题
  2. 装载问题
  3. 0-1背包问题
  4. 图的m着色问题

基于排列树框架的问题求解:

  1. 旅行商问题(TSP)
  2. N皇后问题

子集和问题

问题描述:给定n个不同正实数的集合W = {w(i) | 1 <= i <= n}和一个正整数M, 要求找到子集S使得求和为M。

子集和问题要求出所有可行解

左剪枝:求和不超过M => cw + a[i] <= M
右剪枝:当前已选的价值与剩余的数的价值的和要大于M否则不可能找到 => cw + rw >= M

#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>

using namespace std;

const int N = 110;

int a[N], n, M, cw, rw;
vector<int> ans;

void dfs(int k)
{
    if (k == n)//根节点是0叶结点就是n
    {
        if (cw == M){
            for(auto i : ans)
                printf("%d ", i);
            puts("");
        }
        return;
    }
    rw -= a[k];
    if (cw + a[k] <= M){//左剪枝条件
        cw += a[k];
        ans.push_back(a[k]);
        dfs(k + 1);//向左走
        ans.pop_back();
        cw -= a[k];
    }
    if(rw + cw >= M)//右剪枝条件
        dfs(k + 1);//向右走
        
    rw += a[k];
}

int main()
{
    scanf("%d%d", &n, &M);
    
    for (int i = 0; i < n; i ++) scanf("%d", &a[i]), rw += a[i];
    
    dfs(0);
    return 0;
}

在这里插入图片描述

装载问题

和贪心中的装载问题不同
问题描述:n个集装箱要装到2艘载重量分别c1,c2的货轮,其中集装箱 i i i 的重量 为 w i w_i wi。要求找到合理的装载方案将这n个货箱装上这2艘轮船。

要使两个船都装上,可以考虑将第一个船尽可能装满,然后将剩下的货物装在第二艘船上,如果没超重就是可行的,将考虑两个船的问题转换成考虑一个。

为找到将左边轮船撞得最满的方案,用bestw进行限界

#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>

using namespace std;

const int N = 110;

int a[N], n, c1, c2;
int cw, bw, rw, other;
vector<int> ans;

void dfs(int k)
{
    if (k == n){
        bw = cw;
        if(other > c2) return;//other是另一艘船货物重量
        for (auto i : ans)
            printf("%d ", i);
        puts("");
        return;
    }
    
    rw -= a[k];
    if (cw + a[k] <= c1)
    {
        cw += a[k];
        ans.push_back(a[k]);
        dfs(k + 1);
        ans.pop_back();
        cw -= a[k];
    }
    if (cw + rw >= bw){
        other += a[k];
        dfs(k + 1);
        other -= a[k];
    }
    rw += a[k];
    
}

int main()
{
    scanf("%d%d%d", &n, &c1, &c2);
    
    for (int i = 0; i < n; i ++) scanf("%d", &a[i]), rw += a[i];
    
    dfs(0);
    return 0;
}

在这里插入图片描述

下一篇

算法设计与分析复习–回溯法(二)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

ˇasushiro

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

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

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

打赏作者

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

抵扣说明:

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

余额充值