回溯法(基础版)

2023-11-04

“能进则进,不能进则换,不能换则退,退一步海阔天空。”


算法适用问题

搜索问题(求解的个数)/最优解问题

算法思想步骤

深度优先搜索

  1. 定义解空间
    解的组织形式:一个n元组{x 1 {_1} 1,x 2 {_2} 2,x 3 {_3} 3,……,x n {_n} n}。
    显约束:对解分量x i {_i} i取值范围的限定,控制解空间的大小。
  2. 确定解空间的组织结构
    通常用解空间树形象表示。一般为子集树、排列数、m叉树等等。
  3. 搜索解空间
    根据隐约束即剪枝函数(约束函数->找可行解和限界函数->找最优解)搜索解,若当前结点满足条件,继续向下搜索,若不满足返回上一级结点。
子集树、m叉树:
void backtrack(int t)          //t表示递归深度,即当前扩展节点在解空间树的深度
{ 
    if ( t > n ) output(x);    //n控制递归深度,如果算法已经搜索到叶节点,记录输出可行解X
    else
    {
        for(int i = f(n,t) ; i <= g(n,t) ; i++)  //在深度t,i从未搜索过得起始编号到终止编号
        {
            x[t] = h(i);       //查看i这个点的值是否满足题目的要求
            if( constraint(t) && bound(t)) 
                backtrack(t+1)
       //constraint(t)为true表示在当前扩展节点处x[1:t]的取值满足问题的约束条件;
       //bound(t)为true表示当前扩展节点取值没有使目标函数越界;
       //为true表示还需要进一步的搜索子树,否则减去子树。
         }
    }
}
排列树:
void backtrack(int t)          //t表示递归深度,即当前扩展节点在解空间树的深度
{ 
    if ( t > n ) output(x);    //n控制递归深度,如果算法已经搜索到叶节点,记录输出可行解X
    else
    {
        for(int i = f(n,t) ; i <= g(n,t) ; i++)  //在深度t,i从未搜索过得起始编号到终止编号
        {
            swap(x[t],x[i]);
            if( constraint(t) && bound(t)) 
                backtrack(t+1)
            swap(x[t],x[i]);
       //constraint(t)为true表示在当前扩展节点处x[1:t]的取值满足问题的约束条件;
       //bound(t)为true表示当前扩展节点取值没有使目标函数越界;
       //为true表示还需要进一步的搜索子树,否则减去子树。
         }
    }
}

基础题目


A.装载问题

有两艘货船,载重分别为w1、w2,物品总重量不超过载重总量w1+w2,问物品是否都可以装下。如,w1=w2=10,物品c1=c2=9,c3=2,则无法装下;c1=c2=5,c3=10,则可以装下。
基本思路:问题可以等价于,使第一艘货船尽可能装满,看剩下的物品能否装入第二艘货船

  1. 定义解空间
    开始装第一艘货船, 对于每个物品只有拿与不拿两种选择。所以解空间为{x 1 {_1} 1,x 2 {_2} 2,x 3 {_3} 3,……,x n {_n} n}。显约束x i {_i} i=0或1,i=1,2,3…,n。有2 n {^n} n种解。
  2. 确定解空间树
    子集树。
  3. 搜索解空间
    约束条件:判断装入第一艘货船的物品总重量是否超出第一艘货船的容量。 ∑ i = 1 n c i x i \sum_{i=1}^{n}c_{i}x_{i} i=1ncixi < = W 1 <=W_{1} <=W1
    限界条件:如果要选择第t个分量,目前的重量加上剩余的所有重量,都没有当前最优解的重量大,放弃向下搜索,剪枝。
    c w = ∑ i = 1 t − 1 c i x i cw=\sum_{i=1}^{t-1}c_{i}x_{i} cw=i=1t1cixi(当前载重量)
    r = ∑ i = t n c i x i r=\sum_{i=t}^{n}c_{i}x_{i} r=i=tncixi(剩余物品总重量)
    bestw=当前最优解
    ( c w + r > b e s t w ) (cw+r>bestw) (cw+r>bestw)
#include<bits/stdc++.h>
#define M 105
using namespace std;

int n; //物品个数 
double W1,W2; //两艘货船载重 
double cw; //当前重量 
double bestw;  //将第一艘货船尽可能装满 
double c[M],x[M]; //每个物品的重量 

