迷宫问题算法设计与实现

迷宫求解

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

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);
}
时间: 2024-11-10 11:34:29

迷宫问题算法设计与实现的相关文章

算法设计与分析 - 李春葆 - 第二版 - pdf-&gt;word v3

1 1.1 第1章─概论 2 3 1.1.1 练习题 4 1. 下列关于算法的说法中正确的有( ). 5 Ⅰ.求解某一类问题的算法是唯一的 6 Ⅱ.算法必须在有限步操作之后停止 7 Ⅲ.算法的每一步操作必须是明确的,不能有歧义或含义模糊 8 Ⅳ.算法执行后一定产生确定的结果 9 A. 1个 B.2个 C.3个 D.4个 10 2. T(n)表示当输入规模为n时的算法效率,以下算法效率最优的是( ). 11 A.T(n)= T(n-1)+1,T(1)=1 B.T(n)= 2n2 12 C.T(n)

(转)常用的算法设计与分析-一夜星辰的博客

算法设计与分析 分治法 思想 1. 将一个规模为n的问题分解为k个规模较小的子问题,这些子问题互相独立且与原问题相同.递归地解这些子问题,然后将各子问题的解合并得到原问题的解. 2. divide-and-conquer(P) { if(|P| <= n0)adhoc(P); divide P into samller subinstances P1,P2...,Pk; for(int i = 1;i < k;i++) { yi = divide-and-conquer(Pi); } retu

AACOS:基于编译器和操作系统内核的算法设计与实现

AACOS:基于编译器和操作系统内核的算法设计与实现 [计算机科学技术] 谢晓啸 湖北省沙市中学 [关键词]: 编译原理,操作系统内核实现,算法与数据结构,算法优化 0.索引 1.引论 1.1研究内容 1.2研究目的 1.3研究提要 正文 2.1研究方法 2.2编译器部分 2.2.1从计算器程序中得到的编译器制作启示 2.2.2在编译器中其它具体代码的实现 2.2.3编译器中栈的高级应用 2.2.3编译器中树的高级应用 2.2.4编译器与有限状态机 2.3操作系统内核部分 2.3.1操作系统与底

“谁是大V”算法设计 (Map-Reduce TopN)

作业设计与资料 链接: http://pan.baidu.com/s/1o6MJTyi 密码: 628d 一.     作业要求   根据关注列表relsample.json文件,设计MapReduce算法得到被关注次数最多的前十人,即寻找谁是大V. 二.     算法设计   第一个Mapper用于解析json文件: 从relsample.json文件中解析出ids列表的内容,即每个人所关注的人的列表.输出的value为列表中的每一项,k为one. 对应的文件为\src\myMapper\re

[迷宫中的算法实践]迷宫生成算法&mdash;&mdash;Prim算法

       普里姆算法(Prim算法),图论中的一种算法,可在加权连通图里搜索最小生成树.意即由此算法搜索到的边子集所构成的树中,不但包括了连通图里的所有顶点(英语:Vertex (graph theory)),且其所有边的权值之和亦为最小.该算法于1930年由捷克数学家沃伊捷赫·亚尔尼克(英语:Vojtěch Jarník)发现:并在1957年由美国计算机科学家罗伯特·普里姆(英语:Robert C. Prim)独立发现:1959年,艾兹格·迪科斯彻再次发现了该算法.因此,在某些场合,普里姆

【字符串处理算法】将输入字符串中的各个单词反序的算法设计及C代码实现

一.需求描述 输入一个字符串,编写程序将该字符串中的各个单词反序拼装并输出.例如,如果输入的字符串是"Hello, how do you do",那么输出的字符串为"do you do how Hello,".注意保留各个单词之间的空格及相应的标点符号. 二.算法设计 通过观察示例字符串(即"Hello, how do you do"),我们可以看到该字符串中各个单词与空格之间的关系为:单词总数=空格总数+1.也就是说,示例字符串中的空格总数为4

《算法之道》精华 算法设计部分

<算法之道>精华 算法设计部分 本书作者邹恒明,作者另有一本书<数据结构之弦>,以及<操作系统之哲学原理>都是非常好的书 这本书能够算得上是深入浅出.文笔非常好,作者加入了非常多自己的思考 本文仅包含算法设计部分,算法分析略去,并没有严格依照章节顺序来记录 附录 算法随想 有人喜欢遍历,希望踏遍千山万水,人生丰富多彩:有人一生贪婪,眼界不宽,及时行乐:有人注定穷搜,辛辛苦苦,收获有限:有人善用时空均衡,用最少的时间办最多的事情.十分精明:有人会分治,再难的问题也能解决.

《数据结构与算法分析:C语言描述》复习——第十章“算法设计技巧”——Alpha-Beta剪枝

2014.07.08 22:43 简介: “搜索”与“剪枝”几乎是如影随形的.此处的“搜索”指的是带有回溯算法的深度优先搜索. 在之前的“Minimax策略”中我们给出了一个三连棋的程序,运行后你就知道计算一步棋要花多少时间. 为了计算最优的一步棋,我们可能需要递归9万多次.如果毫无疑问这种阶乘式的穷举过程必须通过剪枝来加速. 本篇介绍一种用于Minimax策略的剪枝思路——α-β剪枝. 剪枝的英语是pruning,所以不要想当然说成trimming. 图示: 在上一篇讲解Minimax策略的博

【转载】数据结构与算法设计

原文: 数据结构与算法设计