C++实践笔记(三)----找到公主啦,好开心啊!

看完电影,突然想到Paris和Helen还在地牢里呆着呢,得在学泛型算法之前救他们出来啊.于是拿起纸和笔坐到马桶上,就开始想了.

回想起那个Helen不动的版本,算法也就是找到了所有的可能性,一步一步的走.那么,既然要找最短路径,我想,找遍所有的可能性是必须的,Helen会动也一样.怎样才能一点都不放过呢?在实践一里面,每走到一点,记录下走到这一点所花的步数,同时做个标记,就不会从别的路来这了.而且,搜寻的时候是广度优先,这样就保证了每到一点所花的步数都是最小的.

但是,Helen如果会动,情况就复杂了.比如可能为了让Helen从某个死胡同里面出来,Paris得重复经过某个点.既然Paris经过某个点可能不只一次,那么,什么是"唯一"的呢??一对点呗!如果某个时刻Paris在A点而Helen在B点,走了n步之后Paris又到了A同时Helen又到了B,那显然这n步是白走了!

好了,那我们搜寻的时候还是广度优先,找这样一对对的点,不就ok了? 

冲水,开始动手写吧!

--------------------------

看题目点这里

--------------------------

 

首先,我们定义FindWay类,学完了C++的容器部分,解决问题果然很有用啊.

先是类的私有成员,我是这样定义的:

typedef pair < int , int >  Coordi;
typedef pair
< Coordi,Coordi >  Dou_coordi;
private :
    vector
< vector < char >   >  maze; // 存储地牢
     int  lines; // 行数
     int  columns; // 列数
     char  pton; // paris向北走时,helen的行动方向
     char  ptos; // paris向南走时,helen的行动方向
     char  ptow; // paris向西走时,helen的行动方向
     char  ptoe; // paris向东走时,helen的行动方向
    Coordi paris; // Paris的位置
    Coordi helen; // Helen的位置
     int  steps; // 当前已走的步数
    multimap < int ,Dou_coordi >  came_points;
        
// multimap容器,存放paris和helen同时来过的一对对点的集合

根据题目的要求,我们要设定地牢的大小,设置地牢里每一点的标志,放入Paris和Helen,还要设置Helen的行动方向.最后得出需要的步数.因此,我定义了下面几个公共成员函数:

public :
    
bool  set_size( int  m, int  n, string   & help); // 检查地牢的大小是否符合要求,设置地牢大小
     void  set_flag( int  x, int  y, char  flag) // 设置地牢里每一点的标志
    {
        
if (flag == ' P ' )
        {
            paris
= make_pair(x,y);
            flag
= ' . ' ;
        }
        
else   if (flag == ' H ' )
        {
            helen
= make_pair(x,y);
            flag
= ' . ' ;
        }
        maze[x][y]
= flag;
    }
    
bool  is_effective( string   & help)  const ; // 检查地牢是否是封闭的,以及是否放入了Paris和Helen
     void  set_direction( char  tn, char  ts, char  tw, char  te) // 存入Helen相应的行动方向
    {
        pton
= tn;
        ptos
= ts;
        ptow
= tw;
        ptoe
= te;
    }
    
int  get_steps(); // 计算并返回步数,如果找不到路则返回-1

set_flag()函数因为要用到很多次,把它定义在头文件中,这样它就是一个内联函数.set_direction()很短,也把他定义在头文件中.另外有些函数中的help参数是为了返回错误信息.

接下来就可以写主函数了:

View Code
#include  " stdafx.h "
#include 
< iostream >
#include 
< string >
#include 
" FindWay.h "
using   namespace  std;


