游戏中的寻路算法--Dijkstra算法和AStar(A*)算法

2023-05-16

前言

如今游戏中最最常用的两种寻路算法为Dijkstra算法A*算法,虽然现代引擎中的Al寻路算法看似很复杂,其实大部分是Dijkstra算法或者A*算法的变种

导航网格(图数据)

无论是2D游戏的导航网格或者3D游戏导航网格, 本质上就是一个图,里面包含了各种点数据和边数据。

图(Graph)由点(Node)和边(Edge)构成,这里以二维为示例

class FVector2D
{
public:
	float x;
	float y;
}
//2D位置
class FVector2D
{
public:
	float x;
	float y;
}

//点
class FGraphNode
{
private:
	int index;
	FVector2D pos;
}

​//边
class FGraphEdge
{
private:
	int fromIndex;
	int toIndex;
	float distance;
}

//图
class FGraph
{
public:
	vector<FGraphNode> nodes;
	vector<list<FGraphEdge>> edges;
}

寻路算法(最短路径)

所谓 “寻路” 就是从图的一个点A出发,经过一系列边,到达一个点B,一般而言都是要求经过最短的路径。

深度优先搜索(Depth First Search)

深度优先搜索(DFS)就是在优先在树节点的深度搜索,直到到达最深度在回朔上一个较浅的节点,大致行为如下:

深度优先搜索(DFS)本质上是一种暴力搜索。如果采用深度优先搜索(DFS)在图中寻找从一个点寻找到另外一个点,寻找的路径并不是最短路径,如下所示:

广度优先搜索(Breaadth First Search)

广度优先搜索(BFS)就是在优先搜索同一个层级的节点,然后在考虑下一个层级的节点。

广度优先搜索(BFS)本质上也是一种暴力搜索,寻找的路径也和深度优先搜索(DFS)一样不是最短路径.

Dijkstra

Dijkstra质上是一种贪心算法,从起始点出发,总是优先选择沿着起点到目前点为最短的那个路径往下走.

这里引入一个概念:最短路径树(Short Path Tree, SRT),就是代表了从SPT这颗树上的任意一点达到起点都是最短路径。

这里引入一个搜索边的概念(Search Frontier)代表了图中的某条边是否被搜索过.

下面展示下,搜索点5到点3的最短路径的过程:

从上面图中可以看出Dijkstra算法总是非常贪心的, 如果起点到目前搜索点是最短的路径, 继续往下寻找,如果不是最短路径,回到最短路径的那个点继续往下找。这个过程模拟就是Queue队列,只不过这个队列的元素是点,而决定点优先级的是起点到对应点的距离,原点到哪个点的路径越短,就是在优先级最高的,所以我们采用数据结构优先队列(priority_queue)或者索引优先队列(index_priority_queue)。

实现代码:

struct CostNode
{
public:
	int nodeIndex;
	float cost;

public:
	CostNode(int newNodeIndex = -1, float newCost = 0.0f):
		nodeIndex(newNodeIndex),
		cost(newCost)
	{
	}
};

struct OperaterCostNode
{
	bool operator() (CostNode a, CostNode b)
	{
		return a.cost > b.cost;
	}
};


void GetPathInDijkstra(int souceIndex, int destIndex, vector<FGraphNode>& pathPoints)
	{
		pathPoints.empty();

		//SRT最短路径树(edge toindex = index)
		vector<const FGraphEdge*> shortestPathTree(nodes.size(), nullptr);

		//目前到N点最小花费
		vector<float> costToNodes(nodes.size(), 0.0);

		//目前到达N点最小花费的边(edge toindex = index)
		vector<const FGraphEdge*> searchFrontier(nodes.size(), nullptr);

		priority_queue<CostNode, vector<CostNode>, OperaterCostNode> pq;

		pq.push(CostNode(souceIndex));

		while (!pq.empty())
		{
			int nextNodeIndex = pq.top().nodeIndex;
			pq.pop();
			shortestPathTree[nextNodeIndex] = searchFrontier[nextNodeIndex];

			if(destIndex == nextNodeIndex)
				break;
			
			const list<FGraphEdge>& EdgeList = edges[nextNodeIndex];
			for (auto& edge : EdgeList)
			{
				float newCost = costToNodes[nextNodeIndex] + edge.GetDistance();
				int toNodeIndex = edge.GetToIndex();

				if (nullptr == searchFrontier[toNodeIndex])
				{
					costToNodes[toNodeIndex] = newCost;
					searchFrontier[toNodeIndex] = &edge;
					pq.push(CostNode(toNodeIndex, newCost));
				}
				else if(nullptr == shortestPathTree[toNodeIndex] &&
					newCost < costToNodes[toNodeIndex])
				{
					costToNodes[toNodeIndex] = newCost;
					searchFrontier[toNodeIndex] = &edge;
					pq.push(CostNode(toNodeIndex, newCost));
				}
			}
		}

		int findNodeIndex = destIndex;
		pathPoints.push_back(nodes[destIndex]);
		while (findNodeIndex != souceIndex)
		{
			findNodeIndex = shortestPathTree[findNodeIndex]->GetFromIndex();
			pathPoints.push_back(nodes[findNodeIndex]);
		}

		std::reverse(pathPoints.begin(), pathPoints.end());
	}

