C++实践笔记(三)----找到公主啦,好开心啊!
看完电影,突然想到Paris和Helen还在地牢里呆着呢,得在学泛型算法之前救他们出来啊.于是拿起纸和笔坐到马桶上,就开始想了.
回想起那个Helen不动的版本,算法也就是找到了所有的可能性,一步一步的走.那么,既然要找最短路径,我想,找遍所有的可能性是必须的,Helen会动也一样.怎样才能一点都不放过呢?在实践一里面,每走到一点,记录下走到这一点所花的步数,同时做个标记,就不会从别的路来这了.而且,搜寻的时候是广度优先,这样就保证了每到一点所花的步数都是最小的.
但是,Helen如果会动,情况就复杂了.比如可能为了让Helen从某个死胡同里面出来,Paris得重复经过某个点.既然Paris经过某个点可能不只一次,那么,什么是"唯一"的呢??一对点呗!如果某个时刻Paris在A点而Helen在B点,走了n步之后Paris又到了A同时Helen又到了B,那显然这n步是白走了!
好了,那我们搜寻的时候还是广度优先,找这样一对对的点,不就ok了?
冲水,开始动手写吧!
--------------------------
--------------------------
首先,我们定义FindWay类,学完了C++的容器部分,解决问题果然很有用啊.
先是类的私有成员,我是这样定义的:
typedef pair<Coordi,Coordi> Dou_coordi;
vector<vector<char> > maze;//存储地牢
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的行动方向.最后得出需要的步数.因此,我定义了下面几个公共成员函数:
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参数是为了返回错误信息.
接下来就可以写主函数了:
#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()这两个函数比较简单:
{
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;
}
//检查地牢是否是封闭的,以及是否放入了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是不是已经相遇了.因此,我定义了下面四个私有成员函数:
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()的定义如下:
{
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;
}
{
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()就不难了:
{
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不动的那个版本有了进步.
<三傻大闹宝莱坞>是一部难得的佳片,推荐!