OpenJudge 迷宫问题

(我是一名普通的本科生,我会将我在编程做题时遇到的题目分享上来,这是我的第一篇文章,希望在此之后我的文章可以伴随大家一起成长。)

目录

迷宫问题:

问题描述:

输入:

输出:

样例输入:

样例输出:

问题分析:

优化:

最终代码:

总结:


迷宫问题:

总时间限制: 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");
}

 

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值