一、实验目的:
掌握用回溯法解题的算法框架;根据回溯法解决实际问题。
二、实验所用仪器及环境
Windows 7 以上操作系统,PC机,codeblocks环境
三、实验原理:
算法总体思想:回溯法的基本做法是搜索,或是一种组织得井井有条的,能避免不必要搜索的穷举式搜索法。这种方法适用于解一些组合数相当大的问题。回溯法在问题的解空间树中,按深度优先策略,从根结点出发搜索解空间树。算法搜索至解空间树的任意一点时,先判断该结点是否包含问题的解。如果肯定不包含,则跳过对该结点为根的子树的搜索,逐层向其祖先结点回溯;否则,进入该子树,继续按深度优先策略搜索。
(1)问题的解向量:回溯法希望一个问题的解能够表示成一个n元式(x1,x2,…,xn)的形式。
(2)显约束:对分量xi的取值限定。
(3)隐约束:为满足问题的解而对不同分量之间施加的约束。
(4)解空间:对于问题的一个实例,解向量满足显式约束条件的所有多元组,构成了该实例的一个解空间。
基本步骤:
(1)针对所给问题,定义问题的解空间,主要有子集树(如图1所示)和排列树(如图2所示)两种解空间形式。
(2)确定易于搜索的解空间结构;
(3)以深度优先方式搜索解空间,并在搜索过程中用剪枝函数避免无效搜索。
图1 子集树
图2 排列树
四、实验内容:
1、0-1背包问题
有N件物品和一个容量为V的背包。第i件物品的重量是w[i],价值是v[i]。求解将哪些物品装入背包可使这些物品的重量总和不超过背包容量,且价值总和最大。要求每个物品要么放进背包,要么不放进背包。
#include <iostream>
#include<bits/stdc++.h>
using namespace std;
#define maxn 100000
int w[maxn]={0};
int v[maxn]={0};
int ans[maxn]={0};
int a[maxn]={0};
int n;
int Maxw,Sumv;
int huisu(int t,int Sw,int Sv)
{
if(t>=n)///最大层了
{
if(Sumv<Sv)///永远取最大值
{
Sumv=Sv;
for(int i=0;i<n;i++)
{
ans[i]=a[i];
}
}
}else{
for(int i=0;i<=1;i++)//0-1
{
a[t]=i;///临时选择方式
Sw=Sw+i*w[t];
Sv=Sv+i*v[t];
if(Sw<=Maxw)///目前的实际重量比最大重量(重量上限)小才行
{
huisu(t+1,Sw,Sv);
}else{
Sw-=i*w[t];///拿出来
Sv-=i*v[t];
}
}
}
}
int main()
{
cin>>n>>Maxw;
for(int i=0;i<n;i++)
{
cin>>v[i];
}
for(int i=0;i<n;i++)
{
cin>>w[i];
}
huisu(0,0,0);///0号层,重量0,价值0
for(int i=0;i<n;i++)
{
cout<<ans[i]<<" ";/// 最优选择
}
cout<<endl;
cout<<Sumv;
return 0;
}
//void backtrack(int k)
//{
// if(到达边界) 更新或者输出结果
// else
// {
// 对于每一种可能进行操作(for循环 i)
// {
// if(限定条件合法)
// backtrack(k+i);
// }
// }
//
//}
//n 代表的是物品个数
//
//MaxWight 背包能装的最大体积
//
//SumValue 物品的最大价值
//
//ans 数组,最终选择方式
//
//a 数组,临时选择方式
//
//
//
//思路:物品只有两种选择,放或者不放,构造二叉树,n 个物品就是 n 层树,然后进行最优值更新。
//in
//4 10
//1 3 5 9
//2 3 4 7
//out
//0 1 0 1
//12
2、旅行售货员问题
设有一个售货员从城市1出发,到城市2,3,…,n去推销货物,最后回到城市1。假定任意两个城市i,j间的距离dij(dij=dji)是已知的,问他应沿着什么样的路线走,才能使走过的路线最短。
#include <iostream>
#include<bits/stdc++.h>
using namespace std;
#define maxn 10000
int n;
int dis[maxn][maxn]={0};
int mindis=maxn;
int x[maxn];
int bestx[maxn];
int cw;
///0- n-1 城
int BackTrack(int t)
{
if(t>=n)
{
for(int i=1;i<=n;i++)
{
bestx[i]=x[i];
}
mindis=cw+dis[x[n-1]][x[n]]+dis[x[n]][1];///最优值走到头才更新
return mindis;
}else{
for(int i=t;i<=n;i++)
{
if(cw+dis[x[t-1]][i]<mindis||mindis==maxn)
{
swap(x[t],x[i]);///遍历全排列
cw+=dis[x[t-1]][x[t]];
BackTrack(t+1);
cw-=dis[x[t-1]][x[t]];
swap(x[t],x[i]);
}
}
}
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
cin>>dis[i][j];
for(int i=1;i<=n;i++)
{
x[i]=i;
}
BackTrack(2);
cout<<mindis;
return 0;
}
//void backtrack(int k)
//{
// if(到达边界) 更新或者输出结果
// else
// {
// 对于每一种可能进行操作(for循环 i)
// {
// if(限定条件合法)
// backtrack(k+i);
// }
// }
//
//}
//#回溯法#回溯法+排序树为什么swap?https://blog.csdn.net/qq_37967797/article/details/92624531?utm_medium=distribute.pc_aggpage_search_result.none-task-blog-2~all~first_rank_v2~rank_v25-1-92624531.nonecase&utm_term=%E5%9B%9E%E6%BA%AF%E6%B3%95%E6%8E%92%E5%88%97%E6%A0%91swap%E4%BD%9C%E7%94%A8&spm=1000.2123.3001.4430
//4
//-1 30 6 4
//30 -1 5 10
//6 5 -1 20
//4 10 20 -1
//25
3、图的m着色问题
(1)问题描述:给定无向连通图G和m种不同的颜色。用这些颜色为图G的各顶点着色,每个顶点着一种颜色。是否有一种着色法使G中每条边的2个顶点着不同颜色。若这个图不是m可着色的,就输出no。若是则输出每种着色方案和可行方案数。
(2)样例1
输入:当输入如下图时
顶点数:5
颜色数 4
边数:9
各边顶点:
1 2
1 3
1 4
2 3
2 4
2 5
3 4
3 5
4 5
输出:
样例2:
(3)实验步骤
(a)首先将给定的图利用抽象图表示出来
(b)判断该节点k当前的着色是否符合条件,需要判断x[k]与k结点其他相邻节点h的x[h]是否相等
(c)回溯过程,若此时的结点值已经大于结点总数,代表已经着色完成,并且找到了一种可行解,此时可以将可行解数+1
(d)回溯从最后一个结点往上回溯,并一层一层更改结点至其他可用着色,以此来找到所有的填色方案。
#include <iostream>
#include<bits/stdc++.h>
using namespace std;
#define maxn 10000
int n,m,sum;
int w[maxn][maxn]={0};
int bestw=maxn;
int x[maxn];///全排列的缓冲?
int bestx[maxn];
int cw;
int tong(int k){
for(int i=1;i<k;i++)
{
if(w[k][i]==1&&x[i]==x[k])
{
return 0;
}
}
return 1;
}
void BackTrack(int t)
{
if(t>n)
{
for(int i=1;i<=n;i++)
{
cout<<x[i]<<" ";
}
cout<<endl;
sum++;
}else{
for(int i=1;i<=m;i++)
{
x[t]=i;
if(tong(t)==1)
{
//cout<<"*";
BackTrack(t+1);
}
x[t]=0;
}
}
}
int main()
{
cin>>n;
int bian;
cin>>m;
cin>>bian;
// for(int i=0;i<=n;i++)
// for(int j=0;j<=n;j++)
// w[i][j]=-1;
for(int i=1;i<=bian;i++)
{
int x,y;
cin>>x>>y;
w[x][y]=1;
w[y][x]=1;
}
// for(int i=0;i<=n;i++)
// {for(int j=0;j<=n;j++)
// cout<<w[i][j]<<" ";
// cout<<endl;}
///format
for(int i=1;i<=n;i++)
cout<<i<<" ";
cout<<endl;
for(int i=1;i<=n;i++)
cout<<"--";
cout<<endl;
//
BackTrack(1);
cout<<sum;
return 0;
}
//void backtrack(int k)
//{
// if(到达边界) 更新或者输出结果
// else
// {
// 对于每一种可能进行操作(for循环 i)
// {
// if(限定条件合法)
// backtrack(k+i);
// }
// }
//
//}.
//in1
//5
//4
//9
//1 2
//1 3
//1 4
//2 3
//2 4
//2 5
//3 4
//3 5
//4 5
//out1
//1 2 3 4 5
//----------
//1 2 3 4 1
//1 2 4 3 1
//1 3 2 4 1
//1 3 4 2 1
//1 4 2 3 1
//1 4 3 2 1
//2 1 3 4 2
//2 1 4 3 2
//2 3 1 4 2
//2 3 4 1 2
//2 4 1 3 2
//2 4 3 1 2
//3 1 2 4 3
//3 1 4 2 3
//3 2 1 4 3
//3 2 4 1 3
//3 4 1 2 3
//3 4 2 1 3
//4 1 2 3 4
//4 1 3 2 4
//4 2 1 3 4
//4 2 3 1 4
//4 3 1 2 4
//4 3 2 1 4
//24
//in2
//4 3 4
//1 2
//1 4
//2 3
//4 3
//1 2 3 4
//--------
//1 2 1 2
//1 2 1 3
//1 2 3 2
//1 3 1 2
//1 3 1 3
//1 3 2 3
//2 1 2 1
//2 1 2 3
//2 1 3 1
//2 3 1 3
//2 3 2 1
//2 3 2 3
//3 1 2 1
//3 1 3 1
//3 1 3 2
//3 2 1 2
//3 2 3 1
//3 2 3 2
//18
五、实验结果与分析:
通过运行程序验证程序的正确性,给出程序运行结果,分析程序出现的bug的原因,调试程序过程种出现的错误和解决方法。