前两天在慕课网学习c++时,老师留下了一道作业题,关于迷宫算法的:自己设置一个迷宫,里面有起点和终点,然后程序要帮助闯关者找到逃生的路径【起点——出口】。当时老师给出的提示是使用“左手扶墙”或“右手扶墙”策略,大意就是,比如“左手扶墙”,从起点开始走时,就始终保持着左手扶墙的状态,这样一直走,最终你会找到迷宫的出口。嗯,这种方法确实挺实用的,但恐怕不适用于一些特殊的路形,比如“日”字型迷宫,如果我们把起点放在“日”字中间那一横的某一点上,那么无论闯关者使用左手扶墙还是右手扶墙策略,最终他都将陷入一个死循环中。。。
考虑到上面的这种情况,我觉得有必要新设计一个程序算法。而这种算法,我暂且称之为“灌水法”,顾名思义,就是在起点处一直灌水,最后水一定会从出口流出去。而活水所走的路径,就是程序所要寻找的路径。
程序的核心算法很简单,首先设计一个迷宫类,类里面开辟两个数组,一个是地图数组,一个是路径数组,除此之外,还需要两个变量,一个代表初始位置,一个代表出口位置。在地图数组中,每个元素用1和0来表示有路和无路;而在路径数组中,每个节点元素只能在这四个数值中选择一个:1、-1、16和-16,这是因为我用的是16*16的一维矩阵,而上面的四哥元素值可以分别对应矩阵中的上下左右。
程序运行后,首先调用maze类的初始化函数来设置迷宫,至于如何初始化迷宫程序员可以随意设置。重点在下面的两个函数,首先说put_water,它每次先寻找一个单向路径,如果找到,就记录下这个单向路径的走法,直到遇到一个岔路口为止,在此之后它会递归调用自身,依次进入每个岔路口并摸清它们的情况,因为函数调用栈开销的特性,这样当程序从某个死路回溯时,就会重新刷新原先记录的record数组。。。接下来是显示路径函数showpath,这个就要简单多了,无非是先把被破坏的迷宫复位,然后按照record数组记录的走法显示出逃生路径。其中,“森”字表示障碍物,“起”表示起点,“终”字表示出口位置,“人”字代表逃生所走的路径,本来老师要求的是动态显示出逃生过程,但为了本文的简洁,我就不写这些冗余的代码了,以下源代码
此为类声明:
class maze { public: maze(int m[], int r[], int , int ); int put_water(int, int); void showpath(int *); protected: int map[256], record[256], Start, Exit; };
此为类定义:
#include <iostream> #include "maze.h" using namespace std; maze::maze(int m[], int r[], int s, int e) { for (int i = 0; i < 256; i++) { map[i] = m[i]; if (i % 16 == 0) printf("\n"); if (m[i]) printf(" "); else printf("森"); } for (int i = 0; i < 256; i++) record[i] = r[i]; Start = s; Exit = e; } int maze::put_water(int p, int count)// p表示当前位置,count配合record记录以寻找的路径 { int new_p; while (map[p - 1] + map[p - 16] + map[p + 1] + map[p + 16] == 1) //如果当前位置不是岔路口,则一直记录直至到岔路口或出口为止 { if (p == Exit) { record[count] = 0; return 1; } map[p] = 0; //配合这个while循环,就这样一直把水灌满这个单向路 new_p = (p - 1)*map[p - 1] + (p - 16)*map[p - 16] + (p + 1)*map[p + 1] + (p + 16)*map[p + 16]; //寻找单向路的下一个节点 record[count] = new_p - p; //记录下来这个单向路的走法 count++; p = new_p; } map[p] = 0; if (map[p - 1] + map[p - 16] + map[p + 1] + map[p + 16] > 1) //这里表示这个路口属于岔路口,至少有两个选择,最多有三个选择 { if (1 == map[p - 1]) //先往上寻找,表达式为真表示上面有路 { record[count] = -1; if (1 == put_water(p - 1, count + 1)) //既然上面有路,就递归调用自身,看看上面的路情况如何 return 1; } if (1 == map[p - 16]) //再往左寻找,表达式为真表示左方有路 { record[count] = -16; if (1 == put_water(p - 16, count + 1)) //同上 return 1; } if (1 == map[p + 1]) //再往右寻找,表达式为真表示右方有路 { record[count] = 1; if (1 == put_water(p + 1, count + 1)) return 1; } if (1 == map[p + 16]) //最后往下寻找,表达式为真表示下面有路 { record[count] = 16; if (1 == put_water(p + 16, count + 1)) return 1; } } return 0; } void maze::showpath(int m[]) { printf("\n\n我们逃跑的路径是:\n"); for (int i = 0; i < 256; i++) { map[i] = m[i]; } map[10] = -9; for (int i = 0; record[i]; i++) { Start += record[i]; map[Start]++; } for (int i = 0; i < 256; i++) { if (i % 16 == 0) printf("\n"); switch (map[i]) { case 0: printf("森"); break; case 1: printf(" "); break; case 2: printf("人"); break; case 9: printf("起"); break; case -9: printf("终"); } } }
最后这个是主函数:
#include <iostream> #include "maze.h" int MAPARR[256] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 9, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, RECORD[256]; int main() { maze m(MAPARR,RECORD,149,26); if (0 == m.put_water(149, 0)) { printf("迷宫设计有误!"); return 0; } else m.showpath(MAPARR); return 0; }