C++实践笔记(一)----迷宫算法初探

      前些天在园子里看到一篇关于"脱离地牢"算法的文章,看了题目就没有在往下看了.很想自己搞定它.
      如果你感兴趣,不妨看完下面的题目,就把它复制粘贴下来,一个人琢磨,可以看C++语言的工具书,但是不要去看数据结构和算法的书,就只是一个人思考,收获应该会不小哦.

脱离地牢

题目描述:

在一个神秘的国度里,年轻的王子Paris与美丽的公主Helen在一起过着幸福的生活。他们都随身带有一块带磁性的阴阳魔法石,身居地狱的魔王Satan早就想着得到这两块石头了,只要把它们溶化,Satan就能吸收其精华大增自己的魔力。于是有一天他趁二人不留意,把他们带到了自己的地牢,分别困在了不同的地方。然后Satan念起了咒语,准备炼狱,界时二人都将葬身于这地牢里。

危险!ParisHelen都知道了Satan的意图,他们要怎样才能打败魔王,脱离地牢呢?Paris想起了父王临终前给他的备忘本,原来他早已料到了Satan的野心,他告诉Paris只要把两块魔法石合在一起,念出咒语,它们便会放出无限的光荣,杀死魔王,脱离地牢,而且本子上还附下了地牢的地图,Paris从中了解到了Helen的位置所在。于是他决定首先要找到Helen,但是他发现这个地牢很奇怪,它会增强二人魔法石所带的磁力大小,而且会改变磁力的方向。这就是说,每当Paris向南走一步,Helen有可能会被石头吸引向北走一步。而这个地狱布满了岩石与熔浆,Paris必须十分小心,不仅他不能走到岩石或熔浆上,而且由于他行走一步,Helen的位置也会改变,如果Helen碰到岩石上,那么她将停留在原地,但如果Helen移动到了熔浆上,那么她将死去,Paris就找不到她了。

Paris仔细分析了地图,他找出了一条最快的行走方案,最终与Helen相聚。他们一起念出了咒语“·#%^…*&@!,轰隆一声,地牢塌陷了,他们又重见光明

输入描述(escape.in

输入数据第一行为两个整数n,m(3<=n,m<=20),表示地牢的大小,nm列。接下来n行,每行m个字符,描述了地牢的地图,“.”代表通路,“#”代表岩石,“!”代表熔浆,“H”表示Helen,P”表示Paris。输入保证地牢是封闭的,即四周均是岩石或熔浆。接下来一行有四个字符“N”(北),“S”(南),“W”(西),“E”(东)的排列,表示Paris分别向NSWE四个方向走时Helen受磁石磁力影响的移动方向。

输出描述(escape.out

输出文件只有一行,如果Paris能找到Helen,输出一整数d,为Paris最少需要行走的步数;如果Paris255步之后仍找不到Helen,则输出“Impossible”。注意相遇是指ParisHelen最终到达同一个格子,或者二人在相邻两格移动后碰到了一起,而后者的步数算他们移动后的步数。

 

这个问题看起来很复杂,对,我觉得太复杂了,一时解决不了.于是我决定从简单的开始,我假设 ParisHelen身上没有带着魔法石,那么呢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)

posted on 2011-04-15 14:57  Barryhe  阅读(2604)  评论(1编辑  收藏  举报

导航