(我是一名普通的本科生,我会将我在编程做题时遇到的题目分享上来,这是我的第一篇文章,希望在此之后我的文章可以伴随大家一起成长。)
目录
迷宫问题:
总时间限制: 1000ms(其实这题根本不会超,迷宫太小了)
内存限制: 65536kB
问题描述:
定义一个二维数组:
int maze[5][5] = { 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, };
它表示一个迷宫,其中的1表示墙壁,0表示可以走的路,只能横着走或竖着走,不能斜着走,要求编程序找出从左上角到右下角的最短路线。
输入:
一个5 × 5的二维数组,表示一个迷宫。数据保证有唯一解。
输出:
左上角到右下角的最短路径,格式如样例所示。
样例输入:
0 1 0 0 0
0 1 0 1 0
0 0 0 0 0
0 1 1 1 0
0 0 0 1 0
样例输出:
(0, 0)
(1, 0)
(2, 0)
(2, 1)
(2, 2)
(2, 3)
(2, 4)
(3, 4)
(4, 4)
问题分析:
其实当我们看到这个迷宫的大小只有5*5的时候,心中就已经有数了:这题除非死循环,否则必然不会超时。所以问题就只剩下:如何把程序编出来。
这是一个搜索题,但其实我们完全可以不管什么是搜索,从正常人的角度出发,如果你站在左上角,你会怎么找到去往右下角的最短通路?
答案自然就是:试错,把每一条道路都走一遍,如果这条路无法通到终点,那就返回到之前的分岔点;如果这条路能通到终点而且还比其他通到终点的路更短,就将它记下来。最后,把所有路都走完之后,就可以将自己最终记录下的路径输出出来了。
如何实现这种试错方式呢?=>递归函数
优化:
虽然我在算法方面并不强,但是我们还是可以看到一些比较简单的优化。
1)如果有两条通路都可以通到终点,并且我们已经走完了第一条路,记录下了当前的最长路径长度(MaxLen),那么在尝试下一条路径时,如果我们发现它还未走到终点时,当前路径长(CurrLen)就已经超过了最长路径长(MaxLen),那么我们就不必将它走完,反正即使能够走到终点,也会因为太长而放弃这一方案。
if (CurrLen >= MaxCurrLen) {
return false;
}
2)注意到起点是左上角而终点是右下角,所以我们在尝试路径时,可以先考虑向右和向下移动。(向左和向上的话一定会让路径长度大于10(两临边长度之和))
int dx[4] = { 0,1,0,-1 };
int dy[4] = { 1,0,-1,0 };
3)在最初存储迷宫时,我们并不一定只存5*5的二维数组,存成7*7并将边界设成-1,这样在递归函数中就可以不用特地考虑边界了。同时存储一个7*7的bool数组来记录已经走过的地方。
int Maze[7][7];
bool Walked[7][7]{};
*4)一般我们会考虑的是设置两个vector来存储走过的路径,一个是当前的(curr),一个是最终最短的(ans),当前一旦走到终点并且小于当前最长路径,就让ans=curr。这种方式固然可以,但是在vector互相赋值时,要花费不小的时间(此题很小所以影响不大)。所以我们希望找到一个只存一个vector就可以的方式。
如果只存储一个vector的话,我们不能在递归函数一开始就将路径存储起来,这样会在最终回溯的时候又把它删掉。那么我们不妨从终点开始倒着存储,如果走到了终点,我们就在终点回溯的过程中不断存储路径(这样保存在最后面的一定是最短路径)。最终输出的时候,利用rbegin和rend实现倒序输出就可以了。
最终代码:
#include<iostream>
#include<vector>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
int Maze[7][7];
bool Walked[7][7]{};
int dx[4] = { 0,1,0,-1 };
int dy[4] = { 1,0,-1,0 };
int MaxCurrLen = 99;
int CurrLen = 0;
struct Position{
int x;
int y;
Position(int a,int b):x(a),y(b){}//构造函数
Position()=default;//默认构造函数
};
vector<Position> ans;//存储路径
inline bool Walk(Position now) {
bool ReachTheEnd = false;//是否到达了终点
CurrLen++;
bool flag = false;
if (now.x == 5 && now.y == 5) {
if (CurrLen < MaxCurrLen) {
MaxCurrLen = CurrLen;
}
ans.push_back(now);
return true;//返回已经到达终点
}
if (CurrLen >= MaxCurrLen) {
return false;//返回尚未到达终点就被剪枝
}
Walked[now.x][now.y] = true;
for (int i = 0; i < 4; i++) {
Position temp(now.x + dx[i], now.y + dy[i]);//尝试四个方向
if (Maze[temp.x][temp.y] == 0 && !Walked[temp.x][temp.y]) {//如果这个地方可以走并且没有被走过
ReachTheEnd = Walk(temp);//递归
if (ReachTheEnd) {//如果已经达到过终点,就在回溯的时候加进队列中
ans.push_back(now);
flag = true;
}
CurrLen--;//回溯时,当前走的路程长度要-1
}
}
return flag;
}
int main() {
memset(Maze, -1, sizeof(Maze));//设置边界为-1
for (int i = 1; i <= 5; i++) {
for (int j = 1; j <= 5; j++) {
scanf("%d", &Maze[i][j]);
}
}
Position now(1,1);
Walk(now);//从(1,1)开始递归
for (auto i = ans.rbegin(); i != ans.rend(); i++) {//倒序输出
printf("(%d, %d)\n", (*i).x - 1, (*i).y - 1);
if((*i).x==5&&(*i).y==5){//路径输出到终点
break;
}
}
}
总结:
这个题目最关键的就是优化过程中的第四条,希望可以对读者有所帮助,在处理一些规模较大的迷宫时,可以会减少vector复制的时间。(其实我也不清楚到底会快多少😂)
补充:广度优先搜索(我又来了)
广度优先搜索较为复杂,需要先对queue容器有一定的了解,所以我在这里不再多讲,只贴上代码,其实这个题搞懂上面的深度优先搜索就可以了。我会在之后的其他问题中讲一下广度优先搜索的。(其实是因为二次编辑太麻烦了😂)
#include<iostream>
#include<queue>
#include<cstring>
#include<cstdio>
using namespace std;
int Maze[7][7];
struct Position{
int x{};
int y{};
bool flag;
Position(int a,int b):x(a),y(b),flag(false){}//构造函数
Position():flag(false){};//默认构造函数
};
Position Walked[7][7]{};//利用这个数组存一下,这个点是否走过,以及上一个点的坐标
int dx[4] = { 0,1,0,-1 };
int dy[4] = { 1,0,-1,0 };
queue<Position> Ans;
inline bool Walk(Position& now) {
if(now.x==1&&now.y==1){
return true;
}
Walked[now.x][now.y].flag=true;
for(int i=0;i<4;i++){
if(!Walked[now.x+dx[i]][now.y+dy[i]].flag&&Maze[now.x+dx[i]][now.y+dy[i]]==0){
Position New(now.x+dx[i],now.y+dy[i]);
Walked[now.x+dx[i]][now.y+dy[i]].x=now.x;
Walked[now.x+dx[i]][now.y+dy[i]].y=now.y;
Ans.push(New);
}
}
Ans.pop();
return false;
}
int main() {
memset(Maze, -1, sizeof(Maze));//设置边界为-1
for (int i = 1; i <= 5; i++) {
for (int j = 1; j <= 5; j++) {
scanf("%d", &Maze[i][j]);
}
}
Position Begin(5,5);
Ans.push(Begin);
bool flag;
Walked[5][5].x=-1;
Walked[5][5].y=-1;
while(!Ans.empty()){
Position now=Ans.front();
flag=Walk(now);//从(5,5)开始递归,依然是倒着进行
if(flag) break;
}
int Left=1;int Right=1;
while(Walked[Left][Right].x!=-1&& Walked[Left][Right].y!=-1){
printf("(%d, %d)\n",Left-1,Right-1);
int temp=Walked[Left][Right].x;
Right=Walked[Left][Right].y;
Left=temp;
}
printf("(4, 4)\n");
}