一、深度优先遍历
深度优先遍历就是往一个方向找,直到找到死路就返回,这个性能消耗很大,游戏里几乎不会用,一般用在图论相关的算法里
public class Solution {
public IList<IList<int>> res=new List<IList<int>>();
public IList<IList<int>> AllPathsSourceTarget(int[][] graph) {
dfs(graph,0, new List<int>(){0});
return res;
}
public void dfs(int[][] graph,int x, List<int> path){
//终止条件
if(x==graph.Length-1){
res.Add(new List<int>(path));
return;
}
//回溯过程
for(int i=0;i<graph[x].Length;i++){
path.Add(graph[x][i]);
dfs(graph,graph[x][i], path);
path.RemoveAt(path.Count-1);
}
}
}
二、广度优先遍历
广度优先遍历就是一圈一圈的往外找,由于遍历的圈数就是遍历的深度,而且没次遍历都是取出离当前节点最近的节点,所以默认路径就是最短路径
节点类定义,提供+运算符重载与判断相同的方法
public struct Node
{
public int x;
public int y;
public Node(int x, int y)
{
this.x = x;
this.y = y;
}
public static Node operator +(Node node1, Node node2)
{
int sumX = node1.x + node2.x;
int sumY = node1.y + node2.y;
return new Node(sumX, sumY);
}
public bool IsSame(Node other)
{
if (other.x == x && other.y == y)
{
return true;
}
return false;
}
}
using System;
using System.Collections.Generic;
using System.Data.SqlClient;
namespace 寻路算法
{
//广度优先
public class PathFinder
{
private Node[] offsets = new Node[] { new Node(1, 0), new Node(0, -1), new Node(-1, 0), new Node(0, 1) };
//传入一个二维数组表示地图,1代表障碍物,0代表可行走路径
public void BreadFirst(int[][] map, Node start, Node end)
{
Queue<Node> que = new Queue<Node>();
//记录每个节点的父节点,用来返回路径
Dictionary<Node, Node> camFrom = new Dictionary<Node, Node>();
camFrom[start] = new Node(-1, -1);
que.Enqueue(start);
// camFrom[start] = new Node(-1,-1);
bool hasRoute = false;
while (que.Count>0)
{
Node cur = que.Dequeue();
if (cur.IsSame(end))
{
hasRoute = true;
break;
}
foreach (Node offset in offsets)
{
Node newPos = cur + offset;
if (newPos.x < 0 || newPos.y < 0 || newPos.x >= map.Length || newPos.y >= map[0].Length)
{
continue;
}
if (camFrom.ContainsKey(newPos))
{
continue;
}
//障碍物检测,如果该节点是障碍物就不加入到列表中
if (map[newPos.x][newPos.y] == 1)
{
continue;
}
que.Enqueue(newPos);
camFrom[newPos] = cur;
}
}
if (hasRoute)
{
//找到了路径 ,就打印出来,注意这里是从终点到起点的路径
Node pos = end;
while (camFrom.ContainsKey(pos))
{
Console.WriteLine();
pos = camFrom[pos];
}
}
}
}
}
三、德吉斯特拉算法
德吉斯特拉算法运用了贪心的思想,与广度优先相比引入了代价函数,每次都选择距离起点最近的节点,减少了遍历的次数
//迪杰斯特拉
public class Dijkstra
{
public int manhattan(Node a, Node b)
{
return Math.Abs(a.x - b.x) + Math.Abs(a.y - b.y);
}
private Node[] offsets = new Node[] { new Node(1, 0), new Node(0, -1), new Node(-1, 0), new Node(0, 1) };
//传入一个二维数组表示地图,1代表障碍物,0代表可行走路径
public void DijkstraFind(int[][] map, Node start, Node end)
{
List<Node> sortList = new List<Node>();
//记录每个节点的父节点,用来返回路径
Dictionary<Node, Node> camFrom = new Dictionary<Node, Node>();
camFrom[start] = new Node(-1, -1);
sortList.Add(start);
// camFrom[start] = new Node(-1,-1);
bool hasRoute = false;
while (sortList.Count>0)
{
sortList.Sort((Node a,Node b)=>
{
//用曼哈顿算法从小到大排序,选出消耗最小的路径
return manhattan(start, a) - manhattan(start,b);
});
Node cur = sortList[0];
sortList.RemoveAt(0);
if (cur.IsSame(end))
{
hasRoute = true;
break;
}
foreach (Node offset in offsets)
{
Node newPos = cur + offset;
if (newPos.x < 0 || newPos.y < 0 || newPos.x >= map.Length || newPos.y >= map[0].Length)
{
continue;
}
if (camFrom.ContainsKey(newPos))
{
continue;
}
//障碍物检测,如果该节点是障碍物就不加入到列表中
if (map[newPos.x][newPos.y] == 1)
{
continue;
}
sortList.Add(newPos);
camFrom[newPos] = cur;
}
}
if (hasRoute)
{
//找到了路径 ,就打印出来,注意这里是从终点到起点的路径
Node pos = end;
while (camFrom.ContainsKey(pos))
{
Console.WriteLine();
pos = camFrom[pos];
}
}
}
}
四、A星算法
A星算法是一种常用的启发式搜索算法,A*算法不但记录其到源点的代价,还计算当前点到目标点的期望代价
启发式算法是什么
启发式算法是一种问题解决方法,通过利用经验、规则或者启发性信息来指导搜索过程,以找到问题的近似最优解。启发式算法通常用于解决那些复杂度很高的问题,其中传统的精确算法很难找到最优解或者需要耗费很长时间。启发式算法不保证找到最优解,但通常能够在合理的时间内找到较好的解。常见的启发式算法包括贪婪算法、遗传算法、模拟退火算法等。
//Astar
public class AStar
{
public int manhattan(Node a, Node b)
{
return Math.Abs(a.x - b.x) + Math.Abs(a.y - b.y);
}
private Node[] offsets = new Node[] { new Node(1, 0), new Node(0, -1), new Node(-1, 0), new Node(0, 1) };
//传入一个二维数组表示地图,1代表障碍物,0代表可行走路径
public void AstarFind(int[][] map, Node start, Node end)
{
List<Node> sortList = new List<Node>();
//记录每个节点的父节点,用来返回路径
Dictionary<Node, Node> camFrom = new Dictionary<Node, Node>();
camFrom[start] = new Node(-1, -1);
sortList.Add(start);
// camFrom[start] = new Node(-1,-1);
bool hasRoute = false;
while (sortList.Count>0)
{
sortList.Sort((Node a,Node b)=>
{
//与迪杰斯特拉的不同是把距离起点的距离和距离终点的距离都考虑进去了
return (manhattan(start, a)+manhattan(end,a)) - (manhattan(start,b)+manhattan(end,b));
});
Node cur = sortList[0];
sortList.RemoveAt(0);
if (cur.IsSame(end))
{
hasRoute = true;
break;
}
foreach (Node offset in offsets)
{
Node newPos = cur + offset;
if (newPos.x < 0 || newPos.y < 0 || newPos.x >= map.Length || newPos.y >= map[0].Length)
{
continue;
}
if (camFrom.ContainsKey(newPos))
{
continue;
}
//障碍物检测,如果该节点是障碍物就不加入到列表中
if (map[newPos.x][newPos.y] == 1)
{
continue;
}
sortList.Add(newPos);
camFrom[newPos] = cur;
}
}
if (hasRoute)
{
//找到了路径 ,就打印出来,注意这里是从终点到起点的路径
Node pos = end;
while (camFrom.ContainsKey(pos))
{
Console.WriteLine();
pos = camFrom[pos];
}
}
}
}
五、总结
游戏中常用的寻路算法是A星算法,但是A星算法是从广度遍历优先和德吉斯特拉算法改进而来的,了解前两种算法能加深对A星的理解。由于A星加入了从起点到终点的距离计算,当目标点很多时,A*算法会带入大量计算,所以评估函数比较复杂的情况下,Dijkstra算法更好