算法设计与分析:实验四 回溯法

一、实验目的

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;

}

程序测试:

如需源文件,请私信作者,无偿

  • 27
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值