int  _tmain( int  argc, _TCHAR *  argv[])
{
    FindWay fd;
    cout
<< " 请输入两个大于等于3小于等于20的数字,分别表示迷宫的行数和列数,用空格隔开: " << flush;
    
int  m,n;
    
string  size_help;
    cin
>> m >> n;
    
if ( ! fd.set_size(m,n,size_help))
    {
        cout
<< size_help << endl;
        system(
" PAUSE " );
        
return   - 1 ;
    }

    cout
<< " 请输入字符用以描述地图.“.”代表通路,“#”代表岩石,“!”代表熔浆,“H”表示Helen,“P”表示Paris。 " << endl
        
<< " 输入完第一行再输入第二行,以此类推.用空格隔开. " << endl
        
<< " 你需要输入 " << m << " 乘以 " << n << " 等于 " << m * n << " 个字符: " << endl; 
    
char  flag;
    
string  maze_help;
    
for ( int  i = 1 ;i <= m;i ++ )
        
for ( int  j = 1 ;j <= n;j ++ )
        {
            cin
>> flag;
            fd.set_flag(i,j,flag);
        }
    
if ( ! fd.is_effective(maze_help))
    {
        cout
<< maze_help << endl;
        system(
" PAUSE " );
        
return   - 1 ;
    }

    cout
<< " 请输入四个字符“n”(北),“s”(南),“w”(西),“e”(东)的排列, "
        
<< " 表示Paris分别向NSWE四个方向走时Helen受磁石磁力影响的移动方向: " << endl;
    
char  tn,ts,tw,te;
    cin
>> tn >> ts >> tw >> te;
    fd.set_direction(tn,ts,tw,te);

    
int  steps = fd.get_steps();
    
if (steps < 0 )
        cout
<< " 可惜,找不到可以遇见Helen的道路! " << endl;
    
else
        cout
<< " " << steps << " 步之后可以遇见Helen! " << endl;

    system(
" PAUSE " );
    
return   0 ;
}

下面,我们就可以不关心别的,专心实现刚才声明的三个公共成员函数.

其中set_size()和is_effective()这两个函数比较简单:

bool  FindWay::set_size( int  m, int  n, string   & help) // 检查地牢的大小是否符合要求,设置地牢大小
{
    
if (m < 3 || m > 20 )
    {
        help
= " 行数应该在3到20之间! " ;
        
return   false ;
    }
    
if (n < 3 || n > 20 )
    {
        help
= " 列数应该在3到20之间! " ;
        
return   false ;
    }
    lines
= m;
    columns
= n;
    paris
= make_pair( 0 , 0 );
    helen
= make_pair( 0 , 0 );
    maze.resize(lines
+ 1 ,vector < char > (columns + 1 ));
        
// 为了更易读,不使用下标'0',所以容器要定义的大一号
     return   true ;
}

 

bool  FindWay::is_effective( string   & help)  const
    
// 检查地牢是否是封闭的,以及是否放入了Paris和Helen
{
    
if (paris.first == 0 )
    {
        help
= " 没有在地图上指定Paris的位置! " ;
        
return   false ;
    }
    
else   if (helen.first == 0 )
    {
        help
= " 没有在地图上指定Helen的位置! " ;
        
return   false ;
    }
    
for ( int  i = 1 ;i <= lines;i += (lines - 1 ))
        
for ( int  j = 1 ;j <= columns;j ++ )
            
if (maze[i][j] == ' . ' || maze[j][i] == ' . ' ) // 这样就可以检查地牢的四周
            {
                help
= " 地牢应该是封闭的,四周应该是岩石或者熔浆! " ;
                
return   false ;
            }
    
return   true ;
}

 

到了最重要的get_steps()函数.在定义它之前,先来定义几个私有成员函数,我们经常要判断Paris或Helen能不能向某个方向移动,如果没有被挡住可以还要判断"这一对点"是不是已经来过了.另外我们要判断Paris和Helen是不是已经相遇了.因此,我定义了下面四个私有成员函数:

private :
    
int  meet() // 判断Paris和Helen是否相遇
    {
        
return  abs(paris.first - helen.first) + abs(paris.second - helen.second);
        
// 0表示已经在同一点;1表示相邻,经过一次移动即可相遇
    }
    Coordi 
