一、实验目的
1. 理解回溯法的基本思想。
2. 掌握回溯法解决问题的步骤。
3. 掌握使用回溯法解决问题的能力。
二、实验环境
1. 操作系统:Windows 10及以上。
2. 程序设计软件:Microsoft Visual C++、Microsoft Visual Studio或DEV C++等。
三、实验要求
1. 完整记录实验过程,包括算法设计思想描述、代码实现和程序测试。
2. 按照实验内容编写相应功能的程序,记录完成步骤、结果及在实验过程中所遇到的问题。
四、实验内容
1. 设计一个算法在1、2、…、9(顺序不能变)数字之间插入+或-或什么都不插入,使得计算结果总是100的程序,并输出所有的可能性。例如1+2+34-5+67-8+9=100。描述算法设计思想,编写完整的实验程序,并采用相应数据进行测试。
2. 求解0/1背包问题。设n个物品的编号为1~n,重量用数组w[1…n]存放,价值用数组v[1…n]存放,装入背包中物品重量和恰好为W。设计从这些物品中选取一部分物品放入该背包的方案,每个物品要么选中要么不选中,要求选中的物品不仅能够放到背包中,而且具有最大的价值。描述算法设计思想,编写完整的实验程序,并采用相应数据进行测试。
3. 求解子集和问题。给定n个不同的正整数集合w={w1,w2,…,wn}和一个正整数W,要求找出w的子集s,使该子集中所有元素的和为W。例如,当n=4时,w=(11,13,24,7),W=31,则满足要求的子集为(11,13,7)和(24,7)。描述算法设计思想,编写完整的实验程序,并采用相应数据进行测试。
4. 使用回溯法求解n皇后问题。描述算法设计思想,编写完整的实验程序,并采用相应数据进行测试。
5. 求解图的m着色问题。给定无向连通图G和m种不同的颜色,用这些颜色为图G的各顶点着色,每个顶点着一种颜色。如果有一种着色法使G中每条边的两个顶点着不同颜色,则称这个图是m可着色的。图的m着色问题是对于给定图G和m种颜色,找出所有不同的着色法。描述算法设计思想,编写完整的实验程序,并采用相应数据进行测试。
6. 求解填字游戏问题。在3*3方格的方阵中要填入数字1~10的某9个数字,每个方格填一个整数,是所有相邻两个方格内的两个整数之和为素数。编写一个实验程序,求出所有满足这个要求的数字填法。描述算法设计思想,编写完整的实验程序,并采用相应数据进行测试。
1、在1、2、…、9(顺序不能变)数字之间插入+或-或什么都不插入,使得计算结果总是100的程序
算法设计思想描述:
用数组记录1到9的整数,字符数组记录插入的运算符,+、-、空格。先设计1+2+……的情况,加到后面的整数和sum比较100,不对就回溯,取某个数值为两位数即运算符取空格,或者使用运算符-,第一次成功完成就记录下来,即得到一个解,继续按此找下一个解,直至找完就输出全部解。
代码实现:
#include<stdio.h>
#define Num 9//定义1~9个数字
int count=0;
void fun(char op[],int sum,int prevadd,int a[],int i)
//op[]插入运算符,sum和,prevadd记录数值,a[]存放整数,i数字
{
if(i==Num)//扫描完所有位置,即1~9
{
if(sum==100)//找到一个解
{
printf("第%d个解为:",++count);//count记录解个数
printf("%d",a[0]);//输出解
for(int j=1;j<Num;j++)//继续找下一个解
{
if(op[j]!=' ')//如果不输出空格,就输出其他运算符
printf("%c",op[j]);
printf("%d",a[j]);//否则就输出空格,即成两位数
}
printf("=100\n");
}
return;
}
op[i]='+';//在i位置插入+
sum=sum+a[i];//计算和
fun(op,sum,a[i],a,i+1);//继续处理下一个位置
sum=sum-a[i];//sum不等于100就减掉之前加上的,回溯
op[i]='-';//在i位置插入-
sum=sum-a[i];//计算差
fun(op,sum,-a[i],a,i+1);//继续处理下一个位置
sum=sum+a[i];//sum不等于100就加上之前减掉的,回溯
op[i]=' ';//在i位置插入空格
sum=sum-prevadd;//现在的和等于和减去前面一个元素
int temp;//定义交换变量,计算新元素值
if(prevadd>0)//前面一个元素大于0
temp=prevadd*10+a[i];
//变量等于前面一个元素×10加i位置的元素
else
temp=prevadd*10-a[i];
//前面一个元素小于0,变量等于前面一个元素×10减i位置的元素
sum=sum+temp;//现在和等于之前的和加上变量
fun(op,sum,temp,a,i+1);//继续处理下一个位置
sum=sum-temp;//sum不等于100就减掉之前加上的变量,回溯
sum=sum+prevadd;//现在的和等于和加上前面一个元素
}
int main()
{
int a[Num];
char op[Num];//op[i]表示在位置i插入运算符
for(int i=0;i<Num;i++)//将a赋值为1、2、...、9
a[i]=i+1;
printf("求解结果如下:\n");
fun(op,a[0],a[0],a,1);//插入位置从1开始
}
程序测试:
2、求解0/1背包问题
算法设计思想描述:
先定义设计物品数量、限重重量,数组存放物品重量、物品价值,最优解以及其总价值。根先什么都不放,左边放入物品,右边不放;左边放入物品,第一次出现等于限重情况,记录下来先做最优解,后面找到更优的解覆盖并记录;左边继续放,超重后就剪枝以及剪枝下面继续加物品情况,然后回溯;右边不选物品以及后面分支的左边加入物品后,后面再加物品价值都达不到限重,剪枝,回溯;其他得出来的解互相比较价值,价值大且重量是限重的为最优解。
代码实现:
#include<stdio.h>
#include <iostream>
using namespace std;
#define MAXN 20//最多物品数量
int n=4,W=6,rw=0;//共4种物品、限制重量为6
int w[]={0,5,3,2,1},v[]={0,4,4,3,1};//存放4个物品重量、价值,不用下标0元素
int x[MAXN],maxv=0;//存放最终解、最优解总价值
void dispasolution()//定义dispasolution函数,用于输出最优解
{
printf("最优解为选取重量为(");
for(int j = 1; j <= n; j++)//循环输出所选物品的重量
if(x[j] == 1)
printf("%d",w[j]);
printf(")的物品,");
printf("其价值为:%d",maxv);//输出最大价值
}
void dfs(int i,int tw,int tv,int rw,int op[])
//求解0/1背包问题
//定义i物品、tw装入背包的总重量、tv装入背包的总价值、rw所有物品总重量、op解向量
{
if(i>n)//找到一个叶子结点
{
if(tw==W&&tv>maxv)//找到一个满足条件的更优解,记录保存
{
maxv=tv;
for(int j=1;j<=n;j++)//复制最优解
x[j]=op[j];
}
}
else//没找完所有物品
{
if(tw+w[i]<=W)//左孩子结点剪枝
{
op[i]=1;//选取第i个物品
dfs(i+1,tw+w[i],tv+v[i],rw-w[i],op);
}
if(tw+rw-w[i]>=W)//右孩子结点剪枝
{
op[i]=0;//不选取第i个物品,回溯
dfs(i+1,tw,tv,rw-w[i],op);
}
}
}
int main()
{
int op[MAXN];//存放临时解
for(int i = 1; i <= n; i++)//所有物品重量等于当前物品重量加第i个物品重量
{
rw=rw+w[i];
}
dfs(1,0,0,rw,op);//i从1开始选取
dispasolution();//输出最优解
return 0;
}
程序测试:
3、求解子集和问题
算法设计思想描述:
先定义设计元素数量、一个正整数,集合存放元素,选取的整数和、剩下的整数和。根先什么都不放,左边放入第一个元素,右边不放;子集元素的和,第一次出现等于要求的整数情况,记录下来得到第一个解;左边继续放,元素和小于要求整数后就剪枝以及剪枝下面继续加元素情况,然后回溯;右边不选元素以及后面分支的左边加入元素后,后面再加元素的和大于要求整数情况,剪枝,回溯;累计计算解,并输出
代码实现:
#include<stdio.h>
#define MAXN 20//最多整数个数
int n=4,W=31;//n正整数个数,W正整数,找集合中子集元素加起来等于W
int w[]={0,11,13,24,7};//存放所有正整数,不用下标0的元素
int count=0;//累计解个数
void dispasolution(int x[]) //定义dispasolution函数,输出一个解
{
printf("第%d个解为:\n",++count);//输出解个数
printf("选取的数为:(");//输出所选的子集元素
int i;//第i个整数
for (i=1;i<=n;i++)//循环输出符合子集和等于W的解
if (x[i]==1)
printf("%d ",w[i]);
printf(")\n");
}
void dfs(int i,int tw,int rw,int x[])
//求解子集和
//定义第i个整数,tw考虑第i个整数时选取的整数和,rw剩下的整数和
{
if(i>n)//找到一个叶子结点
{
if(tw==W)//找到一个满足条件的解,输出
dispasolution(x);
}
else//没找完所有整数
{
if(tw+w[i]<=W)//左孩子剪枝,当前选第i个整数的和加i整数等于W时,选第i个整数
{
x[i]=1;
dfs(i+1,tw+w[i],rw-w[i],x);
}
if(tw+rw-w[i]>=W)//右孩子剪枝,不选第i个整数,回溯
{
x[i]=0;
dfs(i+1,tw,rw-w[i],x);
}
}
}
int main()
{
int x[MAXN],rw=0;//x[MAXN]存放一个解向量,
for(int j=1;j<=n;j++)//求所有整数和rw
rw=rw+w[j];
dfs(1,0,rw,x);//i从1开始,即不选0
}
程序测试:
4、使用回溯法求解n皇后问题
算法设计思想描述:
用数组存放皇后位置,先尝试放第一个皇后,再往下放,根据要求每个皇后不同行、不同列、不同对角线作为先决条件,试探放置,若不满足条件,就回溯,换皇后元素,若满足条件则接着走下去,直至得到解,记录,可以得出解个数。
代码实现:
#include<stdio.h>
#include<stdlib.h>
#define MAXN 20//最多皇后个数
int q[MAXN],count;
//q[MAXN]存放各皇后的列号全局变量,count累计解个数
void disp(int n)
//输出解
{
printf("第%d个解:\n",++count);
for(int i=1;i<=n;i++)
printf("(%d,%d)\t",i,q[i]);//(i,q[i])为第i个皇后放置位置
printf("\n");
}
bool p(int i)//测试第i行的q[i]列上能否摆放皇后
{
int j=1;
if(i==1)
return true;//放置第一个位置情况,永真
while(j<i)
//j=1~i-1是已放置了皇后的行,表明不同行情况
{
if((q[j]==q[i])||(abs(q[j]-q[i])==abs(j-i)))
//该皇后是否与以前的皇后同列,位置(j,q[j])与(i,q[i])是否同对角线
return false;
j++;//若同列同对角线,则j往下一个位置
}
return true;
}
void Q(int n)//求解n皇后问题
{
int i=1;//i表示当前行,也表示放置第i个皇后
q[i]=0;//q[i]是当前列,每个新考虑的皇后初始位置是0列
while(i>=1)//尚未回溯到头,循环
{
q[i]++;//原位置后移一列
while(q[i]<=n&&!p(i))//试探一个位置(i,q[i])
q[i]++;
if(q[i]<=n)//为第i个皇后找到了一个合适位置(i,q[i])
{
if(i==n)//若放置了所有皇后,输出一个解
disp(n);
else//皇后没有放置完
{
i++;//转向下一行,即开始下一个新皇后的放置
q[i]=0;//每个新考虑的皇后的初始位置为0列
}
}
else i--;//若第i个皇后找不到合适的位置,则回溯到上一个皇后位置
}
}
int main()
{
int n;//n存放实际的皇后个数
scanf("%d",&n);
printf("%d皇后问题求解如下:\n",n);
Q(n);
}
程序测试:
5、求解图的m着色问题
算法设计思想描述:
图用邻接矩阵存储,采用二维数组存储两顶点,分有边无边情况,有边则相邻,从第一个顶点试探,顶点相邻不能相同颜色,无边情况则不用考虑,依次试探,如果发现相邻相同颜色了,则回退上一个顶点,即擦掉当前顶点颜色,回上一个顶点改颜色,直至所有相邻顶点颜色都不相同。
代码实现:
#include<stdio.h>
#include<string.h>
#define MAXN 20//图最多的顶点个数
int n,k,m;//n个顶点,k条边,m种颜色
int a[MAXN][MAXN];//二维数组,顶点MAXN边MAXN
int count=0,x[MAXN];
//全局变量,count累计解个数 ,x[i]表示顶点i的着色
void disp()//输出
{
printf("第%d个着色方案:\n",count);
for(int j=1;j<=n;j++)
printf("%d",x[j]);
printf("\n");
}
bool Same(int i)//判断顶点i是否与相邻顶点存在相同的颜色
{
for(int j=1;j<=n;j++)
if(a[i][j]==1&&x[i]==x[j])//当相邻顶点存在相同颜色时
return false;
return true;
}
void dfs(int i)//求解图的m着色问题
{
if(i>n)//达到叶子结点
{
count++;//着色方案数增1
disp();//输出
}
else
{
for(int j=1;j<=m;j++)//试探每一种颜色
{
x[i]=j;//试探着色j
if(Same(i))//可以着色j,进入下一个顶点着色
dfs(i+1);
x[i]=0;//不能着色就回溯
}
}
}
int main()
{
memset(a,0,sizeof(a));//a初始化
memset(x,0,sizeof(x));//x初始化
int p,q;
scanf("%d %d %d",&n,&k,&m);//输入n,k,m
for(int j=1;j<=k;j++)
{
scanf("%d %d",&p,&q);//输入一条边的两个顶点
a[p][q]=a[q][p]=1;//无向图的边对称
}
dfs(1);//从顶点1开始搜索
if(count>0)//输出结果
printf("一共有%d个着色方案\n",count);
else
printf("-1\n");
return 0;
}
程序测试:
6、求解填字游戏问题
算法设计思想描述:
从1~10个数字选9个数填进3x3方格里,相邻两个数加起来是素数,从第一个格子里放数字,运用素数判断方法,不能被2整除,得到下一个可填数字,依次判断相邻数字是否为素数,是则继续,不是则回溯。其中可以得到1~10数字中,两两不是素数,但加起来是素数条件,可以依据这个条件尝试填。
代码实现:
#include<stdio.h>
#include<iostream>
#include<cmath>//数学函数库
using namespace std;
int m[11]= {0,1,2,3,4,5,6,7,8,9,10}; //1~10数字,不用下标0元素
int x[11];//存放符合条件的填法元素
int count=0;//累计解个数
bool isprime(int m,int n)
//isprime函数,功能是判断素数。
//isPrime()函数,参数为整数,要有异常处理。如果整数是素数,返回True,否则返回False。
{
int a=m+n,counter=0;
//相邻两个方格内的两个整数之和
//counter计数器
for(int i=2; i*i<=a; i++)
//一个数不能被2整除,那么这个数一定不能被其他偶数整除
{
if(a%i==0)
counter++;
}
if(counter!=0)
return true;
else
return false;
}
void fun(int i)
//函数声明//求解填字游戏问题
{
if(i==11) {
for(int i=1; i<9; i++) {
if(isprime(x[i],x[i+1]))
//如果相邻两个方格内的两个整数之和是素数,直接返回解
return;
}
if(isprime(x[8],x[1])||isprime(x[9],x[2])||isprime(x[9],x[4])||isprime(x[9],x[6]))return;
//1~10数字里这些不是素数,但是两数加起来是素数
count++;//计算解总和
for(int i=1; i<=3; i++)
cout<<x[i]<<' ';//输出方格前三个数字
cout<<endl;
cout<<x[8]<<' '<<x[9]<<' '<<x[4]<<endl;
cout<<x[7]<<' '<<x[6]<<' '<<x[5]<<endl<<endl<<endl;
//输出方格后六个数字
} else {
for(int j=i; j<=10; j++) { //两数相等情况
swap(m[i],m[j]);
x[i]=m[i];
fun(i+1);
swap(m[i],m[j]);
}
}
}
int main() {
fun(1);//函数调用
printf("填法总数:%d",count);
return 0;
}
程序测试:
如需源文件,请私信作者,无偿