double Bound(int i) //计算上界cw+r
{
	double r=0;
	while(i<=n)
	{
		r=r+c[i];
		i++;
	}
	return cw+r; 
} 
 
void Backtrack(int t)
{
	if(t>n) //找到一个最优解 
	{
		bestw=cw;
		return ;
	}
	if(cw+c[t]<=W1) //满足约束条件,搜索放入该物品的子树 
	{
		x[t]=1;
		cw=cw+c[t];
		Backtrack(t+1);
		cw=cw-c[t];
	}
	if(Bound(t+1)>bestw) 
	{
		x[t]=0;
		Backtrack(t+1);
	}
 } 
 
 void Init(double W,int n) //初始化 
 {
 	cw=0;
 	bestw=0;
 	double sumw=0;
 	for(int i=1;i<=n;i++)
 	sumw=sumw+c[i];
 	if(sumw<=W)
 	{
	 cout<<"Yes"<<endl;
	 return ;		
	}
	Backtrack(1); 
	if(sumw-bestw<=W2)
	{
		cout<<"Yes"<<endl;
		return ;
	}
	else
	{
	    cout<<"No"<<endl;
		return ;	
	}
}

int main()
{
	cout<<"物品总数为:";
	cin>>n;
	cout<<"两艘货船的载重为:";
	cin>>W1>>W2;
	cout<<"依次输入每个物品的重量"<<endl; 
	for(int i=1;i<=n;i++)
	cin>>c[i];
	Init(W1,n);
	return 0;	 
}

B.0-1背包问题

给定n种物品和一背包。物品 i 的重量为 wi,其价值为 vi,背包的容量为 W。问应该如何选择装入背包中的物品,使得装入背包中物品的总价值最大?

  1. 定义解空间
    对于每个物品只有拿与不拿两种选择。所以解空间为{x 1 {_1} 1,x 2 {_2} 2,x 3 {_3} 3,……,x n {_n} n}。显约束x i {_i} i=0或1,i=1,2,3…,n。有2 n {^n} n种解。
  2. 确定解空间树
    子集树。在搜索的时候,左节点是可行节点的时候,搜索就进入左子树。当右子树可能包含最优解的时候,就进入右子树搜索。否则减去右子树。
  3. 搜索解空间
    约束条件:判断装入购物车的物品总重量是否超出购物车的容量。
    ∑ i = 1 n w i x i \sum_{i=1}^{n}w_{i}x_{i} i=1nwixi<=W)
    限界条件:对于任意一个中间结点z,从根节点到z的分支状态已经确定,从z到子孙结点的分支状态是不确定的。即若z所在解空间树的层数是t,则第一种物品到第t-1种物品的状态已经确定,第t-1种物品到第n种物品的状态还没有确定。前t种物品的状态确定后,当前已装入购物车的总价值用cp表示。第t+1种物品到第n种物品的具体状态未定,假设全部装入购物车,第t+1种物品到第n种物品的总价值用rp表示。所以cp+rp是从跟出发经过中间节点z的可行解的价值上界。如果价值上界小于或等于当前搜索的最优值(bestp,初值为0),则说明从z向下继续搜索不可能得到一个更优的解,即没有搜索下去的必要,反之继续从z向下搜索。即限界条件为 cp+rp>bestp。

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

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

#include<iostream>
#define M 105
using namespace std;

int n;  //物品总数 
double W; //购物车容量 
int x[M];  //当前每个物品的状态 
double w[M],v[M]; // 物品的重量与价值 
double cw;  //购物车当前重量 
double cp;  //购物车当前价值 
double bestp;  //当前最优价值 
int bestx[M];  //当前最优解 

double Bound(int i) //计算上界(已装物品价值+剩余物品i~n总价值) 
{
	double rp=0;
	while(i<=n) //依次计算剩余物品价值 
	{
		rp=rp+v[i];
		i++;
	}
	return cp+rp; //返回上界 
}

void Backtrack(int t)  // 当前判断结点在第t层 
{
	if(t>n) //已到达叶子结点 
	{
		for(int j=1;j<=n;j++)
		{
			bestx[j]=x[j];
		}
		bestp=cp; //保存当前最优解 
		return ;
	}
	 if(cw+w[t]<=W) //当前结点满足约束条件继续向左分支搜索 
	 {
	 	x[t]=1;
		cw=cw+w[t];
		cp=cp+v[t];
		Backtrack(t+1);
		cw=cw-w[t];
		cp=cp-v[t]; 
	 }	 
	 if(Bound(t+1)>bestp) //当前结点满足限界条件继续向右分支搜索 
	 {
	 	x[t]=0;
		Backtrack(t+1); 
	 } 
}

