自己在看严蔚敏的数据结构时看到了迷宫求解问题,但印象中在数据结构的课堂上老师好像并没有讲这个问题,我们当时的教材就是这本书。老师当时可能觉得这个问题不太好讲,而且以当时大多数学生的学习状态来看,讲了也不一定有人听,听了也不一定会懂,懂了也不一定能记住。so 我不讲,考试也不考,节省师生双方的时间。反观我自己,学完这门课,对书里讲过这么一个问题竟然也毫无印象。这么看来老师采取上边的讲法也不无道理。哎,大学教学现状可略见一斑啊。
大概思路是先用一个数据结构man 来模拟走迷宫,结构里存储位置信息(坐标),和方向信息。然后用栈,每走一步把相应的man信息入栈。然后按照固定的顺序,RIGHT DOWN LEFT UP 遍历搜索此位置周围的点是否能走通,能走通则把下一个位置上的man入栈,继续以此点为新起点向下探索,一旦把方向都探索完发现此点周围没有能走通的路,则出栈,返回上一位置继续探索,直至走到出口或者栈为空即迷宫找不到任何一条通路为止。
回到主题。迷宫求解问题简单描述 用一个二维数组来模拟迷宫,0表示能通过,1表示不能通过。给定入口maze[0][1]和出口maze[9][8] 然后写出算法让计算机模拟找到从入口走到出口的通路,2表示走通的路线
Maze maze =
Maze maze =
{
{1,0,1,1,1,1,1,1,1,1},
{1,0,1,1,1,1,0,0,0,1},
{1,0,1,1,1,1,0,1,1,1},
{1,0,1,0,0,0,0,0,0,1},
{1,0,0,0,1,1,0,1,0,1},
{1,1,1,1,1,1,0,1,0,1},
{1,1,0,0,0,0,0,0,0,1},
{1,1,1,1,1,0,1,1,0,1},
{1,1,1,0,0,0,0,1,0,1},
{1,1,1,1,1,1,0,0,0,1}
};
template<class Type>
class Stack
{
public:
Stack()
{
capacity = STACK_SIZE;
base = new Type[capacity];
top = 0;
}
~Stack()
{
capacity = 0;
delete []base;
base = NULL;
top = 0;
}
public:
bool isfull()const
{
return top >= capacity;
}
bool isempty()const
{
return top == 0;
}
void push(Type &x)
{
if(!isfull())
{
base[top++] = x;
}
}
void pop()
{
if(!isempty())
{
top--;
}
}
Type gettop()const
{
if(!isempty())
{
return base[top-1];
}
}
private:
enum{STACK_SIZE=100};
Type *base;
size_t top;
size_t capacity;
};
/
#define ROW 10
#define COL 10
typedef enum
{
RIGHT=1,
DOWN,
LEFT,
UP,
}DIR;
typedef int Maze[ROW][COL];//10x10的二维数组 模拟迷宫
//表示位置
typedef struct Pos
{
int x;
int y;
bool operator==(const Pos &p)
{
return ((x==p.x)&&(y==p.y));
}
}Pos;
//模拟人
typedef struct Man
{
Pos pos;
DIR di;
}Man;
void ShowMaze(Maze maze)
{
for(int i=0; i<10; ++i)
{
for(int j=0; j<10; ++j)
{
cout<<maze[i][j]<<" ";
}
cout<<endl;
}
}
bool Pass(Maze maze, Pos pos)
{
return maze[pos.x][pos.y] == 0;
}
//将能走通的位置标记为2
void FootMaze(Maze maze,Pos pos)
{
maze[pos.x][pos.y] = 2;
}
Pos NextPos(Pos pos, DIR di)
{
switch(di)
{
case RIGHT:
pos.y += 1;
break;
case DOWN:
pos.x += 1;
break;
case LEFT:
pos.y -= 1;
break;
case UP:
pos.x -= 1;
break;
}
return pos;
}
bool PassMaze(Maze maze,Pos start, Pos end)
{
Man man; //pos di
Stack<Man> st;
Pos curpos = start;
do//重复探索 直至curpos==end找到出口,或者栈变空(没有任何一条通路)
{
if(Pass(maze,curpos))//如果当前点curpos能通过 则将man的位置标记到此点,设置默认探索方向从RIGH开始
{
man.pos = curpos;
man.di = RIGHT; //默认从RIGHT开始探索
FootMaze(maze,man.pos);
if(curpos == end)
return true;
st.push(man);
curpos = NextPos(man.pos,man.di);
}
else
{
if(!st.isempty())
{
man = st.gettop();
while(man.di==UP && !st.isempty())//如果curpos不能通过,且man周围的点都探索完毕,人就往后退
{
st.pop();
MarkMaze(maze,man.pos);
if(!st.isempty())
{
man = st.gettop();
}
else//栈空了 说明回到出口了,此迷宫没有通路
{
return false;
}
}
if(man.di < UP)//curpos点不通,则继续按方向顺序探索man周围的下一个点
{
st.pop();
man.di = (DIR)(man.di+1);
st.push(man);
curpos = NextPos(man.pos,man.di);
}
}
}
}while(!st.isempty());
return false;
}
void main()
{
Pos start = {0,1};
Pos end = {9,8};
Maze maze =
{
{1,0,1,1,1,1,1,1,1,1},
{1,0,1,1,1,1,0,0,0,1},
{1,0,1,1,1,1,0,1,1,1},
{1,0,1,0,0,0,0,0,0,1},
{1,0,0,0,1,1,0,1,0,1},
{1,1,1,1,1,1,0,1,0,1},
{1,1,0,0,0,0,0,0,0,1},
{1,1,1,1,1,0,1,1,0,1},
{1,1,1,0,0,0,0,1,0,1},
{1,1,1,1,1,1,0,0,0,1}
};
ShowMaze(maze);
if(!PassMaze(maze,start,end))
{
cout<<"没有出路!"<<endl;
return;
}
cout<<"-------------------"<<endl;
ShowMaze(maze);
}
此算法用栈的存储特点,模拟人前进和后退,能前进则一直前进,遇到死路则回退换方向继续探索。这样如果存在通路的话必能找出一条。但只能找出一条,且不保证是最短。
继续改进式算法使其能找出迷宫的所有通路。思路是这样的,上述算法已经能找出一条通路,在此基础上按原路线往回一步一步退,每退一步,就以此点为新的start点递归调用PassMaze(),直至最外层的栈退为空,这样就能找出所有通路。
如上图找出第一条通路后向回退到(8,8)以此点为start递归调用PassMaze(),除了刚退回来那个方向外其他方向都不为0,PassMaze()返回,同理继续出栈回退到(7,8),退到(6,8)时调用PassMaze()能找到向左的一条通路(6,8)(6,7)(6,6)(6,5)(7,5)(8,5)(8,6)(9,6)(9,7)(9,8)然后在此层PassMaze()中往回退,找到其他通路则继续递归调用PassMaze()
改后算法
void PassMaze(Maze maze,Pos start, Pos end,DIR dir)
{
//每次迷宫的入口都是可行的,可直接将此点man入栈
Man man; //pos di
Stack<Man> st;
Pos curpos = start;
man.pos = curpos;
man.di = dir;//为了控制递归探索时,不重复探索已存在的路线,新增参数dir 控制入口点的起始探索方向
st.push(man);
FootMaze(maze,man.pos);
curpos = NextPos(man.pos,man.di);
do
{
if(Pass(maze,curpos))
{
man.pos = curpos;
man.di = RIGHT;//除了入口点以外 其他点还是默认从RIGHT开始探索
FootMaze(maze,man.pos);
if(curpos == end)//当找到终点时 输出线路,并适当处理,递归继续寻找其他通路
{
ShowMaze(maze);
st.push(man);
// RePassMaze(maze,end,st);
Pos rstart;
DIR rdir;
//找到一条通路后回退 先将终点出栈
man=st.gettop();
st.pop();
CleanFoot(maze,man.pos);//终点标记为0
// maze[man.pos.x][man.pos.y]=0;
// 如果栈没空,即还没退回线路的起点,就逐步后退 逐个点探索
while(!st.isempty())
{
man=st.gettop();
st.pop();
CleanFoot(maze,man.pos);//把刚退出的位置标记为0,因为其他通路也有可能通过此点,
//但因为有dir方向的控制,不会探索出完全一样的线路
// maze[man.pos.x][man.pos.y]=0;
rstart.x=man.pos.x;
rstart.y=man.pos.y;//设置新的起点
rdir=(DIR)(man.di+1);//设置新的方向,已经探索过的死路不必再探,同时防止再走刚退回来的那条路
PassMaze(maze,rstart,end,rdir);//以新起点递归
}
return;
}
st.push(man);
curpos = NextPos(man.pos,man.di);
}
else
{
if(!st.isempty())
{
man = st.gettop();
while(man.di==UP && !st.isempty())
{
st.pop();
// MarkMaze(maze,man.pos);
CleanFoot(maze,man.pos);
if(!st.isempty())
{
man = st.gettop();
}
else
{
return ;
}
}
if(man.di < UP)
{
st.pop();
man.di = (DIR)(man.di+1);
st.push(man);
curpos = NextPos(man.pos,man.di);
}
}
}
}while(!st.isempty());
void main()
{
Pos start = {0,1};
Pos end = {9,8};
Maze maze =
{
{1,0,1,1,1,1,1,1,1,1},
{1,0,1,1,1,1,0,0,0,1},
{1,0,1,1,1,1,0,1,1,1},
{1,0,1,0,0,0,0,0,0,1},
{1,0,0,0,1,1,0,1,0,1},
{1,1,1,1,1,1,0,1,0,1},
{1,1,0,0,0,0,0,0,0,1},
{1,1,1,1,1,0,1,1,0,1},
{1,1,1,0,0,0,0,1,0,1},
{1,1,1,1,1,1,0,0,0,1}
};
ShowMaze(maze);
PassMaze(maze,start,end,RIGHT);
}
这样就找出了所有通路
以此为基础再考虑最短路线就容易了,比较找到每一条通路时人走的步数,即“总栈”的长度,但由于是递归调用的,每一层相当于是一个新栈,这样想计算总长度的话是不是要在每一层调用时还得把当前外层栈的长度传递进去,最后把每一层的长度都加起来。。。好像也不是很好处理