迷宫(暴力破解法)

一、 分析

  • 回溯法

对一个包括有很多个结点,每个结点有若干个搜索分支的问 题,把原问题分解为若干个子问题求解的算法;当搜索到某个结点发 现无法再继续搜索下去时,就让搜索过程回溯(回退)到该节点的前一 个结点,继续搜索该节点外的其他尚未搜索的分支;如果发现该结点 无法再搜索下去,就让搜索过程回溯到这个结点的前一结点继续这样 的搜索过程;这样的搜索过程一直进行到搜索到问题的解或者搜索完 了全部可搜索分支没有解存在为止。

  • 分类

迷宫问题可以分为三种类型

  1. 简单迷宫
  2. 多通路迷宫(通路中不带环)
  3. 环路迷宫(通路中带环)
 

迷宫的规定:

(0是墙,路是1,走过用2标记,入口点规定为(x=5,y=2),走的方向: 左 -> 上 -> 右 -> 下 )

 

对于简单迷宫可以运用(栈+循环),也可以用递归

对于无环路的多通路迷宫可以运用(栈+循环),也可以用递归

对于有环路的多通路迷宫用递归,回溯之前将走过的路重新置为通路

  • 扩展

在递归的前提下找最短通路:

1.递归+栈,递归回溯,保存路径栈

2.栈拷贝,和之前的最短路径作对比

二、顺序栈的基本操作(Stack.h)

初始化、销毁、增删改、查询栈顶元素、栈的元素的个数、栈满、栈空

#include <stdio.h>
#include <assert.h>
#include <string.h>

typedef Position StackDataType;


#define	MAX_SIZE	(100)

typedef struct Stack {
	StackDataType	array[MAX_SIZE];
	int	top;	// 表示当前个数
}	Stack;

// 初始化/销毁
// 增(只能从栈顶)/删(只能删除栈顶)/查(只能查看栈顶元素)
// 个数 / 是否空 / 是否满
// 增 -> 顺序表的尾插
// 删 -> 顺序表的尾删

void StackInit(Stack *pStack)
{
	pStack->top = 0;
}

void StackDestroy(Stack *pStack)
{
	pStack->top = 0;
}

void StackPush(Stack *pStack, StackDataType data)
{
	assert(pStack->top < MAX_SIZE);

	pStack->array[pStack->top++] = data;
}

void StackPop(Stack *pStack)
{
	assert(pStack->top > 0);

	pStack->top--;
}

StackDataType StackTop(const Stack *pStack)
{
	assert(pStack->top > 0);

	return pStack->array[pStack->top - 1];
}

int StackSize(const Stack *pStack)
{
	return pStack->top;
}

int StackFull(const Stack *pStack)
{
	return pStack->top >= MAX_SIZE;
}

int StackEmpty(const Stack *pStack)
{
	return pStack->top <= 0;
}
void StackCopy(Stack *pDest, Stack *pSrc) //栈拷贝
{
	memcpy(pDest->array, pSrc->array, sizeof(StackDataType)* pSrc->top);
	pDest->top = pSrc->top;
}

三、栈+循环实现简单迷宫

1.利用栈实现回溯

2.走过的路需要标记出来

3.找到出口后输出出口位置,程序退出

4.判断是否可以通过(a.是否越界 b.是否路是1)

  • 用来保存迷宫中的坐标
#define ROWS (6)
#define COLS (6)

// 坐标方向和平时不太一样,x 朝下,y 朝右
typedef struct {
	int	x;
	int y;
}	Position;

// 入口点
Position gEntry = { 5, 2 };
  • 判断是否走到出口,最后一列都是出口
int IsExit(Position pos)
{
	if (pos.y == COLS - 1) {
		return 1;
	}
	else {
		return 0;
	}
}
  • 判定是否可以走
int CanPass(Position pos)
{
	if (pos.x >= ROWS) {
		return 0;
	}

	if (pos.y >= COLS) {
		return 0;
	}

	return gMaze[pos.x][pos.y] == 1;
} 
  • 打印通路坐标
void PrintPath(Stack *pStack)
{
	Position at;
	for (int i = 0; i < pStack->top; i++) {
		at = pStack->array[i];
		printf("x = %d, y = %d\n", at.x, at.y);
	}
}
  • 实现迷宫