demo测试:: 构建 5 * 5 的网格数据,从(0, 0)寻找到(3, 4),如下所示:

int main()
{
	FGraph graph;
	BuildTestGraph1(graph);
	vector<FGraphNode> nodes;
	int sourceIndex = graph.GetNodeIndex(FVector2D(0.0, 0.0));
	int destIndex = graph.GetNodeIndex(FVector2D(3.0, 4.0));
	if (sourceIndex == INDEX_INVALID || destIndex == INDEX_INVALID)
	{
		printf("error index\n");
		return 0;
	}

	//graph.GetPathInAStar(sourceIndex, destIndex, nodes);
	graph.GetPathInDijkstra(sourceIndex, destIndex, nodes);
	for (auto& node : nodes)
	{
		node.Print();
		printf("\n");
	}

	system("pause");
	return 0;
}

Dijkstra算法虽然能找出最短路径,由于盲目的贪心,只以起点到目前点最短路径为最优先级来进行搜索,导致寻找了很多无用点,如下所示

A*算法

上面说到Dijkstra算法因为以起点到目前点最短路径为最优先级来进行搜索导致了搜索了很多无用点所以人们改进了Dijkstra算法的“贪心策略”,由 “起点到目前点最短路径为最优先级”  转为  “(起点到目前点的最短距离 + 目前点到目标点的距离)最短距离为最优先级”,即

Dijkstra算法贪心策略 = Min(起点到目前点路径)

A*算法贪心策略  = Min(Min(起点到目前点路径) + 目前点到目标点的距离), 这里得注意:目前点到目标点的距离  不一定是欧式距离,可能是绝对距离,你得根据自己的算法需求来定等等,我们称其为估值函数(estimate function)

这样A*算法就不用寻找很多无用点,快速的得到最短路径

代码实现:(下面的距离计算用一个自定义的函数来实现,根据需求选用欧式距离,绝对距离还是其它)

    #define AStarEstimateFunc std::function<float(const FGraphNode&, const FGraphNode&)>


	void GetPathInAStar(int souceIndex, int destIndex, vector<FGraphNode>& pathPoints, AStarEstimateFunc estimateFunc)
	{
		pathPoints.empty();

		//SRT最短路径树(edge toindex = index)
		vector<const FGraphEdge*> shortestPathTree(nodes.size(), nullptr);

		//目前到N点最小花费
		vector<float> costToNodes(nodes.size(), 0.0);

		//预估花费 = 到N点最小花费 + N点到终点最小距离
		vector<float> costEstimateNodes(nodes.size(), 0.0);

		//目前到达N点最小花费的边(edge toindex = index)
		vector<const FGraphEdge*> searchFrontier(nodes.size(), nullptr);

		priority_queue<CostNode, vector<CostNode>, OperaterCostNode> pq;

		pq.push(CostNode(souceIndex));

		while (!pq.empty())
		{
			int nextNodeIndex = pq.top().nodeIndex;
			pq.pop();
			shortestPathTree[nextNodeIndex] = searchFrontier[nextNodeIndex];

			if (destIndex == nextNodeIndex)
				break;

			const list<FGraphEdge>& EdgeList = edges[nextNodeIndex];
			for (auto& edge : EdgeList)
			{
				int toNodeIndex = edge.GetToIndex();
				float hCost = estimateFunc(nodes[destIndex], nodes[toNodeIndex]);
				float newCost = costToNodes[nextNodeIndex] + edge.GetDistance();
				float costEstimate = hCost + newCost;

				if (nullptr == searchFrontier[toNodeIndex])
				{
					costToNodes[toNodeIndex] = newCost;
					costEstimateNodes[toNodeIndex] = costEstimate;
					searchFrontier[toNodeIndex] = &edge;
					pq.push(CostNode(toNodeIndex, costEstimate));
				}
				else if (nullptr == shortestPathTree[toNodeIndex] &&
					newCost < costToNodes[toNodeIndex])
				{
					costToNodes[toNodeIndex] = newCost;
					costEstimateNodes[toNodeIndex] = costEstimate;
					searchFrontier[toNodeIndex] = &edge;
					pq.push(CostNode(toNodeIndex, costEstimate));
				}
			}
		}

		int findNodeIndex = destIndex;
		pathPoints.push_back(nodes[destIndex]);
		while (findNodeIndex != souceIndex)
		{
			findNodeIndex = shortestPathTree[findNodeIndex]->GetFromIndex();
			pathPoints.push_back(nodes[findNodeIndex]);
		}

		std::reverse(pathPoints.begin(), pathPoints.end());
	}

	auto astarEstimateFunc = [](const FGraphNode& nodeA, const FGraphNode& nodeB)
	{
		return nodeA.GetDistance(nodeB);
	};

	graph.GetPathInAStar(sourceIndex, destIndex, nodes, astarEstimateFunc);