void Knapsack(double W,int n)
{
	//初始化
	cw=0;cp=0;bestp=0;
	double sumw=0.0,sumv=0.0; //统计物品总价值与总重量 
	for (int i=1;i<=n;i++)
	{
		sumv=sumv+v[i];
		sumw=sumw+w[i];
	}
	if(sumw<=W)
	{
		bestp=sumv;
		cout<<"放入购物车最大价值为"<<bestp<<endl;
		cout<<"所有物品均放入购物车"; 
		return;
	} 
	Backtrack(1);
	cout<<"放入购物车最大价值为"<<bestp<<endl;
	cout<<"放入购物车的物品序号为"<<endl;
	for(int i=1;i<=n;i++)
		if(bestx[i]) cout<<i<<" ";
}

int main()
{
	cout<<"请输入物品个数";
	cin>>n;
	cout<<"请输入购物车容量";
	cin>>W;
	cout<<"请依次输入每个物品的重量与价值";
	for(int i=1;i<=n;i++)
	{
		cin>>w[i]>>v[i];
	}
	Knapsack(W,n);
	return 0;
 } 

C.N皇后问题

设计一种算法,打印 N 皇后在 N × N 棋盘上的各种摆法,其中每个皇后都不同行、不同列,也不在对角线上。这里的“对角线”指的是所有的对角线,不只是平分整个棋盘的那两条对角线。

  1. 定义解空间
    以行为主导
    n元组{x 1 {_1} 1,x 2 {_2} 2,x 3 {_3} 3,……,x n {_n} n}。x i {_i} i表示第i个皇后在第i行第x i {_i} i列,显约束x i {_i} i=1,2,…,n。
  2. 确定解空间树
    m(m=n)叉树,深度为n。
  3. 搜索解空间
    约束条件:在第i行放置第i个皇后时,这个皇后不能与j行皇后同列、同斜线。 即**x i {_i} i!=x j {_j} j ∣ i − j ∣ |i-j| ij!=|x i {_i} i- x j {_j} j| ** 。
    限界条件:本题不求最优解只求可行解所以无限界条件。

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

在这里插入图片描述

#include<bits/stdc++.h> 
#define M 105 
using namespace std;
 
int n; //n皇后
int x[M]; //存放每个皇后存放列数
int countn=0; //可行解的个数 
 
bool Place(int t)//约束函数,判断第t个皇后能否放在第i个位置
{
	bool ok=true;
	for(int j=1;j<t;j++)
	{
		if(x[t]==x[j]||t-j==fabs(x[t]-x[j]))
		{
			ok=false;
			break;
		}
	}
	return ok;
}

void Backtrack(int t)//搜索求解 
{
	if(t>n) //找到一个可行解 
	{
		countn++;
		for(int i=1;i<=n;i++ ) //打印位置 
		cout<<x[i]<<" ";
		cout<<endl;
	}
	else
	{
		for(int i=1;i<=n;i++) //分别判断第t层的n个分支 
		{
			x[t]=i;
			if(Place(t))
			Backtrack(t+1); 
		 }		 
	}
}

int main()
{
	cin>>n;
	Backtrack(1);
	cout<<"有"<<countn<<"个可行解"; 
}  

D.涂色问题

给定无向连通图G=(V, E)和m种不同的颜色,用这些颜色为图G的各顶点着色,每个顶点着一种颜色。是否有一种着色法使G中相邻的两个顶点有不同的颜色。找出所有着色方案。

  1. 定义解空间
    n元组{x 1 {_1} 1,x 2 {_2} 2,x 3 {_3} 3,……,x n {_n} n}。x i {_i} i表示第i个顶点是第几种颜色,显约束x i {_i} i=1,2,…,m。
  2. 确定解空间树
    m叉树,深度为n。
  3. 搜索解空间
    约束条件:假设当前扩展结点处于解空间树第t层,则第1到t-1个结点的色号已经确定,继续拓展则需判断第t个结点的色号是否与前t-1个结点中与其有边相连的结点颜色相同。若不相同用此色号;若相同,换下一个色号尝试。
    限界条件:只需寻找可行解,无需限界条件。

