zoukankan      html  css  js  c++  java
  • 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


      本节实践还有一种求最短路径算法: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是 Shortest Path Faster Algorithm 的缩写,中文直译过来就是:最短路径高速算法。

    作用在稀疏图上通常比Dijkstra更快。是一种高效的求最短路径算法。

    和Dijkstra一样,也是求某个顶点到其它全部顶点的最短路径的一种算法。用我自己理解的话来说,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 "GraphGraphPathfinding.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.本文project源码下载


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

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








    版权声明:本文博客原创文章,博客,未经同意,不得转载。

  • 相关阅读:
    前端性能优化 —— reflow(回流)和repaint(重绘)
    前端性能优化 —— 文档在浏览器中的加载和渲染
    前端性能优化 —— 起步篇(一)
    zepto源码研究
    zepto源码研究
    zepto源码研究
    zepto源码研究
    zepto源码研究
    zepto源码研究
    zepto源码研究
  • 原文地址:https://www.cnblogs.com/hrhguanli/p/4633748.html
Copyright © 2011-2022 走看看