Cocos2d-x 地图行走的实现2:SPFA算法

  上一节《Cocos2d-x
地图行走的实现1:图论与Dijkstra算法

  http://blog.csdn.net/stevenkylelee/article/details/38408253

  本节实践另一种求最短路径算法:SPFA

1.寻路算法实现上的优化

  上一节我们实现的Dijkstra用了一个哈希表来保存搜索到的路径树。如果能用直接的访问的方式,就不要用哈希表,因为直接访问的方式会比哈希表更快。我们修改一下图顶点的数据结构。如下:

/*
	图顶点
*/
class Vertex
{
	friend class Graph ;

public:

	Vertex( const string& Name )
	{
		m_strId = Name ;

		m_pGraph = 0 ;
	}

	~Vertex( ) { };

public:

	// 附加数据
	unordered_map< string , void*> UserData ;

public : 

	const unordered_map< string , Edge* >& GetEdgesOut( ) const { return m_EdgesOut ; }

	const unordered_map< string , Edge* >& GetEdgesIn( ) const { return m_EdgesIn ; }

	const string& GetId( ) const { return m_strId ; }

	const string& GetText( ) const { return m_Text ; }
	void SetText( const string& Text ) { m_Text = Text ; }

	Graph * GetGraph( ) { return m_pGraph ; }

protected: 

	// 出边集合
	unordered_map< string , Edge* > m_EdgesOut ; 

	// 入边集合
	unordered_map< string , Edge* > m_EdgesIn ;

	// 节点表示的字符串
	string m_Text ; 

	// 节点的ID
	string m_strId ; 

	// 所属的图
	Graph * m_pGraph ; 

public : 

	// 寻路算法需要的数据
	struct Pathfinding
	{
		// 路径代价估计
		int Cost ; 

		// 标识符
		int Flag ;

		// 顶点的前驱顶点。
		Vertex * pParent ; 