图解

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

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

#include<bits/stdc++.h>
#define M 105
using namespace std;

int sum;  //可行解的个数
int x[M];  //记录每一次可行解
int n,m,edge;  //顶点数、颜色数、边数
int Map[M][M]; //邻接矩阵表示图 

bool OK(int t) //约束条件
{
	for(int j=1;j<t;j++)
	{
		if(Map[t][j])  //t与j有边相连
		{
			if(x[j]==x[t])  //判断t与j色号是否相同
			return false; 
		} 
	}
	return true;
}

void Backtrack(int t)  //搜索函数
{
	if(t>n)  //找到一个可行解 
	{
		sum++;
		cout<<"第"<<sum<<"种方案为:" ;
		for(int i=1;i<=n;i++)
		 cout<<x[i]<<" ";
		cout<<endl;
	}
	else
	{
		for(int i=1;i<=m;i++) //每个结点尝试m种颜色 
		{
			x[t]=i;
			if(OK(t))
			 Backtrack(t+1);
		}
	}
}

void CreatMap()
{
	int u,v;
	cout<<"请输入无向图的边数:";
	cin>>edge;
	memset(Map,0,sizeof(Map)); //邻接矩阵里面数据初始化为0 
	cout<<"请依次输入有边相连的两个结点"<<endl;
	for(int i=0;i<edge;i++)
	{
		cin>>u>>v;
		Map[u][v]=Map[v][u]=1; 
	} 
	Backtrack(1);
}