void RunMaze()
{
	// 需要一个栈,实现回溯
	Stack stack;
	StackInit(&stack);
	Position at;
	Position next;
	at.x = gEntry.x;
	at.y = gEntry.y;
	
	while(1)
	{
	    //标记位置
	    gMaze[at.x][at.y] = 2;
        
        //压栈,为回溯做准备
        StackPush(&stack, at);
        
        //判断是否走出
        if (IsExit(at)) 
        {
			PrintPath(&stack);
			return;
		}
		
		// 根据 左 -> 上 -> 右 -> 下 来尝试
	    next.x = at.x;
		next.y = at.y - 1;
		if (CanPass(next))
		{
			at.x = next.x;
			at.y = next.y;
			continue;
		}

		next.x = at.x - 1;
		next.y = at.y;
		if (CanPass(next)) 
		{
			at.x = next.x;
			at.y = next.y;
			continue;
		}

		next.x = at.x;
		next.y = at.y + 1;
		if (CanPass(next)) 
		{
			at.x = next.x;
			at.y = next.y;
			continue;
		}

		next.x = at.x + 1;
		next.y = at.y;
		if (CanPass(next)) 
		{
			at.x = next.x;
			at.y = next.y;
			continue;
		}
		
		//无法前行,开始回溯
		StackPop(&stack);	// 这里 pop 的是当前的 at
		if (StackEmpty(&stack)) {
			printf("没有出口\n");
			return;
		}

		at = StackTop(&stack);
		StackPop(&stack);
	}
}

四、用递归方法实现

递归尝试的方向不会重新尝试

  • 图形界面打印
void PrintMaze()
{
	for (int i = 0; i < ROWS; i++) {
		for (int j = 0; j < COLS; j++) {
			if (gMaze[i][j] == 0) {
				printf("█");
			}
			else if (gMaze[i][j] == 1) {
				printf("  ");
			}
			else if (gMaze[i][j] == 2) {
				printf("⊕");
			}
		}
		printf("\n");
	}

	printf("\n\n");
}
  • 最短路径
//看最小路径中的元素个数
int lpath = StackSize(&min);
  • 实现迷宫
//定义为全局变量
Stack path; //当前路径
Stack min; //最小路径

void RunMazeRec(Position at)
{
	Position next;
	StackPush(&path, at);

	// 标记走过位置
	gMaze[at.x][at.y] = 2;
	PrintMaze();

	if (IsExit(at)) 
	{
		// 如果当前路径(path)小于之前的最小路径(min),则当前路径是最短
		if (StackEmpty(&min) || StackSize(&path) < StackSize(&min)) {
			StackCopy(&min, &path);
		}

		PrintPath(&path);
		printf("============================\n");
		
		// 重新置为 1
		gMaze[at.x][at.y] = 1;

		StackPop(&path);
		return;	// 会发生回溯
	}

	// 根据 左 -> 上 -> 右 -> 下 来尝试
	next.x = at.x;
	next.y = at.y - 1;
	if (CanPass(next)) {
		RunMazeRec(next);
		PrintMaze();
	}

	next.x = at.x - 1;
	next.y = at.y;
	if (CanPass(next)) {
		RunMazeRec(next);
		PrintMaze();
	}

	next.x = at.x;
	next.y = at.y + 1;
	if (CanPass(next)) {
		RunMazeRec(next);
		PrintMaze();
	}

	next.x = at.x + 1;
	next.y = at.y;
	if (CanPass(next)) {
		RunMazeRec(next);
		PrintMaze();
	}

	// 重新置为 1
	gMaze[at.x][at.y] = 1;

	StackPop(&path);
	return;	// 回溯
}

五、测试用例 gMaze[ROWS][COLS]

//简单迷宫
int gMaze[ROWS][COLS] = {
	{ 0, 0, 0, 0, 0, 0 },
	{ 0, 0, 1, 0, 0, 0 },
	{ 0, 0, 1, 0, 0, 0 },
	{ 0, 0, 1, 1, 1, 0 },
	{ 0, 0, 1, 0, 1, 1 },
	{ 0, 0, 1, 0, 0, 0 }
};


//无环多通路
int gMaze[ROWS][COLS] = {
	{ 0, 0, 0, 0, 0, 0 },
	{ 0, 0, 1, 1, 1, 1 },
	{ 0, 0, 1, 0, 0, 0 },
	{ 0, 0, 1, 0, 0, 0 },
	{ 0, 0, 1, 1, 1, 1 },
	{ 0, 0, 1, 0, 0, 0 }
};

//多通路环路迷宫
int gMaze[ROWS][COLS] = {
	{ 0, 0, 0, 0, 0, 0 },
	{ 0, 0, 1, 1, 1, 0 },
	{ 0, 0, 1, 0, 1, 0 },
	{ 0, 0, 1, 0, 1, 0 },
	{ 0, 0, 1, 1, 1, 1 },
	{ 0, 0, 1, 0, 0, 0 }
};

猜你喜欢

转载自blog.csdn.net/qq_40355351/article/details/82051916