前些天在园子里看到一篇关于"脱离地牢"算法的文章,看了题目就没有在往下看了.很想自己搞定它.
如果你感兴趣,不妨看完下面的题目,就把它复制粘贴下来,一个人琢磨,可以看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
flag;
//
0表示岩石,1表示通路,2表示已经来过这个点了.
int
x;
//
第x行
int
y;
//
第y列
注意这里的(x,y)表示第x行第y列,所以最小是1.坐标系的方向不是一般坐标轴的方向,这里的x轴向下,而y轴向右.
然后怎么办?想想怎么走才是最短呢? 要保证走的路最短,应该满足这样的条件:前进过程中到达每个点所走的步数都是最少的.怎样达成这个条件呢.两点,第一,要多条路同时试着走,不能一条路走了不通再返回走别的路;第二,走到一个点,这个点就被占用了,不能再通过别的路走到这个点了.所以要给这个类加几个成员,首先增加一个step,表示到这一点时一共走了几步.还有增加两个指针,一个指针把一条路上的点连接起来,另一个指针把花费步数相同的所有点连接起来,这样一来就可以同时走好多条路了.
Path
*
fp;
//
父指针,这是以后找路径用的,只输出步数的话它就没什么用.
Path
*
bp;
//
兄弟指针,通过它连在一起的点所花费的步数(step)都相同.
int
step;
//
表示来到这里花了多少步
int
flag;
//
0表示岩石,1表示通路,2表示已经来过这个点了.
再给这个类弄两个构造函数,一个是建立头指针用的,一个可以用来写进坐标和flag.
Path():fp(
0
),bp(
0
),x(
0
),y(
0
),flag(
0
),step(
0
){}
//
默认构造函数
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
"
stdafx.h
"
#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下标.
另外我还定义了两个函数,一个是内联函数,可以得到两点之间的距离.
inline
int
dis(
int
x,
int
y,
int
ex,
int
ey)
{
return
abs(x
-
ex)
+
abs(y
-
ey);
}
还有一个是Path的一个成员函数,用来标记来过的点(step=2),同时记录下到达这一点时的步数.最重要的是把每条路连起来同时把step值相同的点连起来.
void
Path::add_flag(Path
*&
p,Path
*&
p1)
//
p是父指针,p1是头指针
{
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步能到的点......就这么找下去...什么时候找到终点了就到了.
Path
*
p
=&
ce[bx][by];
//
从起点开始起点
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!
附:代码文件(环境是VC++ 2008)