Dijkstra算法和AStar(A*)算法使用对比

从上面可以知道在图数据中 寻找一个点到另外一个目标点的最短路径, A*算法Dijkstra算法都能计算出最短路径,但是A*算法Dijkstra算法快(快多少取决于图中点和线的分布).

但这是否意味着A*算法无敌了呢?答案是否定的,因为A*算法在寻找指定的一个点到另外一个指定点路径是很快,但是碰上模糊条件搜索(存在n多个搜索目标)的情况,A*算法就有点懵逼了,比如:

 一个人在城市市中心,这个城市有几十万甚至更多出口(由一条包围线包围着,可以分解为几十万甚至几百万个点),这时候用A*一个个遍历几百万个点真的比Dijkstra算法? 这种情况下Dijkstra算法真有可能比A*算法快。当然这两种算法并非是生死仇敌,某些情况搭配混用,效果更佳。

所以:

(1)明确一个点到另外一个点的最短路径:A*算法

(2)模糊条件搜索(存在N个对象)优先考虑Dijkstra算法 

源码链接

DijkstraAndAstar.rar-其他文档类资源-CSDN下载

资料参考

(1)《游戏人工智能编程案例精粹》第五章 图的秘密生命

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

游戏中的寻路算法--Dijkstra算法和AStar(A*)算法 的相关文章

  • 最短路径算法之AStar算法(三) 《A* Pathfinding for Beginners》一文中的两个问题

    现在 xff0c 看看网上流传的很广的一篇文章 A Pathfinding for Beginners xff0c 经典的A STar算法的入门文章 xff0c 也是我前面推荐的阅读文章 个人认为 xff0c 这篇入门文章的算法不能找出最短
  • 游戏中的寻路算法--Dijkstra算法和AStar(A*)算法

    前言 如今游戏中最最常用的两种寻路算法为Dijkstra算法和A 算法 xff0c 虽然现代引擎中的Al寻路算法看似很复杂 其实大部分是Dijkstra算法或者A 算法的变种 导航网格 图数据 无论是2D游戏的导航网格或者3D游戏导航网格
  • 2、无人驾驶--路径规划算法:Dijkstra

    目录 2 Dijkstra2 1 算法简介2 2 算法思路具体流程 xff1a 2 3 算法具体实现2 3 1 程序详解 2 Dijkstra 声明 xff1a 本文是学习古月居 基于栅格地图的机器人路径规划算法指南 黎万洪 后写的笔记 x
  • 迪杰斯特拉(Dijkstra)算法

    一 算法介绍 迪杰斯特拉算法是由荷兰计算机科学家在1956年发现的算法 此算法使用类似广度优先搜索的方法解决了带权图的单源最短路径问题 它是一个贪心算法 二 核心思想 1 选定一个点 这个点满足两个条件 1 未被选过 2 距离最短 2 对于
  • 天梯题集——紧急救援(Dijkstra+倒序打印分析)

    Dijkstra算法 用于求单源到其他点的最短路径 紧急救援 该题与 Dijkstra模板题 的不同之处在于该题需要记录更多信息 主要思路从局部最优到整体最优 类似dp的思想 include
  • 寻找 A* 算法的启发式有哪些好方法?

    您有一张方形图块地图 您可以在其中向 8 个方向中的任意方向移动 鉴于您有名为的函数cost tile1 tile2 它告诉您从一个相邻图块移动到另一个图块的成本 您如何找到既可接受又一致的启发式函数 h y goal 给定此设置 寻找启发
  • 如何使用A*算法找到最佳的3条路线

    在 A 中 通常您得到的结果只是一条路径 对于给定的出发地和目的地 是否有可能根据 A 有 3 条推荐路径 所以第二个返回的是第二最佳路径 第三个返回的是第三最佳路径 我正在考虑也许以某种方式修改启发式以反映第二和第三最佳路径 你们觉得怎么
  • 如果顶点属性是指针,如何使用 boost::graph dijkstra 算法?

    我使用 boost graph 来管理图表 我需要制作一个 maxmin 树 现在我尝试使用 boost dijkstra 算法 但我使用指向我的类的指针作为顶点属性 而不是使用typedef property
  • Astar 可以多次访问节点吗?

    我一直在阅读维基百科的 Astararticle 在他们的实现中 他们检查每个节点是否在closed设置 如果是这样 他们会跳过它 是不是有可能 如果启发式是可以接受的 但是NOT一致 我们可能需要重新访问一个节点两次 或更多次 才能改进它
  • Java:使用 Fibonacci 堆实现 Dijkstra 算法

    新来的 但已经作为客人潜伏了一段时间了 好的 所以我一直在尝试使用 Fibonacci 堆 在 Java 中 执行 Dijkstra 的最短路径算法 经过一番搜索 我偶然发现了两个代表斐波那契堆的现成实现 第一个实现做得相当漂亮 可以找到h
  • 一种最小遍历节点数的最短路径算法

    我正在寻找 Dijkstra 算法的实现 它也考虑了遍历的节点数量 我的意思是 典型的 Dijkstra 算法会考虑连接节点的边的权重 同时计算从节点 A 到节点 B 的最短路径 我想在其中插入另一个参数 我希望算法也对遍历的节点数量给予一
  • 网格中两点之间的最短路径。有一个捕获

    我遇到这个问题 我必须通过向右或向下移动来找到 NxM 网格中从 A 点 总是左上角 到 B 点 总是右下角 的最短路径 听起来很容易 是吗 问题是 我只能移动我现在坐在的图块上显示的数字 让我举例说明 2 5 1 2 9 2 5 3 3
  • 明星算法:距离启发式

    我正在使用 A 星算法 如此处所示 取自http code activestate com recipes 578919 python a pathfinding with binary heap http code activestate
  • 如果广度优先搜索 (BFS) 可以更快地完成同样的事情,为什么还要使用 Dijkstra 算法呢?

    两者都可用于从单一源查找最短路径 BFS运行在O E V 而 Dijkstra 运行O V E log V 另外 我见过 Dijkstra 在路由协议中的使用很像 因此 如果 BFS 可以更快地完成同样的事情 为什么还要使用 Dijkstr
  • 具有负权重的 Dijkstra 算法

    我们可以使用具有负权重的 Dijkstra 算法吗 STOP 在你认为 哈哈 你可以在两点之间无休止地跳跃并获得一条无限便宜的路径 之前 我更倾向于考虑单向路径 其应用是具有点的山区地形 显然 从高到低并不需要能量 事实上 它会产生能量 因
  • 具有固定边数的最短路径

    在高效的时间内找到通过图形的最短路径 并附加该路径必须完全包含的约束n nodes 我们有一个有向加权图 它可能包含也可能不包含循环 我们可以使用 Dijkstra 算法轻松找到最短路径 但 Dijkstra 算法不保证边的数量 我们能想到
  • 实施 Dijkstra 算法

    我的任务是 大学课程 实施某种形式的寻路 现在 在规范中 我可以实现强力 因为要搜索的节点数量有限制 开始 中间两个 结束 但我想重新使用此代码并来实现迪杰斯特拉算法 http en wikipedia org wiki Dijkstra
  • 使用字典中的特定键构建列表(python)?

    我正在用 Python 实现 Dijkstra 搜索算法 在搜索结束时 我使用前驱图重建最短路径 从目标节点的前驱开始 例如 path path append destination previous predecessor map des
  • 为什么A*的复杂度在内存中是指数级的?

    维基百科关于 A 复杂度的说法如下 链接在这里 http en wikipedia org wiki A search algorithm 比当时更成问题 复杂度是A 的内存使用量 在 最坏的情况 也必须记住 指数数量的节点 我不认为这是正
  • 数组中超过 640 000 个元素 - 内存问题 [Dijkstra]

    我有一个脚本将 803 803 644809 每个图表内有 1 000 000 个值 使用 500 500 一切正常 但现在它崩溃了 它尝试分配超过 64MB 的内存 我没有 解决办法是什么 以某种方式 分裂 它还是 result mysq

随机推荐