隨機迷宮生成與尋路算法(1)深度優先搜索

迷宮生成算法(1)深度優先搜索

接下來一段時間,想要研究下隨機迷宮生成算法,打算在有空可時候偶爾更新一下這方面的學習過程。隨機迷宮的生成算法有很多種,比如遞歸回溯,遞歸分割,隨機Prime等等。今天是第一次嘗試隨機迷宮生成,就先試一下用遞歸的方法通過深度優先搜索來生成隨機迷宮。

首先我們來明確一下基本觀念,迷宮可以通過一個二維數組來表示,二維數組中的元素就表示存在於迷宮中的位置,他們可能是可以行走的路,也有可能是不能進入的障礙物或者圍欄。我們只要通過兩種不同的字符就可以標記障礙物和通道,比如我們使用false來表示一個位置是障礙物,而使用true來表示位置可以通行。在下面的例子中我們就沿用這個規定,整個迷宮可以使用一個二維的布爾型數組來表示。

接下來我們確定一下對迷宮的看法,我們認爲一個迷宮只能有唯一解,也就是說從起點到終點不會有兩條不一樣的路線。而遍歷迷宮的過程可以被看成是一個拆牆的過程,如果拆了一個牆會導致兩個已經被標記爲通道的方塊連接,那麼拆這面牆就是不合法的,這個條件是遞歸回溯過程中最重要的判定條件。

最後來看一下遞歸回溯算法的過程:

  1. 將初始位置設置爲當前位置(入棧),然後將它標記爲通路
  2. 從上,下,左,右四個方向尋找當前方塊的相鄰方塊,判斷找到的相鄰方塊周圍是否有其他通路,如果有則繼續尋找其他相鄰方塊,如果沒有則選擇該方塊爲當前方塊(入棧)
  3. 標記當前方塊爲通路,然後重複進行上一個過程
  4. 如果當前方塊的相鄰方塊都不滿足條件,則恢復上一個方塊爲當前方塊(出棧),並繼續執行過程2

以上就是深度優先遍歷生成隨機迷宮的基本步驟,接下來我們看代碼實現:

定義一個迷宮類,它的私有數據成員保存着記錄迷宮狀態的二維數組,查找方向,迷宮尺寸,入口點等信息:

class Maze
{
public:
    //構造函數,通過傳入的參數創建並創建並初始化二維數組
	Maze(int row, int column);
	ostream& print();
	//設置迷宮入口的內聯函數,注意這個入口並不是遍歷起點,而是在邊界上挖出的一個洞
	void setEntry(int x,int y)
	{

		if (isValidEntry(x,y)) {
			mazePtr[x - 1][y - 1] = true;
			if (x == 1) {
				startX = 2;
				startY = y;
			}
			if (x == row) {
				startX = row-1;
				startY = y;
			}
			if (y == 1) {
				startX = x;
				startY = 2;
			}
			if (y == column) {
				startX = 2;
				startY = column-1;
			}
			//cout << mazePtr[x - 1][y - 1];
		}
	}
    //創建迷宮
	bool createMaze();
private:
	bool isInRange(int x, int y);
	bool isValidEntry(int x, int y);
    //遞歸回溯的算法的核心實現
	bool dig(int x, int y);
	int startX;
	int startY;
	int row;
	int column;
	vector<pair<int,int>> direction = { {1,0},{0,1},{-1,0},{0,-1} };
	unique_ptr<bool*[]> mazePtr;
};

然後我們在類外完成對構造函數的定義,他接受行列兩個參數來動態創建一個布爾型二維數組,並與此同時初始化爲false,允許通過初始化列表來爲動態分配的內存初始化是C++11引入的新特性,這裏爲了方便管理堆內存,採用了智能指針類型unique_ptr來管理動態分配的內存,這種智能指針類型也是C++11新增的:

Maze::Maze(int row, int column):row(row),column(column)
{
	mazePtr.reset(new bool*[row]);
	for (int i = 0; i < row; i++) {
		mazePtr[i] = new bool[column]{false};
	}
}

定義成員函數createMaze,這個函數用來執行迷宮生成算法,通過調用另一個私有成員dig來完成:

bool Maze::createMaze()
{
	int x = startX;
	int y = startY;
	if (!dig(x, y)) {
		return false;
	}
	return true;
}

然後就是實現dig函數,正如他的名字所顯示的那樣,該函數所做的就是從一個起點開始挖掘障礙物,只要條件允許(存在不鄰接多個路徑的新方塊)他總是儘可能的去多挖掘新的方塊:

bool Maze::dig(int x, int y)
{
	if (!isInRange(x, y)) {
		return false;
	}
	mazePtr[x - 1][y - 1] = true;
	//隨機改變方向
	//random_shuffle(direction.begin(),direction.end());
	for (int i = 0; i < 4; i++) {
		int tmpX = x + 2 * direction[i].first;
		int tmpY = y + 2 * direction[i].second;
		if (!isInRange(tmpX, tmpY)) {
			continue;
		}
		if (mazePtr[tmpX-1][tmpY-1] == false) {
			mazePtr[x+direction[i].first-1][y+direction[i].second-1] = true;
			//print() << endl;
			if (!dig(tmpX, tmpY)) {
				mazePtr[x + direction[i].first - 1][y + direction[i].second - 1] = false;
				continue;
			}
		}
	}
	return true;
}

