迷宫寻径问题(数据结构4.4.3)
这里的迷宫寻径问题是针对邓公《数据结构》第四节的再学习的总结,刚开始学数据结构看到这里的时候确实有点看不太明白,现在整体学完后再回归学习一下。
这里的迷宫寻径过程仍然是基于试探回溯法的。其数据结构与函数的建立分两部分:
- 数据结构:先创建两个枚举,Status存单元cell的状态,ESWN存单元cell的方向。然后建立了Cell类,其创建的单元为cell,内部数据为xy坐标,当前状态,进入方向和出去方向。这里值得一提的是,在后面的运算过程中并没有对除起点外的其他cell的xy坐标进行改变,根据讨论区的助教意思,这里没有太多的必要来进行xy的计算,因为最终的路径可以通过stack的size或者倒推incoming得出来。
- 函数:函数分为三个,方向变换函数nextESWN(东南西北顺序改变,更新当前cell的outgoing),邻格查询函数neighbor(根据当前cell的outgoing进入下个cell,查询这个cell的状态),邻格转入函数advance(实质性的转入,根据当前cell的outgoing进入下个cell,并更新这个cell的incoming)
主要的迷宫寻径算法仍分为试探与回溯两部分,当然在一切开始之前需要先检查当前迷宫的起点和终点是否都是可行的,不然的话直接退出。这里的迷宫我使用了邓公的dsacpp中的迷宫构造函数,并把随机生成改为固定生成,便于调试。之后再将迷宫的起点导入,其状态设为ROUTE表示已经进入路径,输入改为UNKNOWN(因为确实是未知的),然后压入用于存储路径的stack path中。
- 试探:试探与回溯都在一个大循环中进行,首先判断当前的栈顶元素是否到达终点,如果到达则直接返回true即可。如果没有,就开始试探工作,即修改当前栈顶cell的outgoing(由于初始化为UNKNOWN,所以nextESWN的过程可以最多按东南西北顺序遍历完成),一旦发现下个节点使AVAILABLE的或当前节点已经是NO_WAY状态则可直接跳出了(这个发现的过程通过neighbor函数来实现)。倘若发现下个节点处于AVAILABLE状态,则将当前节点通过advance函数实质性的转过去,进行下一步循环。
- 回溯:回溯的过程发生在发现当前cell的四个outgoing方向的节点全部都无法进行,自己的状态变为NO_WAY时,此时则需要回溯当前cell,即将当前cell的状态改为BACKTRACKED,然后将栈顶元素删除,将新的栈顶(即原栈顶的下一位)元素最为下一步循环的节点。(这里指的一提的是,由于nextESWN函数的方向改变是首先执行的,所以不会出现回溯后的节点在一个方向上死循环)
具体的代码如下:
#include <iostream>
#include <stack>
#include <time.h>
using namespace std;//现在所能得到的就是只能判断当前的迷宫是否能够找到出路,而最小路径由于概率不够随机的原因本人目前在DFS下暂时还没有实现/*迷宫寻径主流的三大算法:广度/深度优先搜素算法,以及A*算法*/
/*相对而言,深度优先搜索是最适合迷宫出去路径寻径的,通过一步一步的试探和回溯,能很快找到一条出去的路*/
typedef enum { AVAILABLE, ROUTE, BACKTRACKED, WALL } Status;
typedef enum { UNKNOWN, EAST, SOUTH, WEST, NORTH, NO_WAY } ESWN;
inline ESWN nextESWN(ESWN eswn) { return ESWN(eswn + 1); }static struct Cell
{int x, y; Status status; //xy的坐标与类型ESWN incoming, outgoing; //进入的方向与出去的方向Cell *prev;
};#define LABY_MAX 40
static Cell laby[LABY_MAX][LABY_MAX];
static int ncheck, nback, length;static inline Cell *neighbor(Cell *cell) //移动的探测,即得到当前cell的邻居,根据outgoing确定方向
{switch (cell->outgoing){case EAST:return cell + LABY_MAX;case SOUTH:return cell + 1;case WEST:return cell - LABY_MAX;case NORTH:return cell - 1;default:exit(-1); //如果不是这四个方向,即UNKNOWN和NO_WAY,则直接退出这个switch循环}
}static inline Cell* advance(Cell* cell) //实质性的移动,根据cell的incoming移动当前cell到对应的cell
{Cell *next;switch (cell->outgoing){case EAST:next = cell + LABY_MAX; next->incoming = WEST; next->x = cell->x + 1; break; //这里的操作意思是,现节点的进入为西,即相当于原节点的出是东case SOUTH:next = cell + 1; next->incoming = NORTH; next->y = cell->y + 1; break;case WEST:next = cell - LABY_MAX; next->incoming = EAST; next->x = cell->x - 1; break;case NORTH:next = cell - 1; next->incoming = SOUTH; next->y = cell->y - 1; break;default: exit(-1);}return next;
}static bool labyrinth(Cell Laby[LABY_MAX][LABY_MAX], Cell *s, Cell *t)
{if ((AVAILABLE != s->status) || (AVAILABLE != t->status)) return false; //首先,起点和终点必须是能访问的stack<Cell*> path; //栈中存放的都是指向cell单元的指针,这样对于栈的操作过程都是指针操作,能有效提升效率s->incoming = UNKNOWN; s->status = ROUTE; path.push(s); //将起点的进入点设为无,然后状态设为在路径上,最后入栈do{Cell *c = path.top(); //c是指向栈顶元素的指针,用于处理当前栈顶的节点数据if (c == t){length = path.size();return true; //迷宫的最终条件,找到终点}while (NO_WAY > (c->outgoing = nextESWN(c->outgoing))) //将c的出方向改为nextESWN枚举中的下个元素(未知,东南西北,无路)if (AVAILABLE == neighbor(c)->status) break; //遍历c的各个邻居(东南西北方向),一旦有可行的就跳出,不然就循环//注意上面的循环终止条件,要么是邻居可走就跳出,要么就是走到了NO_WAY,也就是无路可走,所以跳出//同时注意,这里是while循环,回溯之后的cell过此段代码时,会先nextESWN到下一个方向,不会出现一个方向无限循环的情况//这里有个很有意思的想法,既然在检查方向,其肯定会检查到其incoming的方向,但是前面可以看到,只要走过的路都会标成ROUTE,所以不会干涉if (NO_WAY <= c->outgoing) //说穿了,就是无路可走了,如同字面意思{c->status = BACKTRACKED; //将当前的节点c,即对应的栈顶元素标记为BACKTRACKED,即已经走过但是试探全部失败回溯的点,类似于忒休斯的标志path.pop(); //栈顶元素出栈,但是cell c本质上还是存在的,没有删除。从实质上实现回溯nback++;}else{path.push(c = advance(c)); //将c根据前面试探可行的方向移动之后,将移动后的c入栈(此时的C已经是一个新的cell指针了,没有指向之前的栈顶元素了c->outgoing = UNKNOWN; //新的c的出方向必然为未知c->status = ROUTE; //新的栈顶元素的标志改为ROUTE,表示进入路径试探了ncheck++;}} while (!path.empty()); //直到存储路径的path为空length = path.size();return false; //如果循环内没有实现true的返回,代表起点到终点没有路,那么最终只能返回false了
}/******************************************************************************************
* 输出某一迷宫格的信息
******************************************************************************************/
static void printLabyCell(Cell* elem)
{printf("%d -> (%d, %d) -> %d\n",((Cell*)elem)->incoming,((Cell*)elem)->x,((Cell*)elem)->y,((Cell*)elem)->outgoing);
}static int labySize; //此处借用dascpp中邓公的随机迷宫生成程序
static Cell* startCell;
static Cell* goalCell;
static void randLaby()
{labySize = LABY_MAX / 2 + rand() % (LABY_MAX / 2); //生成一个随机size的迷宫/*DSA*/printf("Using a laby of size %d ...\n", labySize);for (int i = 0; i < labySize; i++)for (int j = 0; j < labySize; j++){laby[i][j].x = i;laby[i][j].y = j;laby[i][j].incoming =laby[i][j].outgoing = UNKNOWN;laby[i][j].status = WALL; //边界格点必须是墙}for (int i = 1; i < labySize - 1; i++)for (int j = 1; j < labySize - 1; j++)if (rand() % 4) laby[i][j].status = AVAILABLE; //75%的格点为空可用startCell = &laby[rand() % (labySize - 2) + 1][rand() % (labySize - 2) + 1];goalCell = &laby[rand() % (labySize - 2) + 1][rand() % (labySize - 2) + 1];startCell->status = goalCell->status = AVAILABLE; //起始格点必须可用
}//这里同样借用的是邓公的迷宫显示代码
/******************************************************************************************
* 显示迷宫
******************************************************************************************/
static void displayLaby() { //┘└┐┌│─static char* pattern[5][5] ={"┼", "┼", "┼", "┼", "┼","┼", " ", "┌", "─", "└","┼", "┌", " ", "┐", "│","┼", "─", "┐", " ", "┘","┼", "└", "│", "┘", " "};//system("cls");printf(" ");for (int j = 0; j < labySize; j++)(j < 10) ? printf("%2X", j) : printf(" %c", 'A' - 10 + j);printf("\n");for (int j = 0; j < labySize; j++){(j < 10) ? printf("%2X", j) : printf(" %c", 'A' - 10 + j);for (int i = 0; i < labySize; i++)if (goalCell == &laby[i][j])printf("﹩");elseswitch (laby[i][j].status){case WALL: printf("█"); break;case BACKTRACKED: printf("○"); break;case AVAILABLE: printf(" "); break;default: printf("%s ", pattern[laby[i][j].outgoing][laby[i][j].incoming]); break; //老师这里的代码%s后面没有空格,需要加上,不然迷宫会乱掉}printf("\n");}
}int main()
{srand(int(time(0))); //根据系统时间确定随机种子,保证每次执行都不同randLaby();if (labyrinth(laby, startCell, goalCell))cout << "true" << endl;elsecout << "false" << endl;displayLaby();cout << "start: " << "(" << startCell->x << "," << startCell->y << ")"<< " " << "end: " << "(" << goalCell->x << "," << goalCell->y << ")" << endl;cout << "check times: " << ncheck << " back times: " << nback << endl;cout << "length of path is " << length << endl;return 0;
}
最终的结果展示我借用了邓公的迷宫显示函数,这样更形象一些,具体如下:
根据结果可以看到,对于一个16*16的随机迷宫,可以找到起点到终点的路径(true),其具体的路径如图所示,其中试探次数共68次,回溯次数20次(图中的O即为被回溯的节点),最终的路径长度为49。
从结果可以看到,邓公的这个算法可以实现找到一条从起点到终点的路径,但是似乎找到的路径并非是最短的路径,而关于这个最短的路径,我根据邓公的提示“一种简便而行之有效的策略是,每次都是按随机次序试探相邻格点。为此,需要改写nextESWN()函数(教材102页代码4.10)以及相关的数据结构。”尝试了一下,发现很难做到将nextESWN函数的每个节点的每次方向选值做到完全随机,最多只能做到每次运行时找到的路径都有所不同,但是路径的长度也有长有短。
所以对于邓公的这个算法如何改进,成为找到最短路径的做法,我目前已经没有了太好的思路,目前也已经在学堂在线的讨论区问了这个问题,不知道最终结果如何。
//1月23日下午更新
目前根据老师的数据结构在BFS下实现了最短路径的查找,通过结果的表现来看,本人现在认为对于最小路径的查找确实还是BFS算法更易于实现一些,其只需要一步一步的遍历,队列入队出队,到第一次出现OK的情况停止即可。当然有些情况下可能会有多种最优解,但是如果存在多种最优解,也可以在完成共级别的队列出入后实现再统计也可以。不仅如此,与广度优先搜索算法相关的迪克斯特拉算法也是对于带权网络的优秀解决算法。
迷宫寻径问题(数据结构4.4.3)相关推荐
- 栈(Stack)的应用—试探回溯法:八皇后问题、迷宫寻径
栈的应用 试探回溯法 1.八皇后问题 皇后类 struct Queen { //皇后类int x, y; //坐标Queen (int xx = 0, int yy = 0 ) : x(xx), y( ...
- 迷宫寻径--试探回溯法
空间区域限定为由n* n个方格组成的迷宫,除了四周的围墙,还有分布其间的若干障碍物:只能水平或垂直移动.我们的任务是,在任意指定的起始格点与目标格点之间,找出一条通路(如果的确存在). 1 #prag ...
- 数据结构迷宫代码_数据结构课程设计——迷宫求解(二)
前言 接上文的介绍,本文将主要介绍如何生成随机迷宫,在网上找到的资源也比较多,这里我选取了随机 Prim 算法生成迷宫,选择这个算法的理由如下: 算法思想简单,易于实现 生成的迷宫比较自然,不会出现明 ...
- C语言数据结构迷宫实验报告,数据结构c语言课程设计报告之迷宫
数据结构c语言课程设计报告之迷宫 C语言与数据结构课程设计报告学 号 ** 姓 名 ** 课程设计题目 迷 宫 求 解 2012 年 5 月目 录1 需求分析 1.1 功能与数据需求 1.1.1 题目 ...
- 栈和队列求解迷宫问题(数据结构学习笔记)
文章目录 迷宫问题 "栈"求解迷宫问题 行走规则 算法思路 算法代码 栈的定义 算法设计 完整代码 最终迷宫路径 算法总结 "队列"求解迷宫问题 算法思路 算法 ...
- 迷宫问题【数据结构实验报告】
数据结构实验报告 实验名称:实验二 迷宫问题 学号:*** 姓名:gnosed 实验日期:2017.10.23 一.实验目的 1.了解回溯法在求解迷宫问题中的应用 2.进一步掌握栈的使用 二.实验具体 ...
- 迷宫最短路径问题(数据结构4.4.3 拓展)
这是对于邓俊辉老师的<数据结构>一书的4.4.3节的迷宫最短路径算法的自我拓展.在上一篇博客中,我提到本人用DFS算法尝试不出最短路径的解决方法,所以在此篇博客中,本人采用了BFS算法,并 ...
- C++ 数据结构学习 ---- 栈及其应用
目录 1.头文件 1.1 Stack.h 1.2主文件头文件 2.栈的应用 2.1 进制转换器 2.2 进制转换器运行截图 2.3 括号匹配玉逆波兰表达式 2.4 括号匹配与逆波兰表达式对应的函数 2 ...
- 邓俊辉数据结构学习笔记2
列表 typedef int Rank; //秩 #define ListNodePosi(T) ListNode<T>* //列表节点位置template<typename T&g ...
最新文章
- web应用的负载均衡、集群、高可用(HA)解决方案
- shell xargs的用法
- win10pin不可用进不去系统_解决win7系统下连接网络打印机不可用的处理方法
- 数据挖掘—Apriori算法(Java实现)
- 使用valgrind检测内存问题
- VMware for mac inside error solutions
- 使用git命令把自己的代码上传到gitlab上
- 正则匹配图片地址 php,php正则匹配图片地址
- cannot be cast to android.support.v4.app.Fragment
- 网页上嵌入Flash播放器(2)
- 如何免费使用内网穿透
- Python解决数字棒球游戏
- 计算机桌面性能3.3,显卡天梯图2018年3月最新版 三月桌面显卡性能排行 (全文)
- bzoj4564: [Haoi2016]地图 仙人掌的圆方树 莫队 分块
- STM32工程文件的建立以及Keil软件的基本设置和修改
- java项目——大数据量的处理
- oracle数据结构体
- lightoj1219Mafia
- 微米纳米机器人 课件_部编版四年级语文下册7 纳米技术就在我们身边ppt课件1(共23张ppt)...
- 坐标正算和坐标反算的c语言,坐标正算程序坐标反算程序