基本题一:装载问题
一、实验目的与要求
1、掌握装载问题的回溯算法;
2、进一步掌握回溯算法;
二、实验题目
现有n个集装箱要装进两艘载重分别为c1,c2的船,其中第i个集装箱重w[i],并且题目保证 i=1nwi≤c1+c2 , 问是否存在一个合理的装载方案,使得这n个集装箱都装进两艘船,若有请找出一种方案。
输入第一行三个个整数n,c1,c2表示上述变量的值。
输入第二行n个整数W[i]代表集装箱重量。
若方案存在:
输出一行一个整数,代表载重为c1的船的装货重量,第二行n个整数,其中第i个整数为1表示第i件物品装进载重为c1的船,为0表示装进另一艘船。
若不存在方案,打印一行“no solution”。
Simple input:
10 500 121
21 54 21 45 20 65 320 1 20 54
Simple output:
500
1 1 0 0 1 1 1 0 1 0
三、实现思想
将货物的选择构成树,只针对集装箱a进行装载,使所装货物的质量尽量多,且不超过集装箱的容量。
四、实现代码
#include<bits/stdc++.h>
using namespace std;
int bestw=0;
bool bestflag[100];
void load(int i,int r,int n,int w[],int c1,int cw,bool flag[])
{
if(i>n)
{
if(cw>bestw)
{
bestw=cw;
for(int j=1;j<=n;j++)
bestflag[j]=flag[j];
}
return;
}
r-=w[i];
if(cw+w[i]<=c1)
{
cw+=w[i];
flag[i]=1;
load(i+1,r,n,w,c1,cw,flag);
flag[i]=0;
cw-=w[i];
}
if(cw+r>bestw)
load(i+1,r,n,w,c1,cw,flag);
r+=w[i];
}
int main()
{
int n,c1,c2,w[100],cw=0,r=0;
bool flag[100];
cin>>n>>c1>>c2;
for(int i=1;i<=n;i++)
{
cin>>w[i];
r+=w[i];
}
load(1,r,n,w,c1,cw,flag);
cout<<bestw<<endl;
for(int i=1;i<=n;i++)
cout<<bestflag[i]<<" ";
cout<<endl;
}
五、实验结果
基本题二:0—1背包问题
一、实验目的与要求
1、掌握0—1背包问题的回溯算法;
2、进一步掌握回溯算法;
二、实验题目
给定n种物品和一背包。物品i的重量是wi,其价值为vi,背包的容量为C。问应如何选择装入背包的物品,使得装入背包中物品的总价值最大?
三、实现思想
实现思想跟装载问题很像,只是评判的标准变为了使价值最优。
四、实现代码
#include<bits/stdc++.h>
using namespace std;
int w[100],v[100],n,c,bestv=0,bestflag[100];
void load(int i,int r,int cv,int cw,int cflag[])
{
if(i>n)
{
if(cv>bestv)
{
bestv=cv;
for(int i=1;i<=n;i++)
bestflag[i]=cflag[i];
}
return;
}
r-=v[i];
if(cw+w[i]<=c)
{
cw+=w[i];
cv+=v[i];
cflag[i]=1;
load(i+1,r,cv,cw,cflag);
cflag[i]=0;
cv-=v[i];
cw-=w[i];
}
if(cv+r>bestv&&cw+w[i+1]<c)
load(i+1,r,cv,cw,cflag);
r+=v[i];
}
int main()
{
int cv=0,cflag[100],cw=0,r=0;
cout<<"请分别输入物品数和背包容量"<<endl;
cin>>n>>c;
cout<<"请分别输入物品重量:"<<endl;
for(int i=1;i<=n;i++)
cin>>w[i];
cout<<"请分别输入物品价值:"<<endl;
for(int i=1;i<=n;i++)
{
cin>>v[i];
r+=v[i];
}
load(1,r,cv,cw,cflag);
cout<<bestv<<endl;
for(int i=1;i<=n;i++)
cout<<bestflag[i]<<" ";
}
五、实验结果
基本题三:批处理作业调度问题
一、实验目的与要求
1、掌握批处理作业调度问题的回溯算法;
2、进一步掌握回溯算法;
二、实验题目
给定n个作业的集合J = (j1,j2,j3….jn),每个作业都必须先在第一个机器执行t1时间,然后在第二个机器执行t2时间,问怎样安排作业顺序使得所有作业总的等待加执行时间总和最小。
输入第1行一个整数n表示作业的个数。
输入第2行到第n+1行每行2个整数t1,t2,其中第i+1行表示第i个作业在两个机器上的执行时间。
输出一行一个整数,代表最优值。
Simple input:
3
2 1
3 1
2 3
Simple output:
18
三、实现思想
回溯加全排列
四、实现代码
#include<bits/stdc++.h>
using namespace std;
int bestf=0xffffff,bestx[100];
void dfs(int i,int n,int M[][100],int f,int f1,int f2[],int x[])
{
if(i>n)
{
for(int j=1;j<=n;j++)
bestx[j]=x[j];
bestf=f;
}
else
{
for(int j=i;j<=n;j++)
{
f1+=M[x[j]][1];
f2[i]=((f2[i-1]>f1)?f2[i-1]:f1)+M[x[j]][2];
f+=f2[i];
if(f<bestf)
{
swap(x[i],x[j]);
dfs(i+1,n,M,f,f1,f2,x);
swap(x[i],x[j]);
}
f1-=M[x[j]][1];
f-=f2[i];
}
}
}
int main()
{
int M[100][100],n,f=0,f1=0,f2[100],x[100];
cin>>n;
for(int j=1;j<=n;j++)
for(int k=1;k<=2;k++)
{
cin>>M[j][k];
}
for(int j=0;j<=n;j++)
{
x[j]=j;
f2[j]=0;
}
dfs(1,n,M,f,f1,f2,x);
cout<<bestf<<endl;
}
五、实验结果
基本题四:n后问题
一、实验目的与要求
1、掌握n后问题的回溯算法;
2、进一步掌握回溯算法;
二、实验题目
在一个n*n的棋盘上放置彼此不受攻击的n个皇后,按照国际象棋规则,皇后可以攻击与其在同一行,同一列或者同一对角线的其他皇后,求合法摆放的方案数。
输入一行包含一个整数n。
输出一行一个整数代表方案数。
Simple input
2
Simple output:
2
三、实现思想
采用dfs方法,一行一行地放置,每次放置后去检查新放置的跟之前放置的有没有冲突,没有就继续放置,有就回到上一行,放置在下一列
四、实现代码
#include<bits/stdc++.h>
using namespace std;
int cnt=0,q[20];
int find(int i,int k)
{
int j=1;
while(j<i) //j=1~i-1是已经放置了皇后的行
{
//第j行的皇后是否在k列或(j,q[j])与(i,k)是否在斜线上
if(q[j]==k || abs(j-i)==abs(q[j]-k))
return 0;
j++;
}
return 1;
}
//放置皇后到棋盘上
void place(int k,int n)
{
int j;
if(k>n)
{
cnt++;
return;
} //递归出口
else
{
for(j=1;j<=n;j++) //试探第k行的每一个列
{
if(find(k,j))
{
q[k] = j; //保存位置
place(k+1,n); //接着下一行
}
}
}
}
int main(void)
{
int n;
printf("请输入皇后的个数(n<=20),n=:");
scanf("%d",&n);
if(n>20)
printf("n值太大,不能求解!\n");
else
{
place(1,n); //问题从最初状态解起
cout<<cnt<<endl;
printf("\n");
}
return 0;
}
五、实验结果
基本题五:最大团问题
一、实验目的与要求
1、掌握最大团的回溯算法;
2、进一步掌握回溯算法;
二、实验题目
给定无向图G(N,E),含有n个结点,m条边。现在有以下定义:
完全子图:原图的一个子图,并且该子图是一个完全图。
团:它是一个完全子图,并且它不包含在任何其它更大的完全子图中。
最大团:包含结点最多的团。
请计算G的一个最大团。
输入第一行2个整数n,m;
接下来m行,每行两个数,u,v表示u,v结点有一条无向边。
输出2行
第一行一个整数x表示最大团中结点个数。
第二行x个整数,最大团中结点编号,若答案不唯一,打印任意一个即可。
Simple input:
3 3
1 2
1 3
2 3
Simple output:
3
1 2 3
三、实现思想
每次选取一个顶点,观察其与之前最大团中顶点是否有边,没有边的话,会抛弃该顶点,选择下一个顶点。右枝就是不选择该节点。
四、实现代码
#include<bits/stdc++.h>
using namespace std;
int bestx[100],bestcn=0,G[100][100];
void dfs(int i,int n,int cn,int x[])
{
if(i>n)
{
for(int j=1;j<=n;j++)
bestx[j]=x[j];
bestcn=cn;
return;
}
int OK=1;
for(int j=1;j<i;j++) //j<i,只检查之前的结点
if(x[j]&&G[i][j]==0)
{
OK=0;
break;
}
if(OK)
{
x[i]=1;
cn++;
dfs(i+1,n,cn,x);
x[i]=0;
cn--;
}
if(cn+n-i>bestcn) //右枝
{
x[i]=0;
dfs(i+1,n,cn,x);
}
}
int main()
{
int x[100];
int n,m,u,v,cn=0;
cin>>n>>m;
for(int i=1;i<=m;i++)
{
cin>>u>>v;
G[u][v]=G[v][u]=1;
}
dfs(1,n,cn,x);
cout<<bestcn<<endl;
for(int j=1;j<=n;j++)
{
if(bestx[j]==1)
cout<<j<<" ";
}
}
五、实验结果
基本题六:图的m着色问题
一、实验目的与要求
1、掌握图的m着色问题的回溯算法;
2、进一步掌握回溯算法;
二、实验题目
给定无向连通图G(N,E),含有n个结点,k条边。现在有m种颜色,现在要给n个结点涂色,要求相邻结点不同色。求共有多少种涂色方案。
输入第一行3个整数n,k,m;
接下来k行,每行两个数,u,v表示u,v结点有一条无向边。
输出2行
第一行一个整数x表示方案数。
Simple input:
3 3 3
1 2
1 3
2 3
Simple output:
6
三、实现思想
回溯dfs,不断搜索可行解
四、实现代码
#include<bits/stdc++.h>
using namespace std;
int G[100][100],cnt=0,m;
int OK(int i,int n,int color[])
{
for(int j=1;j<=n;j++)
{
if(G[i][j]==1&&color[i]==color[j])
return 0;
}
return 1;
}
void dfs(int i,int n,int color[])
{
if(i>n)
{
cnt++;
return;
}
for(int j=1;j<=m;j++)
{
color[i]=j;
if(OK(i,n,color))
dfs(i+1,n,color);
color[i]=0;
}
}
int main()
{
int n,k,u,v;
int color[100];
cin>>n>>k>>m;
for(int i=1;i<=k;i++)
{
cin>>u>>v;
G[u][v]=G[v][u]=1;
}
dfs(1,n,color);
cout<<cnt<<endl;
}
五、实验结果
基本题六:旅行商问题
一、实验目的与要求
1、掌握旅行商问题的回溯算法;
2、进一步掌握回溯算法;
二、实验题目
给定无向图G(N,E),含有n个结点,m条边。现在有以下定义:
有一个商人从1号结点出发,希望经过每个结点一次回到起点,并且他希望走权值最小的一条路径。
输入第一行2个整数n,m;
接下来m行,每行三个数,u,v,w表示u,v结点有一条权值为w的无向边。
如果不存在这种路径,打印 -1;
否则打印两行。
第一行一个整数代表最优值,第二行n+1个点表示路径经过村庄的顺序,若答案不唯一,打印任意一个即可。
Simple input:
3 3
1 2 2
1 3 4
2 3 10
Simple output:
16
1 2 3 1
三、实现思想
回溯,排列树,每次查询该节点距离上一节点是否有连接,并且加上后的当前路径长度是否小于bestcp
四、实现代码
#include<bits/stdc++.h>
using namespace std;
int n,m,u,v,w;
int a[100][100],x[100],bestx[100],bestcp=0xffff,cp=0;
void dfs(int t)
{
if(t>n)
{
if(a[x[n]][1]&&a[x[n]][1]+cp<bestcp)
{
bestcp=cp+a[x[n]][1];
for(int j=1;j<=n;j++)
bestx[j]=x[j];
}
}
else
{
for(int i=t;i<=n;i++)
{
if((a[x[t-1]][x[i]])&&(a[x[t-1]][x[i]]+cp<bestcp))
{
swap(x[t],x[i]);
cp+=a[x[t-1]][x[t]];
dfs(t+1);
cp-=a[x[t-1]][x[t]];
swap(x[t],x[i]);
}
}
}
}
int main()
{
cin>>n>>m;
for(int i=1;i<=m;i++)
{
cin>>u>>v>>w;
a[u][v]=a[v][u]=w;
}
for(int i=1;i<=n;i++)
x[i]=i;
dfs(2);
cout<<bestcp<<endl;
for(int i=1;i<=n;i++)
cout<<bestx[i]<<" ";
cout<<bestx[1];
return 0;
}
五、实验结果
基本题七:圆的排列问题
一、实验目的与要求
1、掌握旅行商问题的回溯算法;
2、进一步掌握回溯算法;
二、实验题目
给定n个大小不等的圆,c1,c2,c3….cn,现将n个圆排进一个矩形框中,要求各圆与底边相切,现在求n个圆该怎么排列才能使得矩形最短且能容下n个圆。
输入包含两行:
第一行一个整数n代表圆的个数。
第二行n个整数表示圆的半径。
输出一行一个浮点数x表示最小矩形长度,结果保留三位有效数字。
Simple input:
3
1 1 2
Simple output:
7.657
三、实现思想
回溯,排列树,计算圆心横坐标依据计算相切圆两圆心之间的距离
四、实现代码
#include<bits/stdc++.h>
using namespace std;
int n;
double r[100],x[100],bestr[100];
double minlen=10000;
double center(int t)
{
double temp=0;
for(int j=1;j<t;j++)
{
double value=x[j]+2.0*sqrt(r[t]*r[j]);
if(value>temp)
temp=value;
}
return temp;
}
void compute()
{
double low=0,high=0;
for(int j=1;j<=n;j++)
{
if(x[j]-r[j]<low)
low=x[j]-r[j];
if(x[j]+r[j]>high)
high=x[j]+r[j];
}
if(high-low<minlen)
{
minlen=high-low;
for(int i=1;i<=n;i++)
bestr[i]=r[i];
}
}
void dfs(int t)
{
if(t>n)
{
compute();
}
else
{
for(int i=t;i<=n;i++)
{
swap(r[t],r[i]);
double centerx=center(t);
if(centerx+r[1]+r[t]<minlen)
{
x[t]=centerx;
dfs(t+1);
}
swap(x[t],x[i]);
}
}
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
cin>>r[i];
dfs(1);
cout<<minlen<<endl;
}