剩下的就是一些提供輔助功能的成員函數了,比如判斷指定方塊是否超出迷宮邊界,計算入口是否合法,通過入口尋找遍歷起點,打印迷宮狀態等等,他們的實現如下:

bool Maze::isInRange(int x, int y)
{
	if (x <= 0 or x >= row) {
		return false;
	}
	if (y <= 0 or y >= column) {
		return false;
	}
	return true;
}

bool Maze::isValidEntry(int x, int y)
{
	if ((x == 1 and y == 1) ||
		(x == 1 and y == column) ||
		(x == row and y == 1) ||
		(x == row and y == column)) {
		return false;
	}
	if (x == 1 or x == row or y == 1 or y == column) {
		return true;
	}
	return false;
}

ostream& Maze::print()
{
	for (int i = 0; i < row; i++) {
		for (int j = 0; j < column; j++) {
			cout << mazePtr[i][j] << " ";
		}
		cout << endl;
	}
	return cout;
}

調用這段代碼可以得到下圖這種迷宮:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-pbdu3SJu-1582995964414)(D:\cLearning\c++primer習題\文章\img\maze1\1.png)]

顯然這樣生成的迷宮不具備足夠的隨機性,接着在上面的遞歸回溯中添加一些小的改變,讓每次遍歷方向隨機選擇,這樣就可以得到下面這樣的代碼:

#include<iostream>
#include<vector>
#include<string>
#include<cctype>
#include<iterator>
#include<stdexcept>
#include<memory>
#include<math.h>
#include<algorithm>

using namespace std;

class Maze
{
public:
	Maze(int row, int column);
	ostream& print();

	void setEntry(int x,int y)
	{

		if (isValidEntry(x,y)) {
			mazePtr[x - 1][y - 1] = true;
			if (x == 1) {
				startX = 2;
				startY = y;
			}
			if (x == row) {
				startX = row-1;
				startY = y;
			}
			if (y == 1) {
				startX = x;
				startY = 2;
			}
			if (y == column) {
				startX = 2;
				startY = column-1;
			}
			//cout << mazePtr[x - 1][y - 1];
		}
	}
	bool createMaze();
private:
	bool isInRange(int x, int y);
	bool isValidEntry(int x, int y);
	bool dig(int x, int y);
	int startX;
	int startY;
	int row;
	int column;
	vector<pair<int,int>> direction = { {1,0},{0,1},{-1,0},{0,-1} };
	unique_ptr<bool*[]> mazePtr;
};

bool Maze::isInRange(int x, int y)
{
	if (x <= 0 or x >= row) {
		return false;
	}
	if (y <= 0 or y >= column) {
		return false;
	}
	return true;
}

bool Maze::dig(int x, int y)
{
	if (!isInRange(x, y)) {
		return false;
	}
	mazePtr[x - 1][y - 1] = true;
    
	//每次遍歷四個方向之前,對方向進行一次隨機洗牌
	random_shuffle(direction.begin(),direction.end());
    
	for (int i = 0; i < 4; i++) {
		int tmpX = x + 2 * direction[i].first;
		int tmpY = y + 2 * direction[i].second;
		if (!isInRange(tmpX, tmpY)) {
			continue;
		}
		if (mazePtr[tmpX-1][tmpY-1] == false) {
			mazePtr[x+direction[i].first-1][y+direction[i].second-1] = true;
			//print() << endl;
			if (!dig(tmpX, tmpY)) {
				mazePtr[x + direction[i].first - 1][y + direction[i].second - 1] = false;
				continue;
			}
		}
	}
	return true;
}
bool Maze::createMaze()
{
	int x = startX;
	int y = startY;
	if (!dig(x, y)) {
		return false;
	}
	return true;
}

bool Maze::isValidEntry(int x, int y)
{
	if ((x == 1 and y == 1) ||
		(x == 1 and y == column) ||
		(x == row and y == 1) ||
		(x == row and y == column)) {
		return false;
	}
	if (x == 1 or x == row or y == 1 or y == column) {
		return true;
	}
	return false;
}

ostream& Maze::print()
{
	for (int i = 0; i < row; i++) {
		for (int j = 0; j < column; j++) {
			cout << mazePtr[i][j] << " ";
		}
		cout << endl;
	}
	return cout;
}

Maze::Maze(int row, int column):row(row),column(column)
{
	mazePtr.reset(new bool*[row]);
	for (int i = 0; i < row; i++) {
		mazePtr[i] = new bool[column]{false};
	}
}

int main(int argc, char*argv[])
{
	Maze maze(15,15);
	maze.setEntry(1, 2);
	//cout << "設置入口後:" << endl;
	//maze.print();
	maze.createMaze();
	maze.print();
	system("pause");
	return 0;
}

這次結果如下,隨機性增強了很多:

在這裏插入圖片描述

這次就先做着寫,接下來在慢慢補充改進。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章