主要的思路:
1、要有一个既定的规则,严格按照规则进行搜索,比如:上 下 左 右;
2、需要用到stack容器(new一个叫做openList的stack容器),因为stack具有先进后出的特性;
3、每一个节点都有“位置 (postion) 、在地图上的类型(type) 还有是否已经被探索过(isOpen) ”三种属性;
过程:
每次探路过后,把经过的节点都压入栈中,每压入一个节点,把这个节点的属性改为已经被探索过;
然后把栈顶的节点设为当前节点继续往下探路,如果遇到岔路口则按照 “上 下 左 右” 的搜索规则来进行压栈;
找到第一个能走的方向就继续前进,如果遇到死路则从栈顶开始弹栈,一直返回到上一个岔路口,继续进行下一个方向的探索;
如果探索到了终点,也一样开始往外弹栈,每弹出一个节点,用另一stack容器接收,最终从stack容器挨个取出节点,打印出一条从起点到终点的路。
上代码:(可以直接全选复制使用)
using System;
using System.Collections.Generic;
using System.Threading;
namespace DeepFirst
{
class Program
{
static void Main(string[] args)
{
DeepFindPath fp = new DeepFindPath();//创建一个对象
fp.PrintMap();
fp.Init();
Pos start = new Pos() { x = 1, y = 1 };
Pos end = new Pos() { x = 18, y = 18 };//给到起点和终点的坐标
Stack<Pos> path = fp.PathFind(start, end);//传入起点终点坐标寻路
while (path.Count > 0)
{
Pos pos = path.Pop();
SetCursor(pos.x, pos.y);
Console.WriteLine("○");
Thread.Sleep(60);
SetCursor(pos.x, pos.y);
}//弹栈寻路
SetCursor(30, 0);
}
static public void SetCursor(int x, int y)
{
Console.SetCursorPosition(2 * y, x);
}
}
//Main函数调用,我这里写的不太好,主函数里内容有点多
//应该用一些方法包装好,但是应该比较容易理解
struct Pos { public int x, y; }
class DeepFindPath
{
public int[,] mapData = new int[,]
{
{10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10},
{10,0,0,0,0,0,0,0,0,0,10,0,10,0,10,0,0,0,0,10 },
{10,10,10,10,10,0,10,10,10,0,10,0,10,0,10,0,10,10,0,10 },
{10,0,0,0,10,0,0,0,10,0,10,0,10,0,10,0,10,0,0,10 },
{10,0,10,0,10,0,10,0,10,0,10,0,10,0,0,0,10,0,10,10 },
{10,0,10,0,10,0,10,0,10,0,10,0,10,10,10,10,10,0,10,10 },
{10,0,10,0,0,0,10,0,0,0,10,0,0,0,0,0,0,0,0,10 },
{10,0,10,10,10,10,10,0,10,0,10,0,10,10,10,10,0,10,10,10 },
{10,0,10,0,0,0,0,0,10,0,10,0,10,0,0,10,0,0,0,10 },
{10,0,10,10,10,10,10,10,10,0,0,0,10,0,0,10,10,10,10,10 },
{10,0,0,0,0,0,0,0,10,10,10,10,10,10,0,10,0,10,0,10 },
{10,0,10,10,10,10,10,0,0,0,0,0,0,10,0,10,0,10,0,10 },
{10,0,10,0,0,0,10,10,10,10,10,10,0,10,0,10,0,10,0,10 },
{10,0,10,0,10,0,10,0,0,0,0,10,0,10,0,10,0,0,0,10 },
{10,0,10,0,10,0,10,0,10,10,10,10,0,10,0,10,0,10,0,10 },
{10,0,10,0,10,0,0,0,10,0,0,0,0,0,0,10,0,10,0,10 },
{10,0,0,0,10,0,10,0,10,0,10,10,10,10,10,10,0,10,0,10 },
{10,0,10,10,10,10,10,0,10,0,10,0,0,0,0,0,0,10,0,10 },
{10,0,10,0,0,0,0,0,10,0,0,0,10,10,10,10,0,10,0,10 },
{10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10},
};//地图数据
int[,] dir = new int[,]
{
{-1,0 },//上
{ 1,0 },//下
{0,-1 },//左
{ 0,1 },//右
};//探路的规则,上下左右,用二维数组装坐标
public void PrintMap()
{
for (int i = 0; i < mapData.GetLength(0); i++)
{
for (int j = 0; j < mapData.GetLength(1); j++)
{
switch (mapData[i, j])
{
case 0:
Console.Write(" ");
break;
case 10:
Console.Write("■");
break;
default:
Console.WriteLine("没有{0}这种类型", mapData[i, j]);
break;
}
}
Console.WriteLine();
}
}//打印地图的方法
class Tile
{
public Pos position;
public bool isOpen;
public int type;
public Tile(int x, int y, int mapDate)
{
isOpen = false;
position.x = x;
position.y = y;
type = mapDate;
}
}//内部类,定义节点
Tile[,] tiles;//Tile数组接收节点
public void Init()
{
if (tiles == null)
{
tiles = new Tile[mapData.GetLength(0), mapData.GetLength(1)];
}
for (int i = 0; i < mapData.GetLength(0); i++)
{
for (int j = 0; j < mapData.GetLength(1); j++)
{
tiles[i, j] = new Tile(i, j, mapData[i, j]);
}
}
}//给每一个节点附上属性,Pos(坐标)、
//type(也就是地图上的类型,初始化的时候已经设定mapeData=type)
Stack<Tile> openList = new Stack<Tile>();
//设一个 Stack 容器,取名为 openList,用来装探索过的tile
public Stack<Pos> PathFind(Pos start, Pos end)
{
openList.Clear();//首相清空openList
PushTile(tiles[start.x, start.y]);
//1、起点压栈
//这里还缺一个起点就是终点的判断,我没有加上
Stack<Pos> path = new Stack<Pos>();
//再设一个 stack容器,接收弹出来的节点,返回一个路径 Path
while (openList.Count > 0) //当openList不为空是开始探路的过程
{
Thread.Sleep(100);//给一个延迟
Tile curTile = openList.Peek();
//设一个当前节点,为openList的第一个节点,第一次循环进来的时候即为起点
bool canMove = false;//定一个是否能移动的布尔值备用
for (int i = 0; i < dir.GetLength(0); i++)//因为有四个方向,循环四次探路
{
Tile tile = tiles[curTile.position.x + dir[i, 0], curTile.position.y + dir[i, 1]];
//按上下左右探索,是否能移动在下面判断
if (tile.type != 10 && tile.isOpen == false /* && 边界条件(若地图是开放的)*/)
{
if (tile.position.Equals(end))//终点判断
{
PushTile(tile);
while (openList.Count > 0)
{
path.Push(openList.Pop().position);
}
return path;
}//如果是终点,开始弹栈,同时把弹出的节点压进 Path 里返回出来
else
{
canMove = true;
PushTile(tile);
break;
}
//如果四个方向都探索过了,没有找到终点
//把canMove标记为true,再把节点压进栈里
//因为如果遇到岔路口的话,找到一个方向能走就不再探索其他方向
//直接往能走的方向继续走了,所以这里要跳出循环
//PushTile 方法在下面,这里还要把节点的isOpen属性设为true
}
}
if (canMove)
{
continue;
}
//为了外层大循环继续,探索下一个节点,
//这里要 continue 一下,继续下个节点的探索
else
{
Tile popedTile = openList.Pop();
Program.SetCursor(popedTile.position.x, popedTile.position.y);
Console.WriteLine(" ");
}
//如果一个节点的四个方向都已经探索过了,
//都不能走,那么会来到这个 else 的判断,开始弹栈
//把已经被弹出栈的节点记录一下,打上空格清除路径,相当于一路返回,
//一直返回到上一个岔路口,进入另一个方向
//因为岔路口的另一个方向的 isOpen属性 还是 false 所以会往另一个方向继续探路
}
return path;
}
void PushTile(Tile tile)
{
openList.Push(tile);
tile.isOpen = true;
Program.SetCursor(tile.position.x, tile.position.y);
Console.WriteLine("□");
}
}
}