要求
由题目已知有三个容量分别为 3kg、5kg和8kg的且没有刻度的酒瓶,3kg和5kg的瓶子均装满了酒,而8kg的瓶子为空。现要求仅用这三个酒瓶将这些酒均分为两个4kg并分别装入5kg和8kg的瓶子中。即图的初始状态为(350),最终状态为(044),本题的求解过程就是把(350)变成(044),也就是找到一条路径,路径的起始点为(350),终点为(044),同时还要输出经过的结点。
步骤
首先初始化各酒瓶的最大容量
for(i=0;i<N;i++)
cin>>full[i];
初始化起点状态
for(i=0;i<N;i++)
cin>>s[0].vexdata[i];//初始状态为s[0]
给出要求的终点
for(i=0;i<N;i++)
cin>>final[i];
然后调用jisuan(number)函数,依次求出顶点0的邻接点,求顶点邻接点的过程如下:
通过两个for循环来改变酒瓶的下标和比较,先是i号酒瓶与j号酒瓶比较。
若i=j,则i和j是同一酒瓶,无需比较和改变。
若i≠j,则进行下面的操作:
I=part[i];//保存i酒杯的当前的容量
J=full[j]-part[j];//j酒杯的空闲量
I是i号酒瓶里当前的酒,J是j号酒瓶里剩余的容量
判断i号酒杯的当前容量是否小于等于j号酒杯的空余量。若是,则将i号酒杯里德酒全部倒入j号酒杯,得到一种新的状态,调用save()函数,保存到状态表。恢复i酒杯和j酒杯改变前的容量;若i号酒杯的当前容量大于j号酒杯的空余量,则将i号酒杯的酒倒入j号酒杯,直至j号酒杯满为止,得到一种新的状态,调用save()函数,保存到状态表。恢复i酒杯和j酒杯改变前的容量。
然后j=j+1,重复上面的步骤,直至j>=N为止;然后是i=i+1,重复上面步骤,直至i>=N为止。这样就把顶点number所有的邻接点都求出来了
源代码
#include<iostream>
using namespace std;
#define N 3 //酒杯数
#define MAX_SIZES 100 //状态表中的最大状态数
typedef struct node //边节点
{
int adjvex;//邻接点的位置
node *next;//指向下一个节点
}ARCNODE;//结点类型
typedef struct //顶点节点
{
int vexdata[N]; //各酒杯的存储状态
ARCNODE *next;//指向下一个邻接点
}VEXNODE;//表头结点类型
int A[MAX_SIZES];//用于存储路径的数组
int top=0;//数组中的个数
int full[N];//各酒杯的最大容量
int part[N];//各酒杯的当前容量
int final[3];//最终状态
VEXNODE s[MAX_SIZES]; //状态表
int sta;//状态表当前状态数
int sum=0;//所有解的个数
int end_vex;//结束的状态点
bool visited[MAX_SIZES];//标记状态是否被访问
bool compare(int a[],int b[]) //比较状态
{
int i;
for(i=0;i<N;i++)
if(a[i]!=b[i])
return true;//两数组不相等,返回true
return false;//相等,返回false
}
void save(int number) //保存状态
{
ARCNODE *p;
int i;
for(i=0;i<=sta;i++)
{
if(!compare(part,s[i].vexdata))
break;//状态存在跳出循环
}
if(i>sta)//将该状态加入状态表
{
sta++;
for(int j=0;j<N;j++)
s[sta].vexdata[j]=part[j];
s[sta].next=NULL;
if(!compare(part,final))
end_vex = sta;//该状态是最终状态,记录此位置
}
p=new ARCNODE;
p->adjvex=i;
p->next=s[number].next;
s[number].next=p;
}
void jisuan(int number)//求出某状态的所有邻接点
{
int i,j,I,J;
for(i=0;i<N;i++)//编号为i的酒杯与其他酒杯交换
{
for(j=0;j<N;j++)
{
if(j==i) continue;//同一酒杯,无法变换
I=part[i];//保存i酒杯的当前的容量
J=full[j]-part[j];//j酒杯的空闲量
if(I<=J)//若i酒杯的当前容量小于等于j酒杯的空闲量
{
part[i]=0;
part[j]+=I;//将i酒杯的酒倒向j酒杯
save(number);//调用save()函数,保存当前的状态
part[i]=I;
part[j]-=I;//恢复各酒杯的原始状态
}
else//若i酒杯的当前容量大于j酒杯的空闲量
{
part[i]-=J;
part[j]=full[j];//把j酒杯加满
save(number);//调用save()函数,保存当前的状态
part[i]+=J;
part[j]-=J;//恢复各酒杯的原始状态
}
}
}
}
void create()//建立状态的函数
{
int number,i;
cout<<"输入各个酒杯的容积:";
for(i=0;i<N;i++)
cin>>full[i];
cout<<"输入酒杯的起始状态:";
for(i=0;i<N;i++)
cin>>s[0].vexdata[i];//初始状态为s[0]
cout<<"输入酒杯的最终状态:";
for(i=0;i<N;i++)
cin>>final[i];//所求的状态
number=0;
sta=0;
s[0].next=NULL;
while(number<=sta)
{
for(i=0;i<N;i++)
part[i]=s[number].vexdata[i];//将s[number]的状态赋给各酒杯当前的容量
jisuan(number);//求出该状态所有的邻接点
number++;
}
for(number=0;number<=sta;number++)
visited[number]=false;
}
void prtf() //输出路径
{
int i,j,k;
cout<<"第"<<sum<<"种解法:" << endl;
for(i=0;i<top;i++)
{
cout << " ";
j=A[i];//路径中的位置
for (k = 0; k < N; k++)
cout << s[j].vexdata[k];//输出该状态
cout << " ";
}
cout << endl;
}
void dfs(int v0)//深度优先遍历
{
if(!visited[v0])//判断该状态是否被访问过
{
if(v0==end_vex)//判断该状态是否是最终状态
{
sum++;
prtf();//调用输出函数
}
else//若不是,深度搜索
{
ARCNODE *p;
p=s[v0].next;
visited[v0]=true;//标记该状态已访问
while(p)
{
A[top++]=p->adjvex; //记录路径
dfs(p->adjvex);//递归调用遍历函数
A[--top];//清空路径
p=p->next;
}
visited[v0]=false;//标记该状态未访问,用于其它路径中访问
}
}
}
void findpath()
{
A[top++]=0; //选择深度所搜遍历的起点
dfs(0); //进行深度所搜遍历
}
int main()
{ int i,k;
create(); //建立模型的状态
cout<<"状态表中的"<< sta + 1<<"种状态:";
for(i=0;i<=sta;i++)
{
for(k=0;k<N;k++)
cout<< s[i].vexdata[k];
cout << " ";
}
findpath(); //输出得到的解
}
运行结果
按照题目要求,首先输入各个酒杯的容积3、5、8,然后输入酒杯的起始状态3、5、0,最后输入酒杯的最终状态0、4、4,按回车键就可以得到全部的16种状态和所有解法,每个解法还展示了从起始态到最终态经过的结点状态。
算法性能分析
本算法所有问题的求解都是围绕三个酒杯之间的转换进行的,而且函数之间都是嵌套调用,全局变量的使用在一定程度上降低了空间复杂度。三个酒杯的状态在它们之间互相变换,要计算出三个酒杯所有可能的状态,在计算过程中还要用到嵌套调用,再加上循环语句的大量使用,更是增加了时间复杂度。在深度优先遍历算法中用了递归的思想,使算法的时间复杂度进一步增加。