		Pathfinding( )
		{
			Cost = 0 ;
			Flag = 0 ;
			pParent = 0 ;
		}
	}
	PathfindingData ;

};

  修改的地方是:把int m_Cost成员变量删掉,末尾增加了一个Pathfinding类型的字段。这个结构体负责保存寻路算法所需要的一些变量。虽然我们可以像这样unordered_map< Vertex* , int > , unordered_map< Vertex* , Vertex*> 动态地为顶点增加一些“临时属性”,但这种做法运行起来比较慢。Pathfinding的pParent字段表示寻路算法执行完后,该顶点到起始顶点的一条”反向路径“,一直查找pParent直到为空,可追溯到起始顶点,这就是一条路径。起始顶点的Pathfinding::pParent肯定为空,因为它就是路径树的根节点。如果非起始顶点的Pathfinding::pParent为空,表示起始顶点到该顶点没有通路。

  上一节我们实现的Dijkstra是按照Dijkstra算法的思想用最简单的方法直接做的。这样做是为了更简单地表达出算法的思想。Dijkstra的算法优化就是在于怎样做”选出拥有最小路径估计的顶点“。关于这个问题的优化,可以搜索下 优先级队列二项堆斐波那契堆

  std有一个叫 priority_queue 的容器,就是优先级队列。是用priority_queue还是自己写一个优先级队列来优化,你们自己考虑吧。俗话说,师傅领进门,修行靠个人。(什么堆来堆去的数据结构,哥早已忘得一干二净了 

2.SPFA算法介绍

  用我自己理解的话来说,SPFA是这样:

  2.1.SPFA算法需要什么

  SPFA需要用到一个先进先出的队列Q。

  SPFA需要对图中的所有顶点做一个标示,标示其是否在队列Q中。可以用哈希表做映射,也可以为顶点增加一个字段。后者的实现效率更高。

  2.2.SPFA是怎样执行的

  2.2.1 SPFA的初始化

  SPFA的初始化和Dijkstra类似。

  先把所有顶点的路径估计值初始化为代价最大值。比如:0x0FFFFFFF。

  所有顶点都标记为不在队列中。

  起始顶点放入队列Q中。

  起始顶点标记在队列中。

  起始顶点的最短路径估计值置为最小值,比如0。

  然后下面是一个循环

  2.2.2 SPFA循环

  循环结束的条件是队列Q为空。第一次进入循环的时候,只有起始顶点一个元素。

  每次循环,弹出队列头部的一个顶点。

  对这个顶点的所有出边进行松弛。如果松弛成功,就是出边终点上对应的那个顶点的路径代价值被改变了,且这个被松弛的顶点不在队列Q中,就把这个被松弛的顶点入队Q。注意,这里顶点入队的条件有2:1.松弛成功。2.且不在队列Q中。

  当队列Q没有了元素。算法结束。

  2.3.SPFA伪代码

void Spfa( 图G,起始顶点VStart )
{
	foreach( 对图G中的所有顶点进行遍历,迭代对象v表示遍历到的每一个顶点对象)
	{
		设置顶点v的路径代价估计值为代价最大值,例如:0x0FFFFFFF
		设置标示顶点v不在队列中
		顶点v的前驱顶点都为空
	}
	起始顶点VStart路径代价估计值为最小值0
	起始顶点VStart入队Q

	for( 如果队列Q不为空)
	{
		队列Q弹出一个队头元素v
		记录v已经不在队列Q中了
		for( 遍历从队列Q中弹出的队头顶点v的每一个出边)
		{
			u = 边终点上的顶点
			Relax( v , u,边上的权值)
			if( Relax松弛成功了 && 顶点u不在队列Q中)
			{
				u入队Q
				记录u在队列中了
			}
		}
	}
}

  

  从以上伪代码来看,SPFA和BFS很像:都用了队列,都是从队列弹出一个元素进行扩展子节点。SPFA不同于BFS的扩展:SPFA的扩展子节点是有条件的,根据松弛的结果。

3.SPFA算法的实现

  Dijkstra不需要关心松弛的结果,所以之前的Dijkstra的Relax函数返回值为void。而SPFA是需要知道松弛是否成功的,它根据此结果决定松弛的顶点是否需要入队。所以,我们实现的SPFA的Relax函数需要返回bool。

  以下,是我的SPFA实现代码

  Spfa.h

#pragma once

#include "Graph\GraphPathfinding.h"

class Spfa :
	public GraphPathfinding
{
public:
	Spfa( );
	~Spfa( );

public : 

	virtual void Execute( const Graph& Graph , const string& VetexId ) ; 

private:

	inline bool Relax( Vertex* pStartVertex , Vertex* pEndVertex , int Weight ) ;

};

  Spfa.cpp

#include "Spfa.h"
#include <queue>
using namespace std ;

Spfa::Spfa( )
{
}

Spfa::~Spfa( )
{
}

void Spfa::Execute( const Graph& Graph , const string& VetexId )
{
	// 取得图的顶点集合
	const auto& Vertexes = Graph.GetVertexes( ) ;
	//  取得起始顶点对象
	Vertex *pVStart = Vertexes.find( VetexId )->second   ;

	// Spfa算法需要一个队列保存顶点
	queue< Vertex* > Q ; 

	// 初始化
	for ( auto& it : Vertexes )
	{
		Vertex *pV = it.second ; 

		pV->PathfindingData.Cost = 0x0FFFFFFF ;
		//IsInQueue[ pV ] = false ;
		pV->PathfindingData.Flag = false ;
		pV->PathfindingData.pParent = 0 ; // 顶点的父路径都设置为空
	}
	pVStart->PathfindingData.Cost = 0 ;			// 起始顶点的路径代价为0
	pVStart->PathfindingData.Flag = true ;		// 起始顶点在队列中
	//m_Ret.PathTree[ pVStart ] = 0 ;				//  起始顶点的父路径为空
	Q.push( pVStart ) ;									// 起始顶点先入队

	// spfa算法
	for ( ; Q.size( ) ;  )
	{
		auto pStartVertex = Q.front( ) ; Q.pop( ) ;	// 队列弹出一个顶点v
		pStartVertex->PathfindingData.Flag = false ;

		// 松弛v的所有出边
		const auto& Eo = pStartVertex->GetEdgesOut( ) ;
		for ( auto& it : Eo )
		{
			auto pEdge = it.second ;
			auto pEndVertex = pEdge->GetEndVertex( ) ;
			bool bRelaxRet = Relax( pStartVertex , pEndVertex , pEdge->GetWeight( ) ) ;
			if ( bRelaxRet )
			{
				// 如果对于出边松弛成功,且出边对应的终点顶点不在队列中的话,就插入队尾
				if ( pEndVertex->PathfindingData.Flag == false )
				{
					Q.push( pEndVertex ) ;
					pEndVertex->PathfindingData.Flag = false ;
				}

			}

		}
		// end for

	}
	// end for

}

bool Spfa::Relax( Vertex* pStartVertex , Vertex* pEndVertex , int Weight )
{
	int n = pStartVertex->PathfindingData.Cost + Weight ;
	if ( n < pEndVertex->PathfindingData.Cost )
	{
		// 更新路径代价
		pEndVertex->PathfindingData.Cost = n ;
		// 更新路径
		//m_Ret.PathTree[ pEndVertex ] = pStartVertex ;
		pEndVertex->PathfindingData.pParent = pStartVertex ;

		return true ;
	}

	return false ;
}

4.Dijkstra与SPFA在实际上的比较

  下图是构造了一个比较大的图,对于一次寻路同时用了Dijkstra和SPFA。图的左下角显示2个算法所用的时间。

  对于上图来说,SPFA的执行要快于Dijkstra。当然,是和没有用任何优化的Dijkstra比较的结果。一般来说Dijkstra运行比较稳定,优化后也可以得到不错的性能。而SPFA的优势在于稀疏图,也就是边数较少的图。原因很明显,SPFA不需要像Dijkstra那样去选最小路径代价的顶点出来松弛,它只是从队列里面弹出一个即可。如果边数越少,入队的顶点也就越少。

5.本文工程源代码下载

  上一节的工程代码不小心弄成了8分。这次设置为0分啦。

  下载地址:http://download.csdn.net/detail/stevenkylelee/7731827

Cocos2d-x 地图行走的实现2:SPFA算法,布布扣,bubuko.com

时间: 2024-08-26 19:04:59

Cocos2d-x 地图行走的实现2:SPFA算法的相关文章

Cocos2d-x 地图行走的实现3:A*算法

上一节<Cocos2d-x 地图行走的实现2:SPFA算法>: http://blog.csdn.net/stevenkylelee/article/details/38440663 1.修改一下Dijkstra的实现 回顾一下之前Dijkstra的实现.Dijkstra需要从一个表Q中选出一个路径代价最小的顶点.之前我们的实现是,一开始就把所有的顶点都放入这个表Q中.仔细想下就会发现,那些被初始化为路径代价最大值0x0FFFFFFF的顶点是不可能会被选中的,对于这些顶点不需要遍历.从表中取出

Cocos2d-x 地图行走的实现:图论与Dijkstra

本文乃Siliphen原创,转载请注明出处:http://blog.csdn.net/stevenkylelee 本文的实现基于Cocos2d-x 3.2. 本文,我们最终实现的地图行走效果如下2图: 下面是2张屏幕录制的gif动画图,有点大,看不到的话,耐心等待一下,或者刷新页面试试. 地图行走用于现实的地图上. 1.什么是地图行走 很多游戏会有一个"世界"的概念.玩家在这个世界中行走,到达不同的地方去执行的任务,打怪或者是触发剧情等.下图是<锁链战记>的世界地图的截图.

Cocos2d-x 2地图步行实现:SPFA算法

本文乃Siliphen原创,转载请注明出处:http://blog.csdn.net/stevenkylelee 上一节<Cocos2d-x 地图行走的实现1:图论与Dijkstra算法> http://blog.csdn.net/stevenkylelee/article/details/38408253 下一节<Cocos2d-x 地图行走的实现3:A*算法> http://blog.csdn.net/stevenkylelee/article/details/38456419

(最短路径算法整理)dijkstra、floyd、bellman-ford、spfa算法模板的整理与介绍

这一篇博客以一些OJ上的题目为载体.整理一下最短路径算法.会陆续的更新... 一.多源最短路算法--floyd算法 floyd算法主要用于求随意两点间的最短路径.也成最短最短路径问题. 核心代码: /** *floyd算法 */ void floyd() { int i, j, k; for (k = 1; k <= n; ++k) {//遍历全部的中间点 for (i = 1; i <= n; ++i) {//遍历全部的起点 for (j = 1; j <= n; ++j) {//遍历

hihoCoder - 1093 - 最短路径&#183;三:SPFA算法 (SPFA)

#1093 : 最短路径·三:SPFA算法 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 万圣节的晚上,小Hi和小Ho在吃过晚饭之后,来到了一个巨大的鬼屋! 鬼屋中一共有N个地点,分别编号为1..N,这N个地点之间互相有一些道路连通,两个地点之间可能有多条道路连通,但是并不存在一条两端都是同一个地点的道路. 不过这个鬼屋虽然很大,但是其中的道路并不算多,所以小Hi还是希望能够知道从入口到出口的最短距离是多少? 提示:Super Programming Festiv

蓝桥杯-最短路 (SPFA算法学习)

SPFA算法主要用来解决存在负边权的单源最短路情况(但不能有负环!!!)一个简单的方法判断是否有没有负环可以通过判断是否有一个节点是否频繁进出队列. 以下内容转自https://blog.csdn.net/xunalove/article/details/70045815 求单源最短路的SPFA算法的全称是:Shortest Path Faster Algorithm. SPFA算法是西南交通大学段凡丁于1994年发表的. 从名字我们就可以看出,这种算法在效率上一定有过人之处. 很多时候,给定的

POJ 3259 Wormholes SPFA算法题解

本题其实也可以使用SPFA算法来求解的,不过就一个关键点,就是当某个顶点入列的次数超过所有顶点的总数的时候,就可以判断是有负环出现了. SPFA原来也是可以处理负环的. 不过SPFA这种处理负环的方法自然比一般的Bellman Ford算法要慢点了. #include <stdio.h> #include <string.h> #include <limits.h> const int MAX_N = 501; const int MAX_M = 2501; const

POJ 1847 Tram 【最短路,spfa算法,题意理解是关键呀!!】

Tram Time Limit: 1000MS   Memory Limit: 30000K Total Submissions: 13468   Accepted: 4954 Description Tram network in Zagreb consists of a number of intersections and rails connecting some of them. In every intersection there is a switch pointing to t

[知识点]SPFA算法

// 此博文为迁移而来,写于2015年4月9日,不代表本人现在的观点与看法.原始地址:http://blog.sina.com.cn/s/blog_6022c4720102vx93.html 1.前言 最短路算法有很多种,类似于Floyd和Dijkstra都是很早之前就学了的.其实每种最短路算法有各自的优势.Floyd适合于跑完全图,但是效率太慢(O(n3)).Dijkstra适合于跑没有负权的图,效率为O(n2).而今天介绍的SPFA算法,是有一位中国人——段凡丁所提出来的(其实我很想吐个槽.