int main() 
{
	cout<<"请输入顶点数:";
	cin>>n;
	cout<<"请输入颜色数:";
	cin>>m;
	CreatMap();
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

回溯法(基础版) 的相关文章

  • 面试专题 - Zookeeper

    因为zookeeper 后面简称zk 是一个功能比较优秀且强大的分布式组件 使用场景也很多 很受欢迎 所以相对来说关于zk的问题也就很多 下面总结归纳一些常见的面试问题 1 请简述ZooKeeper的选举机制 2 客户端对zk的server
  • 有关循环Random随机数重复的解决方案

    在做项目时 我逻辑服循环里面使用random时发现会随机出重复数 我在网上查了资料 然后使用的方法 float objRandomCount new Random Guid NewGuid GetHashCode Next 0 num 可以
  • 下拉列表框组件Spinner 简述及其简单应用

    下拉列表框组件Spinner 提供一系列下拉选项供用户选择 右下角有一个F角箭头 点击后显示出选项 下拉列表框组件Spinner 的简单应用 要求 建立一个下拉列表并填充内容 在下列列表的右边建立一个按钮 点击时显示所选中的内容 一 在ac

随机推荐

  • 【华为OD机试】分班【2023 B卷

    华为OD机试 真题 点这里 华为OD机试 真题考点分类 点这里 题目描述 幼儿园两个班的小朋友在排队时混在了一起 每位小朋友都知道自己是否与前面一位小朋友是否同班 请你帮忙把同班的小朋友找出来 小朋友的编号为整数 与前一位小朋友同班用Y表示
  • 国产操作系统迎来新机遇 统一应用商店成软肋

    今天上午 中国工程院院士倪光南在某桌面操作系统发布会上表示 政府欲采购国产操作系统代替Windows 8系统 意味着政府和央企等部门将会大量使用国产操作系统 会给国内相关企业带来新的发展机遇 据悉 目前操作系统领域几乎已被国外科技公司所垄断
  • python菜鸟教程 pdf下载-Python实战-从菜鸟到大牛的进阶之路 pdf完整版

    Python是一种解释型 面向对象 动态数据类型的高级程序设计语言 现在它已经成为最受欢迎的程序设计语言之一 本专题收录了Python编程实战教程 分享给大家 适用人群 Python 进阶学习者 Web 开发程序员 运维人员 有志于从事互联
  • C++ day6

    将栈和队列封装成模板类 栈 include
  • 控制算法之PID算法

    控制算法之PID算法 从入门到理解到应用 一发入魂 云 社区 腾讯云 tencent com
  • 数据库基础(面试常见题)

    数据库基础 面试常见题 一 数据库基础 1 数据抽象 物理抽象 概念抽象 视图级抽象 内模式 模式 外模式2 SQL语言包括数据定义 数据操纵 Data Manipulation 数据控制 Data Control 数据定义 Create
  • dedecms单独调用指定文章

    dede arclist idlist 指定ID limit 0 1 a href field title a dede arclist
  • 过滤器Filter,登陆验证,过滤敏感词,动态代理,Listener

    Filter 过滤器 概念 生活中的过滤器 净水器 空气净化器 土匪 web中的过滤器 当访问服务器的资源时 过滤器可以将请求拦截下来 完成一些特殊的功能 过滤器的作用 一般用于完成通用的操作 如 登录验证 统一编码处理 敏感字符过滤 快速
  • 前端npm和yarn更换国内淘宝镜像

    由于npm和yarn自带镜像是国外的 下载各种包比较慢 针对国内开发的小伙伴 如果没有科学上网的话 通常都会换一下镜像源 让开发下载各种包飞起来 以下是配置国内 淘宝镜像 提升下载速度的具体方法 赶紧收藏起来吧 关注 技术宅小Y 获取更多新
  • 如何调试JavaScript代码

    1 通过alert 来查看程序中的变量 由此也可以推断出程序跑到哪里就报错 用法 alert 1 弹出窗口显示1 var a 2 alert a a 弹出窗口显示a 2 2 添加debugger来调试javaScript 比较推荐这个 实用
  • PCB走线宽度

    结论 1A电流 至少10mil 建议15mil 2A电流 至少30mil 建议50mil 3A电流 至少60mil 建议100mil 大于3A 建议采用铺铜或开窗的形式 小于10mil线宽 建议电流小于0 1A
  • 如果有一条告警流量你会怎么分析,请详细说明?

    先要判断攻击有没有成功 是攻击成功的告警 还是攻击不成功但是真实的攻击 看规则的告警的名称 分析攻击源IP和目的IP 如果攻击源IP是内网的话 则可能为有关键特征的业务系统 被判为恶意攻击 内网可能沦陷 已被入侵 可能是设备使用盗版软件或者
  • Yolov5的安装配置与使用

    文章目录 一 下载Yolov5 1 下载Yolov5源码 2 下载Yolov5预训练模型 二 安装Yolov5 三 测试Yolov5 1 Img图片测试 2 Video视频测试 3 摄像头测试 三 小结 四 参考链接 在下载配置Yolov5
  • Echarts 实现两个图表联动

    init obj pageSource var that this console log obj pageSource this chart this echarts init document getElementById this i
  • 使用C++ Eigen库求解线性方程组Ax=b

    Eigen http eigen tuxfamily org 是常用的 C 矩阵运算库 具有很高的运算效率 大部分 需要在 C 中使用矩阵运算的库 都会选用 Eigen 作为基本代数库 例如 Google Tensorflow Google
  • 批量读取csv文件指定列

    目录 一 算法原理 二 代码实现 三 注意事项 一 算法原理 在读取csv文件进行点云处理的时候 常常需要跳过表头 并且进行批量读取 本代码 将每行数据记录为一个数组 并将多个csv文件合并记录 在使用中 需要自己修改想要提取的列数以及定义
  • FM33LG0XX-16位基本定时器

    FM33LG0包含1个16位基本定时器 基本定时器包含一个16bit自动重载计数器及一个可编程预分频器 基本定时器主要用来产生系统时基 也可以产生触发事件来驱动ADC采样 测试代码如下 void BSTIM Init uint16 t pr
  • 【转载收藏】Unity预计算实时GI

    初步介绍 新年假期结束了 想不想掌握一个新技能迎接全新的一年呢 不妨来阅读一下Unity预计算实时GI系列文章 本文是该系列的第一篇 在Unity中有两种区别很大的技术被用于计算全局光照GI或光源反射 它们就是烘焙全局光照 Baked GI
  • 【一文搞懂】FD_SET的使用

    阅读大概需要十分钟 绝对干货 看完还没搞懂你找我 随便查一下 可以看到对FD SET的说明如下 一个long类型的数组 提供给select 机制使用的一种数据结构 主要功能是建立联系 其中每一个数组元素都能与任意一个打开的句柄 socket
  • 回溯法(基础版)

    能进则进 不能进则换 不能换则退 退一步海阔天空 文章目录 算法适用问题 算法思想步骤 基础题目 A 装载问题 B 0 1背包问题 C N皇后问题 D 涂色问题 算法适用问题 搜索问题 求解的个数 最优解问题 算法思想步骤 深度优先搜索 定