& move( char  dir,Coordi  & new_coordi) // 得到Paris或Helen移动后的坐标
    {
        
switch  (dir)
        {
        
case   ' n ' :
            new_coordi.first
-- ;
            
break ;
        
case   ' s ' :
            new_coordi.first
++ ;
            
break ;
        
case   ' w ' :
            new_coordi.second
-- ;
            
break ;
        
case   ' e ' :
            new_coordi.second
++ ;
            
break ;
        }
        
return  new_coordi;
    }
    
bool  alive( char  p_dir,Coordi  & p_coordi, char  h_dir,Coordi  & h_coordi);
        
// Paris和Helen的移动是否会被挡住,可行返回true,并且返回移动后两人的坐标.
     void  walk( const   char  pto, const   char  hto);
        
// 向某个方向移动一步,pto表示Paris的移动方向,hto为Helen的移动方向

其中alive()和walk()的定义如下:

typedef multimap < int ,Dou_coordi > ::iterator Dcoordi_iter;

 

bool  FindWay::alive( const   char  p_dir,Coordi  & p_coordi, char  h_dir,Coordi  & h_coordi)
{
    p_coordi
= move(p_dir,p_coordi);
    
char  p_flag = maze[p_coordi.first][p_coordi.second];
    
if (p_flag != ' . ' )
        
return   false ; // Paris不管遇到石头还是熔浆都走不了
    h_coordi = move(h_dir,h_coordi);
    
char  h_flag = maze[h_coordi.first][h_coordi.second];
    
if (h_flag == ' ! ' )
        
return   false ; // Helen遇到熔浆就会死
     else   if (h_flag == ' # ' )
        h_coordi
= helen; // 遇到石头就原地不动
     return   true ;
}

 

void  FindWay::walk( const   char  pto, const   char  hto)
{
    Coordi new_paris(paris);
    Coordi new_helen(helen);
    Dou_coordi new_points;
    
if (alive(pto,new_paris,hto,new_helen))
        
// 如果不会被挡到,就看这一对点是不是已经来过了
    {
        new_points
= make_pair(new_paris,new_helen);
        Dcoordi_iter diter
= came_points.begin();
        
while (diter != came_points.end())
        {
            
if (diter -> second == new_points)
                
break ; // 已经来过了
            diter ++ ;
        }
        
if (diter == came_points.end())
            came_points.insert(make_pair(steps
+ 1 ,new_points));
            
// 没有来过,就把这一对点插入到came_points容器中
    }
}

 写好这些函数,再写get_steps()就不难了:

int  FindWay::get_steps()
{
    steps
= 0 ;
    came_points.insert(make_pair(steps,make_pair(paris,helen)));
        
// 将最开始的一对点插入到容器中
    Dcoordi_iter iter = came_points.begin();
    
while (iter != came_points.end())
    {
        pair
< Dcoordi_iter,Dcoordi_iter >  diter = came_points.equal_range(steps);
        
// 广度优先,从所有步数为steps的"点对(Dou_coordi)"分别出发
         while (diter.first != diter.second)
        {
            paris
= (diter.first -> second).first;
            helen
= (diter.first -> second).second;
            
int  me = meet();
            
if (me <= 1 )
                
return  steps + me; // 遇见!
            walk( ' n ' ,pton); // 向北
            walk( ' s ' ,ptos); // 向南
            walk( ' w ' ,ptow); // 向西
            walk( ' e ' ,ptoe); // 向东
            diter.second = came_points.upper_bound(steps);
                
// 更新diter.second迭代器,这很重要.
            diter.first ++ ; // 步数同为steps的下一点
        }
        iter
= diter.second;
        steps
++ ; // 步数加1
    }
    
return   - 1 ; // 无法遇见.
}

好吧,事实是,我是先写get_steps()函数,写着写着觉得再写几个私有成员函数就简洁了...

这应该不是最简单的方法,不过感觉类写的还是比较有逻辑性,比较易读的,比Helen不动的那个版本有了进步.

<三傻大闹宝莱坞>是一部难得的佳片,推荐!

附:代码文件(环境是VC++ 2008) 

你可能感兴趣的:(C++实践笔记(三)----找到公主啦,好开心啊!)