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)

你可能感兴趣的:(C++实践笔记(一)----迷宫算法初探)