迷宫问题算法设计与实现

迷宫求解  
求迷宫中从入口到出口的所有路径是一个经典的程序设计问题。由于计算机解迷宫时,通常用的是“穷举求解”的方法,即从入口出发,顺某一方向向前探索,若能走通,则继续往前走;否则沿原路退回,换一个方向再继续探索,直至所有可能的通路都探索到为止。为了保证在任何位置上都能沿原路退回,显然需要用一个后进先出的结构来保存从入口到当前位置的路径。因此,在求迷宫通路的算法中应用“栈”也就是自然而然的事了。  假设迷宫如下图所示:

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);
}
    原文作者:迷宫问题
    原文地址: https://blog.csdn.net/u011889952/article/details/44805377
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