前言
NEFU,计算机与控制工程学院,基于C/C++的数据结构 ,五个课程设计
环境
操作系统:Windows 10
IDE:Visual Studio Code、Dev C++ 5.11、Code::Blocks
课设目录
其他联系方式:
Gitee:@不太聪明的椰羊
B站:@不太聪明的椰羊
ps:一定要注明来意!
一、需求和规格说明
1.1 问题描述
可以输入一个任意大小的迷宫数据,用非递归的方法求出一条走出迷宫的路径,并将路径输出。
1.2 要求
写明:存储结构、基本算法(可以使用程序流程图)、源程序、测试数据和结果、算法的时间复杂度、另外可以提出算法的改进方法。
二、设计
2.1 设计思想
题目要求用非递归的方法求出一条走出迷宫的路径,并将路径输出,利用队列和广度优先遍历非递归的方法,可以直接找出一条最短路径。
题目基本要求:
BFSminroad()
(1)为方便输出路径,从终点开始往起点找路径,将终点入队列,设置距离为0;利用数组pre[][]存每一个可以走的点的前一个点的坐标,终点的前一个点设为(0,0),作为输出路径时的结束判定;
(2)取出队首存的点后将队首元素出队列,将此点能到达的点存入队列,并让这些点的距离为上一个点的距离+1,pre[][]存上一个点的坐标;
(3)当前所在点的上下左右四个方向的点的情况(能到达则入队列)全部查找完成后,循环第二步,直到将起点添加到队列中。说明此时已经找到了最短路径;
(4)通过while循环输出pre数组,循环条件为i != 0 && j != 0(终点设置的前点),即输出了最短路径。
Tip:当两条路径一样长时,最后会输出在分叉点先入队列的方向所在路径,因为汇集点(汇集点之前路径一样长)对应的pre会存先入队列方向的点的坐标,并访问汇集点,另外一条路径不再存入队列,最后保存的是在分叉点先入队列的方向所在路径,我的for循环查找四个方向是顺序是左、下、右、上,所以相同最短路径中输出方向优先级为左下右上。
时间复杂度:O(m*n) (最坏情况,所有的点都能走,每个点都入队列。)
题目拓展:利用栈和深度优先遍历递归的方法,可以将所有路径输出
DFSallroad()
访问起点,将起点入栈,调用递归函数
DFS()
取栈顶元素,判断是否为终点。若不是,继续探测栈顶元素的四个方向,探测到一个可达点后立刻访问该点,将其入栈并调用递归;若是,输出路径,回溯到上一个有多个(路径有分支)可达点的点,探测另外一条路径,回溯时清空访问信息并出栈。栈顶为终点时,栈底到栈顶的元素序列即为一条路径,将栈转存到另外一个栈中再输出,可实现从栈底到栈顶的输出。
时间复杂度:O(m*n) (最坏情况,所有的点都能走,每个点都入栈。)
2.2 设计表示(存储结构)
typedef struct node
{
int x,y;//地图坐标,从(1,1)开始
}mapnode;
三、解决方案
3.1 功能示意图
3.2 流程示意图
①建立地图信息流程
②BFS非递归求最短路径流程
③DFS递归求所有路径流程
四、调试报告
输入“m=4 n=4”,地图信息,正确的起点(1,2)和终点(2,4);
输出最短路径,并询问是否输出所有路径,输入“y”
输出所有路径,并询问是否重新输入起点和终点
输入错误的起点(6,6)和终点(2,6)
提示坐标不符合,重新输入
重新输入正确的起点(1,2)和终点(4,4)
选择输出所有路径后,选择不重新输入起点和终点,选择重新输入地图
输入“m=6 n=6”,地图信息,正确的起点(1,2)和终点(4,6);
输出最短路径,并询问是否输出所有路径,输入“y”
输出所有路径,不再重新输入起点终点,不再重新输入地图,退出系统
五、代码
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <stack>
#include <queue>
#define maxnum 1000
using namespace std;
typedef struct node
{
int x,y;//地图坐标,从(1,1)开始
}mapnode;
int flag=0;//判断能否到终点
int m, n;//地图的行m与列n
int **map;//地图信息,0可以走,1不能走
int **visited;//访问信息,0表示未访问,1表示已经访问过
int minsize;//最短路径长度
stack<mapnode> s,mins;//深度遍历求所有路径,存路径和最短路径的栈
queue<mapnode> q;
mapnode in, out,**pre;//起点、终点、当前点的前一个点 的坐标
int movex[4]={0,1,0,-1};
int movey[4]={-1,0,1,0};//对应左、下、右、上移动
void createmap(int m, int n);//输入地图信息,创建访问信息
void visited0();// 清空访问信息
void deleteinfo(int m, int n);//删除申请的空间
void outputmap(int m, int n); // 输出地图
void outputroad(stack<mapnode> s1, int n); 输出路径(坐标),从栈底到栈顶的输出,n为控制符,传递形参,不影响实参
void outputvisited(int m, int n);// 输出访问数组,检查用
int pass(mapnode p);// 检查点是否可以走
int BFSminroad(queue<mapnode> &q);//广度遍历,非递归求最短路径,用visited存路径长度
void DFSallroad(stack<mapnode> &s); // 深度遍历,递归查找所有路径
void DFS(stack<mapnode> &s); // 深度遍历,递归
void createmap(int m, int n) // 输入地图信息,创建访问信息
{
int i, j;
//mapnode test;
map = new int *[m+1];//m+1行
visited = new int *[m + 1];
//map = (int **)calloc(m+1,sizeof(int *));
//visited = (int **)calloc(m + 1, sizeof(int *));
for (i = 0; i <= m; i++)
{
map[i] = new int[n+1];// m+1行n+1列
visited[i] = new int[n+1];
//map[i] = (int *)calloc(n + 1, sizeof(int));
//visited[i] = (int *)calloc(n + 1, sizeof(int));
}
for (i = 1; i <= m; i++)
for (j = 1; j <= n; j++)
{
cin >> map[i][j];//输入地图信息
}
for (i = 1; i <= m; i++)//初始化访问数组为0
for (j = 1; j <= n; j++)
visited[i][j] = 0;
}
void visited0()// 清空访问信息
{
int i, j;
for (i = 1; i <= m; i++)//初始化访问数组为0
for (j = 1; j <= n; j++)
visited[i][j] = 0;
}
void deleteinfo(int m,int n)
{//删除申请的空间
int i;
for (i = 0; i <= m; i++)//从0开始,m+1个全释放
{
delete[] map[i];//释放一级指针
delete[] visited[i];
//delete[] pre[i];
//free(map[i]);
//free(visited[i]);
}
delete[] map;//释放二级指针
delete[] visited;
//delete[] pre;
//free(map);
//free(visited);
}
void outputroad(stack<mapnode> s1,int n)//传递形参,不影响实参
{//输出路径(坐标),从栈底到栈顶的输出,n为控制符
stack<mapnode> s2;
if(n==1)//n为控制符,n=1表示结合DFSallroad使用,每次输出时比较路径长度
{
if(s.size()-1<(unsigned int)minsize)//minsize>0,可强制转化为无符号数
{
mins = s;
minsize = s.size()-1;
}
}//n!=1时,单纯作为输出路径函数使用,不比较最短路径长度,不更新最短路径
while(!s1.empty())//将栈s1翻转存到栈s2中
{//实现从栈底到栈顶的输出
s2.push(s1.top());
s1.pop();
}
while(!s2.empty())
{
cout << '(' << s2.top().x << ',' << s2.top().y << ')';
s2.pop();
if (!s2.empty())
{
cout << "->";
}
}
cout << endl;
}
void outputmap(int m,int n)//输出地图
{
cout << endl;
int i, j;
for (i = 1; i <= m; i++)
{
for (j = 1; j <= n; j++)
{
if(map[i][j]<2)
cout << map[i][j] << ' ';
else if(map[i][j]==2)//起点
cout << "*"<<' ';
else if(map[i][j]==3)//终点
cout << "#"<<' ';
}
cout << endl;
}
cout << endl;
cout << "注:地图坐标从(1,1)开始" << endl;
cout << endl;
}
void outputvisited(int m,int n)//输出访问数组,检查用
{
int i, j;
cout << endl;
for (i = 1; i <= m; i++)
{
for (j = 1; j <= n; j++)
{
cout << visited[i][j] << ' ';
}
cout << endl;
}
cout << endl;
}
int pass(mapnode p)//检查点是否可以走
{
if (p.x<1 || p.x>m)//x坐标超出迷宫范围
{
return 0;
}
else if (p.y<1 || p.y>n)//y坐标超出迷宫范围
{
return 0;
}
else if (!(visited[p.x][p.y] == 0))//已经走过该点
{
return 0;
}
else if (map[p.x][p.y] == 1)//该点是障碍
{
return 0;
}
else
return 1; //可以走
}
int BFSminroad(queue<mapnode> &q)
{//广度遍历,非递归求最短路径,用visited存路径长度
int i, j, temp;
visited0();//清空访问信息
pre = new mapnode *[m + 1]; // m+1行
for (i = 0; i <= m; i++)
{
pre[i] = new mapnode[n+1];// m+1行n+1列
}
q.push(out);//终点入队列,从终点往起点找路径
pre[out.x][out.y].x= 0;
pre[out.x][out.y].y= 0;//终点的前点设为(0,0)
mapnode p,next;
while(!q.empty())
{
p = q.front();//取队列首元素
q.pop();// 队列首元素出队列
if(p.x==in.x&&p.y==in.y)//队首为起点时查找结束
{//能到达终点,输出路径和长度
flag = 1;//能到达终点标志
i = in.x;
j = in.y;
while (i != 0 && j != 0)
{
printf("(%d, %d)", i, j);
temp = i;
i = pre[i][j].x;
j = pre[temp][j].y;
if (i != 0 && j != 0)
{
cout << "->";
}
}
cout << endl;
cout << "最短路径长度为:" << visited[in.x][in.y] << endl;
for (i = 0; i <= m; i++)//从0开始,m+1个全释放
{
delete[] pre[i];
}
delete[] pre;
while(!q.empty())//清空队列
{
q.pop();
}
return 1;
}
for (i = 0; i < 4; i++)//循环遍历探测周围4个方向上的点
{
next.x = p.x + movex[i];
next.y = p.y + movey[i];
//printf("\n%d:%d,%d\n", i, next.x, next.y);
if (pass(next)&&(next.x!=out.x||next.y!=out.y))// next为p的下一个点
{
visited[next.x][next.y] = visited[p.x][p.y] + 1; // 访问对应点,距离+1
q.push(next);
pre[next.x][next.y].x = p.x;
pre[next.x][next.y].y = p.y;
}
}
}
cout << "找不到起点与终点间的路径,故无最短路径"<<endl;//while结束,队列空也没到终点
for (i = 0; i <= m; i++) // 从0开始,m+1个全释放
{
delete[] pre[i];
}
delete[] pre;
while(!q.empty())//清空队列
{
q.pop();
}
return 0;
}
void DFSallroad(stack<mapnode> &s)
{
visited0();
flag = 0;//重置有通路标志符
minsize = m * n;
visited[in.x][in.y] = 1;//访问起点
s.push(in);//起点入栈
DFS(s);
}
void DFS(stack<mapnode> &s)
{//深度遍历,递归查找所有路径
int i;
mapnode p,next;//p工作点,next为p的下一个点
p = s.top();//工作点为栈顶元素
if(p.x==out.x&&p.y==out.y)//栈顶元素为终点
{//能到达终点
outputroad(s,1);//输出路径——从栈底到栈顶
visited[p.x][p.y] = 0;//回退,恢复未访问状态
s.pop();
flag = 1;//能到达终点
return;//找到终点,结束递归
}
for (i = 0; i < 4; i++)//循环遍历探测周围4个方向上的点
{//i的不同,保证递归返回到分叉点时不再选择已选方向
next.x = p.x + movex[i];
next.y = p.y + movey[i];
if(pass(next))//next为p的下一个点
{//p.y = p.y + movey[i];// p.x = p.x + movex[i];
visited[next.x][next.y] = 1; //访问对应点
s.push(next);
DFS(s);//递归
}
}
s.pop();
visited[p.x][p.y] = 0;//回退,恢复未访问状态
if(s.empty())
{
if(flag==0)
{
cout << "找不到起点与终点间的路径";
}
else
{
cout << endl
<< "所有路径已输出。最短路径长度为"<<minsize<<",如下:" << endl;
outputroad(mins,0);
while(!mins.empty())//清空最小路径栈
{
mins.pop();
}
}
}
}
int main()
{
int flag2 = 1, flag3 = 1;
int x;
char y;
while(flag2==1)//一级循环
{
cout << "请输入迷宫的行数和列数:" << endl;
cin >> m >> n; // 迷宫第一个点坐标为(1,1)
cout << "请输入迷宫的信息(0表示可通过,1表示障碍物):" << endl;
createmap(m, n); // 输入地图信息
cout << "注:地图坐标从(1,1)开始" << endl;
cout << "请输入起点和终点:" << endl;
while(flag3==1)//输入起点终点,二级循环
{
cin >> in.x >> in.y;
cin >> out.x >> out.y;
if(in.x<1||in.x>m||in.y<1||in.y>n||out.x<1||out.x>m||out.y<1||out.y>n||(in.x==out.x&&in.y==out.y))
{//起点不能等于终点,起点终点不能为墙
system("cls");
cout << "你输入的地图如下" << endl
<< "0表示可通过,1表示障碍物" << endl;
outputmap(m, n);
cout<<"起点终点坐标不符合,请重新输入:"<<endl;
flag3 = 1;
continue;//进入下一轮二级循环,重新输入起点终点
}
else if (map[in.x][in.y] == 1 || map[out.x][out.y] == 1)
{
system("cls");
cout << "你输入的地图如下" << endl
<< "0表示可通过,1表示障碍物" << endl;
outputmap(m, n);
cout<<"起点终点坐标不符合,请重新输入:"<<endl;
flag3 = 1;
continue;//进入下一轮二级循环,重新输入起点终点
}
else
{
map[in.x][in.y] = 2; // 起点
map[out.x][out.y] = 3; // 终点
}
system("cls");
cout << "你输入的地图如下" << endl
<< "0表示可通过,1表示障碍物,*为起点,#为终点," << endl;
outputmap(m, n); // 打印地图
printf("从(%d,%d)到(%d,%d)的一条最短的路径如下:\n",in.x,in.y,out.x,out.y);
x = BFSminroad(q); // 输出最短路径
if (x == 1) // 有通路
{
cout << "是否输出所有路径?(Y or N)" << endl;
cin >> y;
if (y == 'y' || y == 'Y')
{
system("cls");
cout << "你输入的地图如下" << endl
<< "0表示可通过,1表示障碍物,*为起点,#为终点," << endl;
outputmap(m, n); // 打印地图
printf("从(%d,%d)到(%d,%d)的所有路径如下:\n",in.x,in.y,out.x,out.y);
DFSallroad(s);// 输出所有路径
cout << endl;
}
cout << "是否重新输入起点和终点?(Y or N)" << endl;
cin >> y;
if (y == 'y' || y == 'Y')
{
map[in.x][in.y] = 0; // 重置起点
map[out.x][out.y] = 0; // 重置终点
system("cls");
cout << "你输入的地图如下" << endl
<< "0表示可通过,1表示障碍物" << endl;
outputmap(m, n); // 打印地图
cout << "请输入起点和终点:" << endl;
flag3 = 1;
continue;//进入下一轮二级循环,重新输入起点终点
}
cout << "是否继续输入新地图?(Y or N)" << endl;
cin >> y;
if(y=='y'||y=='Y')
{
deleteinfo(m, n);//释放空间
flag2 = 1;
system("cls");
break;//结束二级循环
}
else
{
deleteinfo(m, n);//释放空间
flag2 = 0;
system("cls");
break;//结束二级循环
}
}
else//无通路
{
cout << "是否重新输入起点和终点?(Y or N)" << endl;
cin >> y;
if (y == 'y' || y == 'Y')
{
map[in.x][in.y] = 0; // 重置起点
map[out.x][out.y] = 0; // 重置终点
system("cls");
cout << "你输入的地图如下" << endl
<< "0表示可通过,1表示障碍物" << endl;
outputmap(m, n); // 打印地图
cout << "请输入起点和终点:" << endl;
flag3 = 1;
continue;//进入下一轮二级循环,重新输入起点终点
}
cout << "是否继续输入新地图?(Y or N)" << endl;
cin >> y;
if(y=='y'||y=='Y')
{
deleteinfo(m, n);//释放空间
flag2 = 1;
system("cls");
break;//结束二级循环
}
else
{
deleteinfo(m, n);//释放空间
flag2 = 0;
system("cls");
break;//结束二级循环,退出系统
}
}
}
}
return 0;
}
/*
test 1:
输入迷宫的行数和列数:
4 4
输入迷宫的信息(0表示可通过,1表示障碍物):
1 0 1 1
1 0 0 0
1 0 1 0
1 0 0 0
输入起点和终点:
1 2
2 4
查询所有路径
重新输入错误的起点、终点
6 6
2 6
提示起点、终点坐标不符合
重新输入正确的起点、终点
1 2
4 4
选择不重新输入起点和终点,选择重新输入地图
test2:
输入迷宫的行数和列数:
6 6
输入迷宫的信息(0表示可通过,1表示障碍物):
1 0 1 1 1 1
1 0 0 0 0 1
1 0 1 1 0 1
1 0 1 1 0 0
1 0 1 1 0 1
1 0 0 0 0 1
输入起点和终点:
1 2
4 6
选择输出所有路径
不再重新输入起点终点,不再重新输入地图
退出系统
*/