前些天在园子里看到一篇关于"脱离地牢"算法的文章,看了题目就没有在往下看了.很想自己搞定它.
如果你感兴趣,不妨看完下面的题目,就把它复制粘贴下来,一个人琢磨,可以看C++语言的工具书,但是不要去看数据结构和算法的书,就只是一个人思考,收获应该会不小哦.
脱离地牢
题目描述:
在一个神秘的国度里,年轻的王子Paris与美丽的公主Helen在一起过着幸福的生活。他们都随身带有一块带磁性的阴阳魔法石,身居地狱的魔王Satan早就想着得到这两块石头了,只要把它们溶化,Satan就能吸收其精华大增自己的魔力。于是有一天他趁二人不留意,把他们带到了自己的地牢,分别困在了不同的地方。然后Satan念起了咒语,准备炼狱,界时二人都将葬身于这地牢里。
危险!Paris与Helen都知道了Satan的意图,他们要怎样才能打败魔王,脱离地牢呢?Paris想起了父王临终前给他的备忘本,原来他早已料到了Satan的野心,他告诉Paris只要把两块魔法石合在一起,念出咒语,它们便会放出无限的光荣,杀死魔王,脱离地牢,而且本子上还附下了地牢的地图,Paris从中了解到了Helen的位置所在。于是他决定首先要找到Helen,但是他发现这个地牢很奇怪,它会增强二人魔法石所带的磁力大小,而且会改变磁力的方向。这就是说,每当Paris向南走一步,Helen有可能会被石头吸引向北走一步。而这个地狱布满了岩石与熔浆,Paris必须十分小心,不仅他不能走到岩石或熔浆上,而且由于他行走一步,Helen的位置也会改变,如果Helen碰到岩石上,那么她将停留在原地,但如果Helen移动到了熔浆上,那么她将死去,Paris就找不到她了。
Paris仔细分析了地图,他找出了一条最快的行走方案,最终与Helen相聚。他们一起念出了咒语“·#¥%^…*&@!”,轰隆一声,地牢塌陷了,他们又重见光明…
输入描述(escape.in)
输入数据第一行为两个整数n,m(3<=n,m<=20),表示地牢的大小,n行m列。接下来n行,每行m个字符,描述了地牢的地图,“.”代表通路,“#”代表岩石,“!”代表熔浆,“H”表示Helen,“P”表示Paris。输入保证地牢是封闭的,即四周均是岩石或熔浆。接下来一行有四个字符“N”(北),“S”(南),“W”(西),“E”(东)的排列,表示Paris分别向NSWE四个方向走时Helen受磁石磁力影响的移动方向。
输出描述(escape.out)
输出文件只有一行,如果Paris能找到Helen,输出一整数d,为Paris最少需要行走的步数;如果Paris在255步之后仍找不到Helen,则输出“Impossible”。注意相遇是指Paris与Helen最终到达同一个格子,或者二人在相邻两格移动后碰到了一起,而后者的步数算他们移动后的步数。
这个问题看起来很复杂,对,我觉得太复杂了,一时解决不了.于是我决定从简单的开始,我假设 Paris和Helen身上没有带着魔法石,那么呢Helen停在原地不动等着Paris来找她就成.Paris在地图里也知道了Helen的位置,那么他就得找到一条最短的路.
因为Helen不动,我干脆把熔浆神马的也去掉了.那么0表示岩石,1表示通路.还是返回最少需要行走的步数,这下就简单了...
为了使文章短一些,我把所有检查输入的语句全去掉了,不要有错误输入哦:)
首先我觉得应该定义一个类,表示地图上的每个点,点得有坐标,还要有一个标志表示这个点走不走得通.所以先给它定义三个公共成员:
int x; // 第x行
int y; // 第y列
注意这里的(x,y)表示第x行第y列,所以最小是1.坐标系的方向不是一般坐标轴的方向,这里的x轴向下,而y轴向右.
然后怎么办?想想怎么走才是最短呢? 要保证走的路最短,应该满足这样的条件:前进过程中到达每个点所走的步数都是最少的.怎样达成这个条件呢.两点,第一,要多条路同时试着走,不能一条路走了不通再返回走别的路;第二,走到一个点,这个点就被占用了,不能再通过别的路走到这个点了.所以要给这个类加几个成员,首先增加一个step,表示到这一点时一共走了几步.还有增加两个指针,一个指针把一条路上的点连接起来,另一个指针把花费步数相同的所有点连接起来,这样一来就可以同时走好多条路了.
Path * bp; // 兄弟指针,通过它连在一起的点所花费的步数(step)都相同.
int step; // 表示来到这里花了多少步
int flag; // 0表示岩石,1表示通路,2表示已经来过这个点了.
再给这个类弄两个构造函数,一个是建立头指针用的,一个可以用来写进坐标和flag.
Path( int i, int j, int f):fp( 0 ),bp( 0 ),step( 0 ) // 构造函数,写入点的坐标(x,y),以及标志(flag)
{
x = i,y = j;
flag = f;
}
设定几个全局变量.m,n表示迷宫的行数和列数(ex,ey)表示终点的坐标.
另外定义一个指向Path型对象的头指针,用来把step值相同的点连起来.这很重要.
#include < iostream >
#include < vector >
#include < string >
#include < math.h >
#include " Path.h "
using namespace std;
int m = 0 ,n = 0 ,ex = 0 ,ey = 0 ;
Path * p1 = new Path();
这样的话,就可以先写输入部分的代码:
cout << " 请输入两个大于2的数字,分别表示迷宫的行数和列数,用空格隔开: " << flush;
cin >> m >> n;
vector < vector < Path > > ce(m + 1 ,vector < Path > (n + 1 ));
cout << " 请输入每个格子的属性(0表示岩石,1表示可以通行),输入完第一行再输入第二行,以此类推.用空格隔开. " << endl
<< " 你需要输入 " << m << " 乘以 " << n << " 等于 " << m * n << " 个数字: " << endl;
for ( int i = 1 ;i <= m;i ++ )
for ( int j = 1 ;j <= n;j ++ )
{
int f;
cin >> f;
ce[i][j] = Path(i,j,f);
}
int bx,by;
cout << " 请输入四个大于0的数字,分别表示起点的坐标和终点的坐标,用空格隔开: " << flush;
cin >> bx >> by >> ex >> ey;
if (bx == ex && by == ey) // 同一点
{
cout << " 起点和终点在一起,还走什么走... " << endl;
system( " PAUSE " );
return - 1 ;
}
if ( ! ce[bx][by].flag ||! ce[ex][ey].flag) // 起点或终点在岩石上.
{
cout << " 杯具,起点或终点在岩石上... " << endl;
system( " PAUSE " );
return - 1 ;
}
由于是变长二维数组,我使用了vector容器,感觉容器用起来比较方便.(其它的我也不会...)而且为了代码更直观,我忽略了0下标.
另外我还定义了两个函数,一个是内联函数,可以得到两点之间的距离.
{
return abs(x - ex) + abs(y - ey);
}
还有一个是Path的一个成员函数,用来标记来过的点(step=2),同时记录下到达这一点时的步数.最重要的是把每条路连起来同时把step值相同的点连起来.
{
flag = 2 ; // 表示这个点已经来过了
fp = p;
step = p -> step + 1 ; // step加1
if (p1 -> bp != p) // 如果p1->bp==p,就说明现在开始用p1连接所有step=this->step的点;
// 如果p1->bp!=p,就说明正连着呢,只管往头指针后面插入就成.
this -> bp = p1 -> bp;
p1 -> bp = this ; // 这是必须的.
}
下面是我写的找路的算法,这个算法其实就是先找出所有走1步能到的点,再找走2步能到的点,再找走3步能到的点......就这么找下去...什么时候找到终点了就到了.
p1 -> bp = p;
int x = bx,y = by; // 当前的坐标,vector的下标
ce[bx][by].flag = 2 ; // 起点已经来过了
while (p)
{
if (dis(x,y,ex,ey) == 1 ) // 到终点了.
{
cout << " 需要走 " << p -> step + 1 << " 步能到目的地. " << endl;
system( " PAUSE " );
return 0 ;
}
if (y > 1 && ce[x][y - 1 ].flag == 1 ) //往 西
ce[x][y - 1 ].add_flag(p,p1);
if (y < n && ce[x][y + 1 ].flag == 1 ) //往 东
ce[x][y + 1 ].add_flag(p,p1);
if (x > 1 && ce[x - 1 ][y].flag == 1 ) //往 北
ce[x - 1 ][y].add_flag(p,p1);
if (x < m && ce[x + 1 ][y].flag == 1 ) //往 南
ce[x + 1 ][y].add_flag(p,p1);
if (p -> bp) // 再从p的兄弟节点出发
{
p = p -> bp;
x = p -> x,y = p -> y;
}
else if (p != p1 -> bp) // 兄弟节点走完了,从下一步的一个候选点开始
{
p = p1 -> bp;
x = p -> x,y = p -> y;
}
else // 兄弟节点也没有,也没有下一步能到的点了,那是真没办法了..
{
cout << " 实在是到不了终点啊! " << endl;
system( " PAUSE " );
return - 1 ;
}
}
return 0 ; // 不会到这来的.
这样一来,这个Helen不动版本的"脱离地牢"就算是解决了.那么,先再学两章C++,再让Helen动起来~~~(也已经解决了,看方法点这里)
Fighting!