商仆过河.docx
《商仆过河.docx》由会员分享,可在线阅读,更多相关《商仆过河.docx(12页珍藏版)》请在冰豆网上搜索。
商仆过河
商仆过河问题
摘要
本文将经典的3对商仆过河问题推广至商仆对数为任意值、小船容量也为任意值的一般问题,建立一般化的数学模型,并采用BFS算法编程求解,得到了此问题的一般解法。
本问题实际为一个NP的图搜索问题,BFS算法是经典的图搜索算法,本文通过对BFS算法进行适当改进,合理压缩状态空间,使算法的时间复杂度大幅减少,能够在有限的极短时间内求得过河方案,并保证方案所需的船只往返次数为最少,即求得的方案为最优方案。
12对商仆,船容量为6人时的最优方案为:
(12,12)->(12,10)->(12,11)->(12,5)->(12,6)->(6,6)
->(7,7)->(4,4)->(5,5)->(2,2)->(3,3)->(0,0)
括号内数字分别为此岸商人数和此岸仆人数,最少需要行船11次。
进一步讨论给出了使商仆过河问题有解的商仆对数与小船容量之间的数量关系:
商仆对数
小船容量
1、2、3
≥2
4、5
≥3
≥6
≥4
本文最后还讨论了用A*算法和IDA*算法对模型进行进一步改进的方法。
关键词:
商仆过河问题BFS算法A*算法IDA*算法
1.问题的提出
3名商人各带1个随从乘船渡河,一只小船只能容纳2人,由他们自己划行。
随从们密约,在河的任何一岸,一旦随从的人数比商人多,就杀人越货。
但是如何乘船渡河的大权掌握在商人们手中,商人们怎样才能安全渡河呢?
这就是著名的商仆渡河问题,对于这类智力游戏经过一番逻辑思索是可以找出解决办法的。
这里要求将问题推广至商仆对数任意、船只容量也任意的一般情况下,建立数学模型,并编程求解。
(1)问题有解,算法结束。
(2)如果队列q为空,则问题无解,算法结束。
(3)转至
(2)执行
观察BFS算法的执行过程,算法首先将距离起始状态为1次状态转移的所有状态加入队列,如果其中没有终止状态,则继续将所有距离起始状态为2次状态转移的状态加入队列……由于队列是先进先出的,可以始终保证所需状态转移步数最少的状态排在队列的最前部,并首先由他们扩展下一层状态。
所以加入队列的每个状态都是以最短路径(最少的状态转移步数)到达的,因为如果有更短的路径存在,则此路径上的状态一定会被更早扩展,更早加入状态队列(所需步数少的状态排在状态队列的前部)。
当终止状态加入队列时,到达终止状态的路径也是最短路径,即求得一个所需状态转移步数最少的过河方案。
若某时刻状态队列为空且始终未到达终止状态,则说明所有自起始状态可达的节点均已被访问,且其中没有终止状态,即终止状态不可达,问题无解。
4.结果分析
对于经典的3对商仆、小船容量为2人时的问题,运行程序求得结果如下:
图13对商仆、小船容量为2时的求解结果
12对商仆,小船容量为2人时,问题无解:
图212对商仆、小船容量为2时的求解结果
12对商仆,小船容量为6人时,运行程序求得结果如下:
图312对商仆、小船容量为6时的求解结果
事实上,12对商仆时的允许状态空间如图:
图412对商仆时的允许状态空间
显然的船容量必须至少保证状态转移能够沿对角线方向向下移动,问题才会有解。
船容量为2时可以使状态转移沿对角线移动,但不能持续向下移动,船容量至少为4才可使状态能够沿对角线向下移动。
下面说明何时需要状态空间沿对角线移动。
当商仆对数为6时,状态空间为:
图56对商仆时的状态空间
此时状态中心(3,3)距离边界状态(6,3)的距离为3,船容量至少为4才可避免状态转移沿对角线下行,而船容量为4已经是状态转移沿对角线移动的条件,即当商仆对数大于等于6时,船容量至少为4,问题有解。
不难说明,当商仆对数为4或5时,所需的最小船容量为3。
综上,使问题有解的商仆对数与船容量之间的关系如下:
表1商仆对数与船容量的关系
商仆对数
小船容量
1、2、3
≥2
4、5
≥3
≥6
≥4
5.模型评价与改进
BFS算法是最经典也是最简单的图搜索算法之一,对于状态空间规模很大,但目标状态距离初始状态并不很远的问题有较好的求解能力,且保证求得最优解。
实际使用主流PC机测试问题规模为101数量级时的求解时间均为0ms,即使在1000对商仆,船容量200人时的求解时间亦仅为1.592s,此求解速度是完全满足需要的。
BFS算法的运行时间主要取决于算法执行过程中状态队列q的最大长度[2],本算法自初始状态(m,m)开始扩展,随着状态的逐层扩展,队列q的长度逐渐增加。
最坏情况下q的长度单调增加,扩展至终止状态(0,0)时,达到最大,算法运行时间最长。
此时可选择使用A*算法(双向广度优先搜索算法),即从初始状态(m,m)和终止状态(0,0)同时开始扩展,当扩展的状态相遇时即找到最优解,此方法至少可使状态队列的最大长度减半,运行时间减半。
更进一步的,还可以在此基础上进行智能化改进(IDA*算法)——不同时扩展两个状态队列,而是总是先扩展较短的状态队列(使队列长度增长速度更慢);也不同时扩展所有状态,而是先扩展距离对侧状态队列近的状态(使状态更早相遇)。
这些改进可以进一步缩短程序运行时间。
6.参考文献
[1]姜启源等,《数学模型(第三版)》,北京:
高等教育出版社,2003
[2]T.H.Cormenetc,《IntroductiontoAlgorithms(SecondEdition)》,MITPress,2001
7.附录
BFS算法求解一般化商仆问题(C++源代码):
#include
#include
#include
#include"windows.h"
usingnamespacestd;
structCNode//节点数据类型
{
intx,y;//节点坐标
intflag;//允许状态标记
intdir;//行船方向标记
CNode*p;//父节点指针
};
CNodeG[1010][1010][2];//状态空间
intV[1010][1010][2];//访问标记
dequeq;//BFS搜索队列
intNum;//商仆对数
intCap;//船容量
intIssol;//解标记
intCount;//步数
voidInit();//初始化
voidBFS();//BFS搜索
voidOutput(CNode*p);//输出结果
intmain()
{
Init();
longt=GetTickCount();
while(!
q.empty()&&!
Issol)//只要q非空且无解则执行BFS
{
BFS();
}
t=GetTickCount()-t;
if(Issol==0)
cout<else
{
cout<"<Output(&G[0][0][1]);
cout<<"最少需要"<}
//cout<<"runtime:
"<return0;
}
voidInit()
{
cout<<"共有商仆对数:
";
cin>>Num;
cout<<"船容量:
";
cin>>Cap;
inti,j;
for(i=0;i<=Num;i++)//初始化状态空间
{
for(j=0;j<=Num;j++)
{
G[i][j][0].x=G[i][j][1].x=i;
G[i][j][0].y=G[i][j][1].y=j;
G[i][j][0].flag=G[i][j][1].flag=0;
G[i][j][0].p=G[i][j][1].p=NULL;
G[i][j][0].dir=-1;
G[i][j][1].dir=1;
V[i][j][0]=V[i][j][1]=0;
}
}
for(i=0;i<=Num;i++)
{
G[0][i][0].flag=G[Num][i][0].flag=G[i][i][0].flag=1;
G[0][i][1].flag=G[Num][i][1].flag=G[i][i][1].flag=1;
}
G[Num][Num][0].flag=G[Num][Num][1].flag=0;
Issol=0;//初始化有解标记
q.push_back(G[Num][Num][0]);//初始化BFS访问队列
Count=0;//初始化步数
}
voidBFS()
{
intdx,dy,nx,ny,x,y,dir;
if(q.empty()||Issol)//搜索队列空或找到解,则退出
return;
x=q.front().x;//取队首
y=q.front().y;
dir=q.front().dir;
q.pop_front();//队首出队
for(dx=0;dx<=Cap;dx++)
{
for(dy=0;dy<=Cap-dx;dy++)//枚举所有可达状态
{
nx=x+dx*dir;
ny=y+dy*dir;
if(nx<0||nx>Num||ny<0||ny>Num)//坐标越界
continue;
if(G[nx][ny][0].flag==0)//该点为不可行点
continue;
if(dx==0&&dy==0)//坐标未改变
continue;
if(dir>0&&V[nx][ny][1]==1)//坐标已访问
continue;
if(dir<0&&V[nx][ny][0]==1)//坐标已访问
continue;
if(dir>0)//将该点加入BFS队列
{
G[nx][ny][0].p=&G[x][y][1];
q.push_back(G[nx][ny][0]);
}
else
{
G[nx][ny][1].p=&G[x][y][0];
q.push_back(G[nx][ny][1]);
}
if(dir>0)//标记该点为已访问
V[nx][ny][1]=1;
else
V[nx][ny][0]=1;
if(nx==0&&ny==0)//若该点为终点
{
Issol=1;
return;
}
}
}
}
voidOutput(CNode*p)
{
if(p->p==NULL)
{
printf("(%2d,%2d)\n",p->x,p->y);
return;
}
Output(p->p);//回溯
printf("(%2d,%2d)\n",p->x,p->y);
Count++;
}