[Algorithm]Maze Prim算法与A*寻路算法(中)
2016-02-06 00:28
387 查看
附上全文连接:
[Algorithm]Maze Prim算法与A*寻路算法(上)
[Algorithm]Maze Prim算法与A*寻路算法(中)
[Algorithm]Maze Prim算法与A*寻路算法(下)
文章中的所有源码下载链接附在“[Algorithm]Maze Prim算法与A*寻路算法(上) ”文章中。
上一篇中大致讲了下内容,接下来将要讲的是Maze Prim算法。
先附上一段Wiki的讲解:https://en.wikipedia.org/wiki/Maze_generation_algorithm#Randomized_Prim.27s_algorithm
其实算法是思路很简单就是不断的找墙拆墙的过程。大致流程如下:
1. 让迷宫所有节点均为墙,即没有路可走。
2. 选择一个偶数列和偶数行的节点设置为通路,然后把它的邻节点(邻墙)(上下左右,不要斜对角的=。=)存入邻节点列表中。
3. 如果邻节点列表中有数据,则执行以下循环。
——1. 从列表中随机选择一个邻节点,如果它相对与对应通路节点的对面节点不是通路的话,则执行以下循环。
————1.把该节点和对面节点设置为通路。
————2.把对面节点的邻节点加入列表。
——2.如果对面的节点已经是通路,则将其移除列表。
程序的大致流程如上,接下来就源码讲解,因为C# 控制台的版本可以直接运行,则以这个工程讲解。
“MazeCSharpTest”工程中只用看“Program.cs”即可,所有实现均在这个文件中。
需要定义节点类型枚举“WallType”和节点信息类“WallInfo”。
“WallType”枚举用于对墙的类型进行分类,如该墙的上左下有邻墙的话,则该墙的类型为“DownLeftUp”,更加形象点就是“┤”。
“WallInfo”中对“ToString”进行了重写,这有便于查看数据。“flag ”未使用,为扩展信息,当然如果有需要,可以在这个类中继续添加。“isFindRoad ”用于寻路后的路径节点,现不做讲解。
“BlockWallInfo”用于创建Prim迷宫时使用的墙信息。“wallDirect ”为对面的对面节点所在方向。
下面的创建流程中的主要代码。
1.初始化迷宫,将节点全部初始化为墙,且更新节点数及坐标。
通过这种做法,可以重复使用前面创建的对象,起到优化的作用。
2.创建迷宫。
输入的宽和高都不是实际的,实际的宽和高都是乘2加1了的。
将Prim算法单独写在一个函数中的好处是,如果还有其他的迷宫算法的话,可以再写一个方法将其替换即可。总之,这个函数起到的作用就是处理已经初始化好的迷宫列表“_maze”。
“UpdateType”作用是在创建完成之后更新节点信息类中的节点类型,位运算还是很实用的:-D。
3.接下来是重点中的重点,RandomPrim算法的实现。
算法中主要有一个需要访问的邻节点列表“neighBlockWallInfo ”。
首先会将(1,1)坐标的节点设置为通路即“hasWall”为false,然后将其周边邻节点添加到列表中。这样就初始化了邻节点列表。
然后循环判断列表是否为空,如果不为空,则随机取一个邻节点,并通过“BlockWallInfo ”中的“wallDirect”属性,取得对面的节点。
如果对面的节点不为空且“hasWall”为true即未打通,则打通该邻节点及对面的节点,均设置“hasWall”为false。并将对面节点的周边符合要求的邻节点存入列表中。
最后将这个已将访问了的节点移除列表。
当邻节点列表没有数据、循环退出的时候就是迷宫创建完成。
程序大致就是酱紫!如果有更好的实现方式或疑问请留言~
[Algorithm]Maze Prim算法与A*寻路算法(上)
[Algorithm]Maze Prim算法与A*寻路算法(中)
[Algorithm]Maze Prim算法与A*寻路算法(下)
文章中的所有源码下载链接附在“[Algorithm]Maze Prim算法与A*寻路算法(上) ”文章中。
上一篇中大致讲了下内容,接下来将要讲的是Maze Prim算法。
先附上一段Wiki的讲解:https://en.wikipedia.org/wiki/Maze_generation_algorithm#Randomized_Prim.27s_algorithm
其实算法是思路很简单就是不断的找墙拆墙的过程。大致流程如下:
1. 让迷宫所有节点均为墙,即没有路可走。
2. 选择一个偶数列和偶数行的节点设置为通路,然后把它的邻节点(邻墙)(上下左右,不要斜对角的=。=)存入邻节点列表中。
3. 如果邻节点列表中有数据,则执行以下循环。
——1. 从列表中随机选择一个邻节点,如果它相对与对应通路节点的对面节点不是通路的话,则执行以下循环。
————1.把该节点和对面节点设置为通路。
————2.把对面节点的邻节点加入列表。
——2.如果对面的节点已经是通路,则将其移除列表。
程序的大致流程如上,接下来就源码讲解,因为C# 控制台的版本可以直接运行,则以这个工程讲解。
“MazeCSharpTest”工程中只用看“Program.cs”即可,所有实现均在这个文件中。
需要定义节点类型枚举“WallType”和节点信息类“WallInfo”。
public enum WallType { None = 0, Up = 1, Down = 2, Left = 4, Right = 8, UpDown = 3, LeftRight = 12, LeftUp = 5, LeftDown = 6, RightUp = 9, RightDown = 10, LeftUpRight = 13, UpRightDown = 11, RigthDownLeft = 14, DownLeftUp = 7, LeftUpRightDown = 15 } public class WallInfo { public int x = -1; public int y = -1; /// <summary> /// 是否有墙 /// </summary> public bool hasWall = true; /// <summary> /// 是否是寻路路径点 /// </summary> public bool isFindRoad = false; /// <summary> /// 墙的类型 /// </summary> public WallType type = WallType.None; /// <summary> /// 状态 /// </summary> public int flag = 0; public void Set(int x, int y, bool hasWall = true, int flag = 0, bool isFindRoad = false, WallType type = WallType.None) { this.x = x; this.y = y; this.hasWall = hasWall; this.type = type; this.flag = flag; this.isFindRoad = isFindRoad; } public override string ToString() { return "[WallInfo:x=" + x + " y=" + y + " hasWall=" + hasWall + " type=" + type + " flag=" + flag + "]"; } } private class BlockWallInfo { public WallInfo info { get; private set; } public WallType wallDirect { get; private set; } public BlockWallInfo(WallInfo info, WallType wallDirect) { this.info = info; this.wallDirect = wallDirect; } public override string ToString() { return "[BlockWallInfo:wallDirect=" + wallDirect + " info=" + info + "]"; } }
“WallType”枚举用于对墙的类型进行分类,如该墙的上左下有邻墙的话,则该墙的类型为“DownLeftUp”,更加形象点就是“┤”。
“WallInfo”中对“ToString”进行了重写,这有便于查看数据。“flag ”未使用,为扩展信息,当然如果有需要,可以在这个类中继续添加。“isFindRoad ”用于寻路后的路径节点,现不做讲解。
“BlockWallInfo”用于创建Prim迷宫时使用的墙信息。“wallDirect ”为对面的对面节点所在方向。
下面的创建流程中的主要代码。
1.初始化迷宫,将节点全部初始化为墙,且更新节点数及坐标。
private void InitList() { int nLerp = _x * _y - _maze.Count; if (nLerp > 0) {//添加 while (nLerp > 0) { _maze.Add(new WallInfo()); --nLerp; } } else if (nLerp < 0) {//移除 _maze.RemoveRange(0, -nLerp); } //初始化 //设置第一个 _maze[0].Set(0, 0); //设置其他 for (int n = 1, length = _maze.Count; n < length; n++) { _maze .Set(n % _x, n / _x); } }
通过这种做法,可以重复使用前面创建的对象,起到优化的作用。
2.创建迷宫。
/// <summary> /// 创建迷宫 /// </summary> /// <param name="w">宽(>0)</param> /// <param name="h">高(>0)</param> /// <param name="randSeed">随机数</param> /// <returns>是否创建成功</returns> public bool Create(int w, int h, int randSeed) { if (w < 1 || h < 1) { return false; } //带墙的格子数 _x = w * 2 + 1; _y = h * 2 + 1; //初始化墙列表 InitList(); //初始化随机数 if (_randSeed != randSeed || _rand == null) { _rand = new Random(randSeed); } //生成迷宫 RandomPrim(); // //更新类型 UpdateType(); return true; } private void UpdateType() { for (int y = 0; y < _y; y++) { for (int x = 0; x < _x; x++) { if (!GetWallInfo(x, y).hasWall) { GetWallInfo(x, y).type = WallType.None; continue; } WallType type = WallType.None; //Left if (x - 1 >= 0 && GetWallInfo(x - 1, y).hasWall) { type |= WallType.Left; } //Right if (x + 1 < _x && GetWallInfo(x + 1, y).hasWall) { type |= WallType.Right; } //Down if (y - 1 >= 0 && GetWallInfo(x, y - 1).hasWall) { type |= WallType.Down; } //Up if (y + 1 < _y && GetWallInfo(x, y + 1).hasWall) { type |= WallType.Up; } GetWallInfo(x, y).type = type; } } }
输入的宽和高都不是实际的,实际的宽和高都是乘2加1了的。
将Prim算法单独写在一个函数中的好处是,如果还有其他的迷宫算法的话,可以再写一个方法将其替换即可。总之,这个函数起到的作用就是处理已经初始化好的迷宫列表“_maze”。
“UpdateType”作用是在创建完成之后更新节点信息类中的节点类型,位运算还是很实用的:-D。
3.接下来是重点中的重点,RandomPrim算法的实现。
/// <summary> /// 随机普里姆算法生成迷宫 /// </summary> private void RandomPrim() { //墙数组下标极限 int nWidthLimit = _x - 2; int nHeightLimit = _y - 2; //起点 int nTarX = 1; int nTarY = 1; //标记起点 GetWallInfo(nTarX, nTarY).hasWall = false; List<BlockWallInfo> neighBlockWallInfo = new List<BlockWallInfo>(); XmWallInfo tempWall = null; //记录邻墙,初始化需要拆的墙 if (nTarY < nHeightLimit) { tempWall = GetWallInfo(nTarX, nTarY + 1);//Up if (tempWall != null) { neighBlockWallInfo.Add(new BlockWallInfo(tempWall, XmWallType.Up));//记录邻墙及其所在方向 } } if (nTarY > 1) { tempWall = GetWallInfo(nTarX, nTarY - 1);//Down if (tempWall != null) { neighBlockWallInfo.Add(new BlockWallInfo(tempWall, XmWallType.Down)); } } if (nTarX < nWidthLimit) { tempWall = GetWallInfo(nTarX + 1, nTarY);//Right if (tempWall != null) { neighBlockWallInfo.Add(new BlockWallInfo(tempWall, XmWallType.Right)); } } if (nTarX > 1) { tempWall = GetWallInfo(nTarX - 1, nTarY);//Left if (tempWall != null) { neighBlockWallInfo.Add(new BlockWallInfo(tempWall, XmWallType.Left)); } } int nIndex = 0; BlockWallInfo tempBlackWall = null; XmWallInfo tempWall2 = null; while (neighBlockWallInfo.Count > 0)//是否还有未拆的墙 { //随机选择一面墙 nIndex = _rand.Next(neighBlockWallInfo.Count); //找出此墙对面的目标格 tempBlackWall = neighBlockWallInfo[nIndex]; switch (tempBlackWall.wallDirect) { case XmWallType.Up: nTarX = tempBlackWall.info.x; nTarY = tempBlackWall.info.y + 1; break; case XmWallType.Down: nTarX = tempBlackWall.info.x; nTarY = tempBlackWall.info.y - 1; break; case XmWallType.Left: nTarX = tempBlackWall.info.x - 1; nTarY = tempBlackWall.info.y; break; case XmWallType.Right: nTarX = tempBlackWall.info.x + 1; nTarY = tempBlackWall.info.y; break; } tempWall = GetWallInfo(nTarX, nTarY);//获取目标格 if (tempWall != null && tempWall.hasWall) { //连通目标格 tempWall.hasWall = false; tempBlackWall.info.hasWall = false; //添加目标格的邻格 if (nTarY > 1)//Down { tempWall = GetWallInfo(nTarX, nTarY - 1);//获取目标格下面的邻墙 if (tempWall != null && tempWall.hasWall) { tempWall2 = GetWallInfo(nTarX, nTarY - 2);//获取目标格下面邻墙下面的目标格 if (tempWall2 != null && tempWall2.hasWall) { neighBlockWallInfo.Add(new BlockWallInfo(tempWall, XmWallType.Down));//添加邻墙及其方向 } } } if (nTarY < nHeightLimit)//Up { tempWall = GetWallInfo(nTarX, nTarY + 1); if (tempWall != null && tempWall.hasWall) { tempWall2 = GetWallInfo(nTarX, nTarY + 2); if (tempWall2 != null && tempWall.hasWall) { neighBlockWallInfo.Add(new BlockWallInfo(tempWall, XmWallType.Up)); } } } if (nTarX < nWidthLimit)//Right { tempWall = GetWallInfo(nTarX + 1, nTarY); if (tempWall != null && tempWall.hasWall) { tempWall2 = GetWallInfo(nTarX + 2, nTarY); if (tempWall2 != null && tempWall2.hasWall) { neighBlockWallInfo.Add(new BlockWallInfo(tempWall, XmWallType.Right)); } } } if (nTarX > 1)//Left { tempWall = GetWallInfo(nTarX - 1, nTarY); if (tempWall != null && tempWall.hasWall) { tempWall2 = GetWallInfo(nTarX - 2, nTarY); if (tempWall2 != null && tempWall2.hasWall) { neighBlockWallInfo.Add(new BlockWallInfo(tempWall, XmWallType.Left)); } } } } //移除此墙 neighBlockWallInfo.RemoveAt(nIndex); tempWall = null; tempWall2 = null; tempBlackWall = null; } }
算法中主要有一个需要访问的邻节点列表“neighBlockWallInfo ”。
首先会将(1,1)坐标的节点设置为通路即“hasWall”为false,然后将其周边邻节点添加到列表中。这样就初始化了邻节点列表。
然后循环判断列表是否为空,如果不为空,则随机取一个邻节点,并通过“BlockWallInfo ”中的“wallDirect”属性,取得对面的节点。
如果对面的节点不为空且“hasWall”为true即未打通,则打通该邻节点及对面的节点,均设置“hasWall”为false。并将对面节点的周边符合要求的邻节点存入列表中。
最后将这个已将访问了的节点移除列表。
当邻节点列表没有数据、循环退出的时候就是迷宫创建完成。
程序大致就是酱紫!如果有更好的实现方式或疑问请留言~
相关文章推荐
- c#调用COM组件
- MySQL 优化
- 书评:《算法之美( Algorithms to Live By )》
- Google排名优化的几个影响因素
- 动易2006序列号破解算法公布
- DB2优化(简易版)
- C#实现把指定数据写入串口
- C#动态创建button的方法
- Mysql limit 优化,百万至千万级快速分页 复合索引的引用并应用于轻量级框架
- C#中抽象方法与虚拟方法的区别
- c#中虚函数的相关使用方法
- C#实现给图片加水印的方法
- C#使用加边法计算行列式的值
- C#实现多线程的同步方法实例分析
- C#中尾递归的使用、优化及编译器优化
- C#中的delegate委托类型基本学习教程
- C#实现子窗体与父窗体通信方法实例总结
- C#通用邮件发送类分享
- 对优化Ruby on Rails性能的一些办法的探究