迷宫求解
求迷宫中从入口到出口的所有路径是一个经典的程序设计问题。由于计算机解迷宫时,通常用的是“穷举求解”的方法,即从入口出发,顺某一方向向前探索,若能走通,则继续往前走;否则沿原路退回,换一个方向再继续探索,直至所有可能的通路都探索到为止。为了保证在任何位置上都能沿原路退回,显然需要用一个后进先出的结构来保存从入口到当前位置的路径。因此,在求迷宫通路的算法中应用“栈”也就是自然而然的事了。 假设迷宫如下图所示:
1 1 1 1 1 1 1 1
1 0 1 1 0 1 0 1
1 0 1 0 0 1 0 1
1 1 0 3 1 0 1 1
1 0 0 1 0 0 4 1
1 0 0 0 0 1 1 1
1 0 1 0 0 1 0 1
1 0 1 0 0 0 1 1
1 1 1 1 0 0 0 1
1 1 1 1 1 1 1 1
假设“当前位置”指的是“在搜索过程中某一 时刻所在图中某个方块位置”,则求迷宫中一条路 径的算法的基本思想是:若当前位置"可通",则纳 入"当前路径",并继续朝“下一位置”探索,即切 换“下一位置”为“当前位置”,如此重复直至到 达出口;若当前位置“不可通”,则应顺着“来向” 退回到“前一通道块”,然后朝着除“来向”之外的其他方向继续探索;若该通道块的四周四个方块均“不可通”,则应从“当前路径”上删除该通道块。所谓“下一位置”指的是“当前位置”四周四个方向(东、南、西、北)上相邻的方块。假设以栈S记录“当前路径”,则栈顶中存放的是“当前路径上最后一个通道块”。由此,“纳入路径”的操作即为“当前位置入栈”;“从当前路径上删除前一通道块”的操作即为“出栈”。
求迷宫中一条从入口到出口的路径的算法可简单描述如下:
设定当前位置的初值为入口位置;
do
{
若当前位置可通,
则
{
将当前位置插入栈顶; // 纳入路径
若该位置是出口位置,则结束; // 求得路径存放在栈中
否则切换当前位置的东邻方块为新的当前位置;
}
否则
{
若栈不空且栈顶位置尚有其他方向未被探索,
则设定新的当前位置为: 沿顺时针方向旋转 找到的栈顶位置的下一相邻块;
若栈不空但栈顶位置的四周均不可通,
则
{
删去栈顶位置; // 从路径中删去该通道块
若栈不空,则重新测试新的栈顶位置,
直至找到一个可通的相邻块或出栈至栈空;
}
}
}while (栈不空);
源码:(普通方法)
#include <stdio.h> #include <stdlib.h> #define TMalloc(type,n) (type *)malloc((n)*sizeof(type)) #define GPATH '#' //宏定义当迷宫能够走通时通路路线的图形 #define GWALL '-' //宏定义当迷宫能够走通时非通路路线的图形 /* 结构体Tcell用于表示迷宫中一格的信息 node存放在迷宫中寻找出路时当前节点的位置信息, node_stack[]存放堆栈中的各个节点的数据 move[]存放上下左右四个方位 */ typedef struct cell { int row; //存放行号 int col; //存放列号 int dir; //存放4个方位, 0为右, 1为下, 2为左, 3为上 }TCell, *TCellPtr; int verify_mat(const char *, int*, int*); int ** read_map(const char *, int, int); void show_map(int ** ,int, int); void destroy_map(int ** ,int); int ** setup_mat(int, int); TCellPtr setup_stack(int, int); void search_maze(int **, int **, TCellPtr , int , int ); void show_route(int **, TCellPtr, int, int, int); // 寻找迷宫路径函数,如果存在出路,则把出路打印出来 void search_maze(int **maze, int **mark, TCellPtr pstack, int nrow, int ncol) { int move[4][2] ={{1, 0}, {0, 1}, {-1, 0}, {0, -1}}; int top = 1; // 路径堆栈的栈顶指针 int found = 0; // 标记是否找到迷宫出路,found=1表示找到出路 int orientation = 0; // orientation存放当前元素正遍历的方位,0为右, 1为下, 2为左, 3为上 int next_row, next_col; // next_row, next_col分别存放当前元素的下一个元素的行列值 TCell curcell; // 当前迷宫单元 // 将迷宫入口单元压入栈顶(注意迷宫外围增设了一层“外墙”,所以入口坐标为(1,1)) pstack[0].row = 1; pstack[0].col = 1; pstack[0].dir = 0; mark[1][1] = 1; // 对迷宫入口元素进行标记,表明该单元已经走过 while(top >= 0 && !found){ // 如果栈非空(没有退回到入口单元),并且没有找到迷宫出口,则持续寻找路径 curcell = pstack[top--]; // 将栈顶元素出栈 orientation = curcell.dir; // 取栈顶元素的方向值为当前方向 // 以curcell的当前方向为起点,顺时针遍历邻居单元 while(orientation < 4 && !found){ //当4个方向没有遍历完,并且没有找到迷宫出口 next_row = curcell.row + move[orientation][0]; // 计算当前方向的邻居单元的行号 next_col = curcell.col + move[orientation][1]; // 计算当前方向的邻居单元的列号 if(next_row == nrow - 2 && next_col == ncol - 2){ found = 1; //如果邻居单元恰好为迷宫出口,将found标志置为1 } else if(!maze[next_row][next_col] && !mark[next_row][next_col]) { // 如果邻居单元可达,并且未被访问过 mark[next_row][next_col] = 1; // 将该单元标记为已访问 pstack[top].dir = ++orientation; // 当前元素的下一个方向赋值给当前元素 top++; //将此元素放入栈顶 pstack[top].row = next_row; pstack[top].col = next_col; pstack[top].dir = 0; curcell.row = next_row; curcell.col = next_col; orientation = 0; //方向控制dir清零 printf("%d %d\n", next_row, next_col); } else orientation++; }// while(dir < 4 && !found) }//while(top > -1 && !found) //如果找到迷宫出路,则调用地图输出函数;否则给出提示告诉用户该迷宫没有出路 if(found){ show_route(maze, pstack, nrow, ncol, top); } else{ printf("\n 该迷宫没有出路!\n"); } } int main() { int nrow_map = 0, ncol_map = 0; const char *filename = "map.txt"; int **maze; int **mark; TCellPtr pstack; if(!verify_mat(filename, &nrow_map, &ncol_map)){ printf("请检查迷宫地图文件:文件读取失败,或地图每行的元素数目不相容\n"); exit(0); } // 读入地图文件 maze = read_map(filename, nrow_map, ncol_map); // 生成迷宫访问标记矩阵 mark = setup_mat(nrow_map, ncol_map); // 初始化路径探测堆栈 pstack = setup_stack(nrow_map, ncol_map); search_maze( maze, mark, pstack, nrow_map, ncol_map ); // 销毁为存储迷宫地图而动态分配的内存 destroy_map(maze, nrow_map); // 销毁为存储迷宫访问标记而动态分配的内存 destroy_map(mark, nrow_map); // 销毁路径探测堆栈 free(pstack); system("pause"); return 0; } // 校验迷宫地图 // 函数功能:从文件中读入矩阵信息,验证每一行元素个数是否相同,如果是则返回1,如果否则返回0 // 函数返回值:文件读取成功返回1,同时借助指针修改传入的两个整型参数的值为行数和列数 int verify_mat(const char *filename, int* pnrow_map, int* pncol_map) { int nrow_map = 0, ncol_map = 0, counter = 0; char temp; FILE *fp; fp=fopen(filename, "r"); if( fp){ //如果不能打开文件,提示用户迷宫文件未准备好,并退出程序 printf( "The file 'map.txt' was opened successfully for read.\n" ); } else{ printf( "The file 'map.txt' can not be opened!\n" ); exit(0); } //将迷宫矩阵的行号存入nrow_map中,列号存入ncol_map中,并检查迷宫矩阵的行列元素数是否都相等 while(1) { temp = fgetc(fp); if(temp == EOF) { // 文件读取完,跳出循环 if(counter != ncol_map){ //如果迷宫矩阵的列号不是全都相同,显示提示语,并退出程序 printf("\n 迷宫矩阵任意一列的元素个数必须相同,请修改 map.txt 中的迷宫图后再运行程序!"); return 0; } else{ nrow_map++; break; } } if(temp != '\n'){ counter++; } else{ // 读入换行符时 if( counter == ncol_map ) counter = 0; else { if( ncol_map == 0 ){ ncol_map = counter; counter = 0; } else{ //如果迷宫矩阵的行号不是全都相同,显示提示语,并退出程序 printf("\n迷宫矩阵任意一行的元素个数必须相同,请修改 map.txt 中的迷宫图后再运行程序!"); return 0; } } nrow_map++; } }//while(1) *pnrow_map = nrow_map; *pncol_map = ncol_map; fclose(fp); return 1; // 表示校验成功 } // 从文件filename出发建立迷宫地图二维数组 int ** read_map(const char *filename, int row, int column) { int **maze, i; char temp; FILE *fp; fp=fopen(filename, "r"); if( fp){ //如果不能打开文件,提示用户迷宫文件未准备好,并退出程序 printf( "The file 'map.txt' was opened successfully for read.\n" ); } else{ printf( "The file 'map.txt' can not be opened!\n" ); exit(0); } // 为迷宫地图申请存储空间 maze = TMalloc(int *, row); if(!maze){ printf( "为读入迷宫地图申请内存失败\n" ); exit(0); } for(i=0;i<row;i++){ maze[i] = TMalloc(int, column); if(!maze[i]){ printf( "为读入迷宫地图申请内存失败\n" ); exit(0); } } for(int i = 0; i < row ; i++){ for(int j = 0; j < column ; j++){ temp = fgetc(fp) - 48; //将迷宫中的ASSIC码转换成数字 if(temp != 0 && temp != 1){ printf("\n文件中的内容只能是0或1,请查看并修改!"); exit(0); } else{ //地图文件中的数值没有错误,将其存入到数组maze[][]中 maze[i][j] = temp; } //mark[i][j] = 0; //将文件中每个元素的遍历标记清零 //node_stack[j + i * row].dir = 0; //将栈的方向元素清零 } fgetc(fp); } fclose(fp); system("cls"); //清屏 printf("\n这是一个 %d行 %d列 的迷宫矩阵!\n", row, column); show_map(maze, row, column); return maze; } // 销毁为存储迷宫地图而动态分配的内存 void destroy_map(int **my_array, int nrow) { int i; for(i=0; i<nrow; i++) free(my_array[i]); free(my_array); } // 显示读入的迷宫数据信息 void show_map(int ** my_array, int row, int column) { int i, j; for(i=0; i<row; i++) { for(j=0; j<column; j++) { //my_array[i][j]=i; printf("%2d", my_array[i][j]); } printf("\n"); } } // 根据输入的行数和列数,动态生成二维数组,并填充全零 int ** setup_mat(int row, int column) { int **matrix, i; // 为二维数组申请存储空间 matrix = TMalloc(int *, row); if(!matrix){ printf( "为二维数组申请内存失败\n" ); exit(0); } for(i=0; i<row; i++){ matrix[i] = TMalloc(int, column); if(!matrix[i]){ printf( "为二维数组申请内存失败\n" ); exit(0); } } for(int i = 0; i < row ; i++){ for(int j = 0; j < column ; j++){ matrix[i][j] = 0; //将二维数组中每个元素的遍历标记清零 } } return matrix; } TCellPtr setup_stack(int nrow, int ncol) { TCellPtr ps; ps = TMalloc( TCell, nrow*ncol ); if(!ps){ printf( "为堆栈申请内存失败\n" ); exit(0); } return ps; } void show_route(int **maze, TCellPtr pstack, int nrow, int ncol, int top) { int i, j; int choice = 1; // 用于保存输出选项 int ** newmap = setup_mat( nrow, ncol); //newmap[][]存放迷宫的出路路径图中的迷宫单元 for(i = 0; i < nrow; i++) for( j = 0; j < ncol; j++) newmap[i][j] = GWALL; // 采用字符“#”填充整个迷宫 while(choice) { printf("\n请选择显示方式:\n 1.显示原始迷宫图\n 2.显示迷宫出路路径坐标\n 3.显示迷宫出路模拟图\n 0.结束游戏\n"); printf("\n请选择相应的数字: "); scanf("%d", &choice); getchar(); switch(choice){ //选择显示方式 case 0: break; case 1: show_map(maze, nrow, ncol); break; case 2: { printf("\n迷宫出路路径为:"); //输出出路路径 printf("\n行\t列"); for( i = 0; i <= top; i++) printf("\n%d\t%d", pstack[i].row, pstack[i].col); printf("\n%d\t%d",nrow, ncol); //choice = 0; break; } case 3: { // 采用符号“-”填充与路径相对应的迷宫单元 for( i = 0; i <= top; i++){ newmap[pstack[i].row][pstack[i].col] = GPATH; } newmap[nrow - 2][ncol - 2] = GPATH; // 填充出口单元 printf("\n迷宫的出路模拟显示图形如下\n"); //输出迷宫穿越路线图 for(i = 0; i < nrow; i++) { for( j = 0; j < ncol; j++){ printf("%c ", newmap[i][j]); } printf("\n"); } break; } default: { printf("输入错误,请重新输入!\n"); scanf("%d", &choice); } }//switch(choice) }//while(choice) destroy_map(newmap, nrow); // 回收为迷宫地图分配的内存空间 } //// 如果采用结构体表示迷宫的话 //typedef struct //{ // int ** maze; // 迷宫地图(二维数组) // int nrow; // 迷宫行数 // int ncol; // 迷宫列数 //}TMAP;
回溯法求解问题:
#include <iostream> #include <iomanip> #include <stdlib.h> using namespace std; #define MaxSize 100 int mg[10][10] = { //定义一个迷宫,0表示通道,1表示墙 {1,1,1,1,1,1,1,1,1,1}, {1,0,0,1,1,0,0,1,0,1}, {1,0,0,1,0,0,0,1,0,1}, {1,0,0,0,0,1,1,0,0,1}, {1,0,1,1,1,0,0,0,0,1}, {1,0,0,0,1,0,0,0,0,1}, {1,0,1,0,0,0,1,0,0,1}, {1,0,1,1,1,0,1,1,0,1}, {1,1,0,0,0,0,0,0,0,1}, {1,1,1,1,1,1,1,1,1,1}}; struct St //定义一个栈,保存路径 { int i; //当前方块的行号 int j; //当前广场的列号 int di; //di是下一可走方位的方位号 } St[MaxSize]; //定义栈 int top = -1; //初始化栈指针 void MgPath(int xi, int yi, int xe, int ye) //路径为从(xi,yi)到(xe,ye) { int i, j, di, find, k; top++; //初始方块进栈 St[top].i = xi;St[top].j = yi;St[top].di = -1; mg[xi][yi] = -1; while(top>-1) //栈不为空时循环 { i = St[top].i;j = St[top].j;di = St[top].di; if(i==xe && j==ye) //找到了出口,输出路径 { cout << "迷宫路径如下:/n"; for(k=0; k<=top; k++) { cout << "/t(" << St[k].i << "," << St[k].j << ")"; if((k+1)%5==0) cout << endl; //每输出五个方块后换一行 } cout << endl; return; } find = 0; while(di<4 && find==0) //找下一个可走方块 { di++; switch(di) { case 0:i = St[top].i-1; j = St[top].j; break; case 1:i = St[top].i; j = St[top].j+1; break; case 2:i = St[top].i+1; j = St[top].j; break; case 3:i = St[top].i; j = St[top].j-1; break; } if(mg[i][j]==0) find = 1; //找到通路 } if(find==1) //找到了下一个可走方块 { St[top].di = di; //修改原栈顶元素的di值 top++; //下一个可走方块进栈 St[top].i = i; St[top].j = j; St[top].di = -1; mg[i][j] = -1; //避免重复走到这个方块 } else //没有路可走,则退栈 { mg[St[top].i][St[top].j] = 0; //让该位置变成其它路径可走方块 top--; } } cout << "没有可走路径!/n"; } int main() { MgPath(1,1,8,8); }