回溯法
一、实验目的:
1、通过回溯法的示例程序理解回溯法的基本思想;
2、运用回溯法解决实际问题进一步加深对回溯法的理解和运用;
二、实验环境:
Visual Studio 2013
三、实验内容:
(写出主要的内容)
四、示例程序:符号三角形问题
符号三角问题:下面都是“-”。 下图是由14个“+”和14个“-”组成的符号三角形。2个同号下面都是“+”,2个异号下面都是“-”。
+ + - + - + +
+ - - - - +
- + + + -
- + + -
- + -
- -
+
在一般情况下,符号三角形的第一行有n个符号。符号三角形问题要求对于给定的n,计算有多少个不同的符号三角形,使其所含的“+”和“-”的个数相同。
参考代码如下,请在此基础上,写出主函数,实现如下功能:
1.分别输出n的值为1----20时,对应的符号三角形个数,如没有满足条件的符号三角形,则输出0
2.输入一个整数n,输出对应的符号三角形的个数,并依次显示出所有的符号三角形。
class Triangle
{
friend int Compute(int n);
private:
void Backtrack(int t);
int n, count, half;
int **p;
int sum;
};
void Triangle::Backtrack(int t)
{
if((count>half)||(t*(t-1)/2-count>half))return;//剪去不满足约束的子树
if(t>n)
{ cout<<endl;
sum++; //找到一个满足要求的三角形
for (int i=1;i<=n;i++)
{
for(int j=1; j<i; j++)
cout<<" ";
for(int j=1;j<=n-i+1;j++)
if (p[i][j]==1)
cout<<'-'<<' ';
else if (p[i][j]==0)
cout<<'+'<<' ';
cout<<endl;
}
}
else
for(int i=0;i<2;i++)
{//子树
p[1][t]=i;
count+=i;
for(int j=2;j<=t;j++) //该子树形成的三角形
{
p[j][t-j+1]=p[j-1][t-j+1]^p[j-1][t-j+2];
count+=p[j][t-j+1];
}
Backtrack(t+1);
for(int j=2;j<=t;j++) //回溯恢复
count-=p[j][t-j+1];
count-=i;
}
}
int Compute(int n)
{
Triangle X;
X.n=n;
X.count=0;
X.sum=0;
X.half=n*(n+1)/2;
if (X.half%2==1) return 0;
X.half=X.half/2;
int**p=new int*[n+1];
for(int i=0;i<=n;i++)
p[i]=new int[n+1];
for(int i=0; i<=n;i++)
for(int j=0; j<=n;j++)
p[i][j]=0;
X.p=p;
X.Backtrack(1);
for(int i=0;i<=n;i++)
delete []p[i];
delete []p;
p=0;
return X.sum;
}
代码:
#include<iostream>
using namespace std;
class Triangle
{
friend int Compute(int n);
private:
void Backtrack(int t);
int n, count, half;
int **p;
int sum;
};
void Triangle::Backtrack(int t)
{
int j, i;
if ((count>half) || (t*(t - 1) / 2 - count>half))return;//剪去不满足约束的子树
if (t>n)
{
cout << endl;
sum++; //找到一个满足要求的三角形
for (i = 1; i <= n; i++)
{
for (j = 1; j<i; j++)
cout << " ";
for (j = 1; j <= n - i + 1; j++)
if (p[i][j] == 1)
cout << '-' << ' ';
else if (p[i][j] == 0)
cout << '+' << ' ';
cout << endl;
}
}
else
for (int i = 0; i<2; i++)
{//子树
p[1][t] = i;
count += i;
for (j = 2; j <= t; j++) //该子树形成的三角形
{
p[j][t - j + 1] = p[j - 1][t - j + 1] ^ p[j - 1][t - j + 2];
count += p[j][t - j + 1];
}
Backtrack(t + 1);
for (j = 2; j <= t; j++) //回溯恢复
count -= p[j][t - j + 1];
count -= i;
}
}
int Compute(int n)
{
int i, j;
Triangle X;
X.n = n;
X.count = 0;
X.sum = 0;
X.half = n*(n + 1) / 2;
if (X.half % 2 == 1) return 0;
X.half = X.half / 2;
int**p = new int*[n + 1];
for (i = 0; i <= n; i++)
p[i] = new int[n + 1];
for (i = 0; i <= n; i++)
for (j = 0; j <= n; j++)
p[i][j] = 0;
X.p = p;
X.Backtrack(1);
for (i = 0; i <= n; i++)
delete[]p[i];
delete[]p;
p = 0;
return X.sum;
}
int main(){
int n;
cin >> n;
cout << "一共有:" << Compute(n) << "个三角形" << endl;
return 0;
}
五、实验题
1.算法实现题:整数变换问题。
整数i的两种变换定义为,(向下取整);设计一个算法求给定两个整数a和b,用最少次数的和变换将整数a变换为b;例如
实现提示:
观察f和g两个操作可知,f总是使得i变大,g总是使得i变小。因此在决定让x执行哪个操作之前可以先判断i和目标值m之间的大小关系。如果x>m,就让其执行g操作;反之,执行f操作。
问题的解分为两种情况,一种是有解,即n可以通过函数变换成m;另一种是无解,即n无法通过函数变换成m。
有解的情况比较容易,只需要判断最后的i是否等于m即可。如果i等于m,那么说明n已经被变换成m了,递归返回。
无解的情况可用下例分析。假设我们的输入
n=9,m=5。
n>m,执行g,n=[9/2]=4
n<m,执行f,n=34=12
n>m,执行g,n=[12/2]=6
n>m,执行f,n=[6/2]=3
n<m,执行g,n=33=9
n>m,执行f,n=[9/2]=4
如果n的值陷入了一个重复的循环,如果在递归的过程中,出现了前面计算过的元素,那就说明n是无法转换成m的。这种方法实现稍微复杂,需要判断当前所求出的数值之前是否出现过。 另一种简单的处理方式: 对于m无论如何变换都不能变为n的情况,可以加一个判断条件,比如深度达一个较大值为止(如1000)。
回溯法, 用子集树实现,子集树结构为:
回溯返回条件有两个,一个是i等于m,另一个是出现了重复的数字。第二个返回条件可以用一个函数test来判断。
剪枝条件:
显式约束:如果x>m,就剪掉它的左子树;如果x<m,就剪掉它的右子树;
隐式约束:如果在某次计算的过程中发现当前的计算次数已经大于或等于最少计算次数了,那么就剪掉这个分支。
代码:
#include<iostream>
using namespace std;
int n,m,min=1000,a[1000];
int f(int i){
return i*3;
}
int g(int i){
return i/2;
}
int test(int b,int w){
for(int i=0;i<w;i++)
if(b==a[i])
return 0;
return 1;
}
void Backtrack(int t,int w)
{
a[w]=t;
if((w>min)||(test(t,w)==0))return;//剪去不满足约束的子
if(t==m)
{
if(w<min)
min=w;
return ;
}
if(t<m)
Backtrack(f(t),w+1);
else
Backtrack(g(t),w+1);
}
int main(){
cin>>n>>m;
Backtrack(n,0);
if(min==1000)
cout<<"无解"<<endl;
else
cout<<min<<endl;
return 0;
}
2.子集和问题。
问题描述:给定集合S,S中有n个正整数,M是一个正整数。子集和问题判定是否存在S的一个子集S1,使得S1中各元素之和等于M。请设计回溯法求解子集和问题,如果问题无解,输出“No Solution”,问题有解,则输出满足子集S1中各元素的值。
代码:
#include <iostream>
#define MAX 10000
using namespace std;
int n, c;
int s[MAX], v[MAX],flag=0;
void t(int num, int sum,int j)
{
if (sum == c)
{
flag = 1;
for (int i = 1; i < num; i++)
cout << v[i] << " ";
cout << endl;
return;
}
for (int i = j; i <= n; i++){
if (s[i] + sum <= c&&s[i] != -1){
v[num] = s[i];
s[i] = -1;
t(num + 1, sum + v[num],i);
s[i] = v[num];
v[num] = -1;
}
}
return;
}
int main()
{
cin>>n>>c;
for (int i = 1; i <= n; i++)
cin>>s[i];
int sum = 0;
for (int i = 1; i <= n; i++)
sum += s[i];
if (sum < c)
cout << "No Solution!" << endl;
else
t(1, 0,1);
if (flag==0)
cout << "No Solution!" << endl;
return 0;
}