//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// // // Purpose: // // $Header: $ // $NoKeywords: $ //=============================================================================// #ifndef UTLGRAPH_H #define UTLGRAPH_H #include "tier1/utlmap.h" #include "tier1/utlvector.h" #include //------------------------------------- //----------------------------------------------------------------------------- // A Graph class // // Nodes must have a unique Node ID. // // Edges are unidirectional, specified from the beginning node. // //----------------------------------------------------------------------------- template class CUtlGraphVisitor; template class CUtlGraph { public: typedef int I; typedef I IndexType_t; typedef T NodeID_t; typedef C CostType_t; typedef CUtlGraphVisitor Visitor_t; struct Edge_t { IndexType_t m_DestinationNode; CostType_t m_EdgeCost; Edge_t( IndexType_t i = 0 ) { m_DestinationNode = i; m_EdgeCost = 0; } bool operator==(const Edge_t &that ) const { return m_DestinationNode == that.m_DestinationNode; } static int SortFn( const Edge_t *plhs, const Edge_t *prhs ) { if ( plhs->m_EdgeCost < prhs->m_EdgeCost ) return -1; else if ( plhs->m_EdgeCost > prhs->m_EdgeCost ) return 1; else return 0; } }; typedef CUtlVector vecEdges_t; // constructor, destructor CUtlGraph( ); ~CUtlGraph(); // Add an edge bool AddEdge( T SourceNode, T DestNode, C nCost ); // Remove an edge bool RemoveEdge( T SourceNode, T DestNode ); // gets particular elements T& Element( I i ); T const &Element( I i ) const; T& operator[]( I i ); T const &operator[]( I i ) const; // Find a node I Find( T Node ) { return m_Nodes.Find( Node ); } I Find( T Node ) const { return m_Nodes.Find( Node ); } // Num elements unsigned int Count() const { return m_Nodes.Count() ; } // Max "size" of the vector I MaxElement() const { return m_Nodes.MaxElement(); } // Checks if a node is valid and in the graph bool IsValidIndex( I i ) const { return m_Nodes.IsValidIndex( i ); } // Checks if the graph as a whole is valid bool IsValid() const { return m_Nodes.IsValid(); } // Invalid index static I InvalidIndex() { return CUtlMap< NodeID_t, vecEdges_t*>::InvalidIndex(); } // Remove methods void RemoveAt( I i ); void RemoveAll(); // Makes sure we have enough memory allocated to store a requested # of elements void EnsureCapacity( int num ); // Create Path Matrix once you've added all nodes and edges void CreatePathMatrix(); // For Visitor classes vecEdges_t *GetEdges( I i ); // shortest path costs vecEdges_t *GetPathCosts( I i ); #ifdef DBGFLAG_VALIDATE void Validate( CValidator &validator, const char *pchName ); #endif // DBGFLAG_VALIDATE protected: struct Node_t { vecEdges_t *m_pvecEdges; vecEdges_t *m_pvecPaths; Node_t() { m_pvecEdges = NULL; m_pvecPaths = NULL; } }; CUtlMap< NodeID_t, Node_t > m_Nodes; }; //----------------------------------------------------------------------------- // A Graph "visitor" class // // From the specified beginning point, visits each node in an expanding radius // //----------------------------------------------------------------------------- template class CUtlGraphVisitor { public: CUtlGraphVisitor( CUtlGraph &graph ); bool Begin( T StartNode ); bool Advance(); T CurrentNode(); C AccumulatedCost(); int CurrentRadius(); private: typedef typename CUtlGraph::IndexType_t IndexType_t; typedef typename CUtlGraph::Edge_t Edge_t; typedef CUtlVector vecEdges_t; CUtlGraph &m_Graph; vecEdges_t m_vecVisitQueue; int m_iVisiting; int m_nCurrentRadius; vecEdges_t m_vecFringeQueue; CUtlVector m_vecNodesVisited; }; //----------------------------------------------------------------------------- // constructor, destructor //----------------------------------------------------------------------------- template inline CUtlGraph::CUtlGraph() { SetDefLessFunc( m_Nodes ); } template inline CUtlGraph::~CUtlGraph() { RemoveAll(); } //----------------------------------------------------------------------------- // gets particular elements //----------------------------------------------------------------------------- template inline T &CUtlGraph::Element( I i ) { return m_Nodes.Key( i ); } template inline T const &CUtlGraph::Element( I i ) const { return m_Nodes.Key( i ); } template inline T &CUtlGraph::operator[]( I i ) { return Element(i); } template inline T const &CUtlGraph::operator[]( I i ) const { return Element(i); } //----------------------------------------------------------------------------- // // various accessors // //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- // Removes all nodes from the tree //----------------------------------------------------------------------------- template void CUtlGraph::RemoveAll() { FOR_EACH_MAP_FAST( m_Nodes, iNode ) { delete m_Nodes[iNode].m_pvecEdges; delete m_Nodes[iNode].m_pvecPaths; } m_Nodes.RemoveAll(); } //----------------------------------------------------------------------------- // Makes sure we have enough memory allocated to store a requested # of elements //----------------------------------------------------------------------------- template void CUtlGraph::EnsureCapacity( int num ) { m_Nodes.EnsureCapacity(num); } //----------------------------------------------------------------------------- // Add an edge //----------------------------------------------------------------------------- template bool CUtlGraph::AddEdge( T SourceNode, T DestNode, C nCost ) { I iSrcNode = m_Nodes.Find( SourceNode ); if ( !m_Nodes.IsValidIndex( iSrcNode ) ) { Node_t Node; Node.m_pvecEdges = new vecEdges_t(); Node.m_pvecPaths = new vecEdges_t(); iSrcNode = m_Nodes.Insert( SourceNode, Node ); } I iDstNode = m_Nodes.Find( DestNode ); if ( !m_Nodes.IsValidIndex( iDstNode ) ) { Node_t Node; Node.m_pvecEdges = new vecEdges_t(); Node.m_pvecPaths = new vecEdges_t(); iDstNode = m_Nodes.Insert( DestNode, Node ); } vecEdges_t &vecEdges = *m_Nodes[iSrcNode].m_pvecEdges; #ifdef _DEBUG FOR_EACH_VEC( vecEdges, iEdge ) { if ( vecEdges[iEdge].m_DestinationNode == iDstNode ) return false; } #endif Edge_t newEdge; newEdge.m_DestinationNode = iDstNode; newEdge.m_EdgeCost = nCost; vecEdges[ vecEdges.AddToTail() ] = newEdge; return true; } //----------------------------------------------------------------------------- // Remove an edge //----------------------------------------------------------------------------- template bool CUtlGraph::RemoveEdge( T SourceNode, T DestNode ) { I iSrcNode = m_Nodes.Find( SourceNode ); if ( !m_Nodes.IsValidIndex( iSrcNode ) ) return false; I iDstNode = m_Nodes.Find( DestNode ); if ( !m_Nodes.IsValidIndex( iDstNode ) ) return false; vecEdges_t &vecEdges = *m_Nodes[iSrcNode].m_pvecEdges; FOR_EACH_VEC( vecEdges, iEdge ) { if ( vecEdges[iEdge].m_DestinationNode == iDstNode ) { // could use FastRemove, but nodes won't have that // many edges, and the elements are small, and // preserving the original ordering is nice vecEdges.Remove( iEdge ); return true; } } return false; } //----------------------------------------------------------------------------- // Get all of a Node's edges //----------------------------------------------------------------------------- template typename CUtlGraph::vecEdges_t *CUtlGraph::GetEdges( I i ) { return m_Nodes[i].m_pvecEdges; } //----------------------------------------------------------------------------- // Get all of a Node's edges //----------------------------------------------------------------------------- template typename CUtlGraph::vecEdges_t *CUtlGraph::GetPathCosts( I i ) { return m_Nodes[i].m_pvecPaths; } //----------------------------------------------------------------------------- // Data and memory validation //----------------------------------------------------------------------------- #ifdef DBGFLAG_VALIDATE template void CUtlGraph::Validate( CValidator &validator, const char *pchName ) { #ifdef _WIN32 validator.Push( typeid(*this).raw_name(), this, pchName ); #else validator.Push( typeid(*this).name(), this, pchName ); #endif ValidateObj( m_Nodes ); FOR_EACH_MAP_FAST( m_Nodes, iNode ) { validator.ClaimMemory( m_Nodes[iNode].m_pvecEdges ); ValidateObj( *m_Nodes[iNode].m_pvecEdges ); validator.ClaimMemory( m_Nodes[iNode].m_pvecPaths ); ValidateObj( *m_Nodes[iNode].m_pvecPaths ); } validator.Pop(); } #endif // DBGFLAG_VALIDATE //----------------------------------------------------------------------------- // Get all of a Node's edges //----------------------------------------------------------------------------- template void CUtlGraph::CreatePathMatrix() { int n = MaxElement(); // Notes // Because CUtlMap stores its nodes in essentially a vector, // we know that we can use its indices in our own path matrix // vectors safely (they will be numbers in the range (0,N) where // N is largest number of nodes ever present in the graph). // // This lets us very quickly access previous best-path estimates // by indexing into a node's vecPaths directly. // // When we are all done, we can then compact the vector, removing // "null" paths, and then sorting by cost. // Initialize matrix with all edges FOR_EACH_MAP_FAST( m_Nodes, iNode ) { vecEdges_t &vecEdges = *m_Nodes.Element( iNode ).m_pvecEdges; vecEdges_t &vecPaths = *m_Nodes.Element( iNode ).m_pvecPaths; vecPaths.RemoveAll(); vecPaths.AddMultipleToTail( n ); FOR_EACH_VEC( vecPaths, iPath ) { vecPaths[iPath].m_DestinationNode = InvalidIndex(); } // Path to self vecPaths[iNode].m_DestinationNode = iNode; // zero cost to self vecPaths[iNode].m_EdgeCost = 0; FOR_EACH_VEC( vecEdges, iEdge ) { // Path to a neighbor node - we know exactly what the cost is Edge_t &edge = vecEdges[iEdge]; vecPaths[ edge.m_DestinationNode ].m_DestinationNode = edge.m_DestinationNode; vecPaths[ edge.m_DestinationNode ].m_EdgeCost = edge.m_EdgeCost; } } // Floyd-Warshall // for k:= 0 to n-1 // for each (i,j) in (0..n-1) // path[i][j] = min( path[i][j], path[i][k]+path[k][j] ); for ( int k = 0; k < n; ++ k ) { if ( !m_Nodes.IsValidIndex( k ) ) continue; // All current known paths from K vecEdges_t &destMapFromK = *m_Nodes[k].m_pvecPaths; for ( int i = 0; i < n; ++i ) { if ( !m_Nodes.IsValidIndex( i ) ) continue; // All current known paths from J vecEdges_t &destMapFromI = *m_Nodes[i].m_pvecPaths; // Path from I to K? int iFromIToK = k; bool bFromIToK = destMapFromI[iFromIToK].m_DestinationNode != InvalidIndex(); CostType_t cIToK = ( bFromIToK ) ? destMapFromI[iFromIToK].m_EdgeCost : INT_MAX; for ( int j = 0; j < n; ++ j ) { if ( !m_Nodes.IsValidIndex( j ) ) continue; // Path from I to J already? int iFromIToJ = j; bool bFromIToJ = destMapFromI[iFromIToJ].m_DestinationNode != InvalidIndex(); CostType_t cIToJ = ( bFromIToJ ) ? destMapFromI[iFromIToJ].m_EdgeCost : INT_MAX; // Path from K to J? int iFromKToJ = j; bool bFromKToJ = destMapFromK[iFromKToJ].m_DestinationNode != InvalidIndex(); CostType_t cKToJ = ( bFromKToJ ) ? destMapFromK[iFromKToJ].m_EdgeCost : INT_MAX; // Is the new path valid? bool bNewPathFound = ( bFromIToK && bFromKToJ ); if ( bNewPathFound ) { if ( bFromIToJ ) { // Pick min of previous best and current path destMapFromI[iFromIToJ].m_EdgeCost = min( cIToJ, cIToK + cKToJ ); } else { // Current path is the first, hence the best so far destMapFromI[iFromIToJ].m_DestinationNode = iFromIToJ; destMapFromI[iFromIToJ].m_EdgeCost = cIToK + cKToJ; } } } } } // Clean up and sort the paths FOR_EACH_MAP_FAST( m_Nodes, iNode ) { vecEdges_t &vecPaths = *m_Nodes.Element( iNode ).m_pvecPaths; FOR_EACH_VEC( vecPaths, iPath ) { Edge_t &edge = vecPaths[iPath]; if ( edge.m_DestinationNode == InvalidIndex() ) { // No path to this destination was found. // Remove this entry from the vector. vecPaths.FastRemove( iPath ); --iPath; // adjust for the removal } } // Sort the vector by cost, given that it // is likely consumers will want to // iterate destinations in that order. vecPaths.Sort( Edge_t::SortFn ); } } //----------------------------------------------------------------------------- // Constructor //----------------------------------------------------------------------------- template CUtlGraphVisitor::CUtlGraphVisitor( CUtlGraph &graph ) : m_Graph( graph ) { m_iVisiting = 0; m_nCurrentRadius = 0; } //----------------------------------------------------------------------------- // Begin visiting the nodes in the graph. Returns false if the start node // does not exist //----------------------------------------------------------------------------- template bool CUtlGraphVisitor::Begin( T StartNode ) { m_vecVisitQueue.RemoveAll(); m_vecFringeQueue.RemoveAll(); m_vecNodesVisited.RemoveAll(); m_iVisiting = 0; m_nCurrentRadius = 0; IndexType_t iStartNode = m_Graph.Find( StartNode ); if ( !m_Graph.IsValidIndex( iStartNode ) ) return false; vecEdges_t *pvecEdges = m_Graph.GetEdges( iStartNode ); Edge_t edge; edge.m_DestinationNode = iStartNode; edge.m_EdgeCost = 0; m_vecVisitQueue[ m_vecVisitQueue.AddToTail() ] = edge; m_vecNodesVisited[ m_vecNodesVisited.AddToTail() ] = iStartNode; m_vecFringeQueue = *pvecEdges; // cells actually get marked as "visited" as soon as we put // them in the fringe queue, so we don't put them in the *next* // fringe queue (we build the fringe queue before we actually visit // the nodes in the new visit queue). FOR_EACH_VEC( m_vecFringeQueue, iFringe ) { m_vecNodesVisited[ m_vecNodesVisited.AddToTail() ] = m_vecFringeQueue[iFringe].m_DestinationNode; } return true; } //----------------------------------------------------------------------------- // Advance to the next node. Returns false when all nodes have been visited //----------------------------------------------------------------------------- template bool CUtlGraphVisitor::Advance() { m_iVisiting++; // Is the VisitQueue empty? move outward one radius if so if ( m_iVisiting >= m_vecVisitQueue.Count() ) { m_nCurrentRadius++; m_iVisiting = 0; m_vecVisitQueue = m_vecFringeQueue; m_vecFringeQueue.RemoveAll(); if ( !m_vecVisitQueue.Count() ) return false; // create new fringe queue FOR_EACH_VEC( m_vecVisitQueue, iNode ) { Edge_t &node = m_vecVisitQueue[iNode]; vecEdges_t &vecEdges = *m_Graph.GetEdges( node.m_DestinationNode ); FOR_EACH_VEC( vecEdges, iEdge ) { Edge_t &edge = vecEdges[iEdge]; if ( m_vecNodesVisited.InvalidIndex() == m_vecNodesVisited.Find( edge.m_DestinationNode ) ) { m_vecNodesVisited[ m_vecNodesVisited.AddToTail() ] = edge.m_DestinationNode; int iNewFringeNode = m_vecFringeQueue.AddToTail(); m_vecFringeQueue[ iNewFringeNode ] = edge; // Accumulate the cost to get to the current point m_vecFringeQueue[ iNewFringeNode ].m_EdgeCost += node.m_EdgeCost; } } } } return true; } //----------------------------------------------------------------------------- // Get the current node in the visit sequence //----------------------------------------------------------------------------- template T CUtlGraphVisitor::CurrentNode() { if ( m_iVisiting >= m_vecVisitQueue.Count() ) { AssertMsg( false, "Visitor invalid" ); return T(); } return m_Graph[ m_vecVisitQueue[ m_iVisiting ].m_DestinationNode ]; } //----------------------------------------------------------------------------- // Get the accumulated cost to traverse the graph to the current node //----------------------------------------------------------------------------- template C CUtlGraphVisitor::AccumulatedCost() { if ( m_iVisiting >= m_vecVisitQueue.Count() ) { AssertMsg( false, "Visitor invalid" ); return C(); } return m_vecVisitQueue[ m_iVisiting ].m_EdgeCost; } //----------------------------------------------------------------------------- // Get the current radius from the start point to this node //----------------------------------------------------------------------------- template int CUtlGraphVisitor::CurrentRadius() { if ( m_iVisiting >= m_vecVisitQueue.Count() ) { AssertMsg( false, "Visitor invalid" ); return 0; } return m_nCurrentRadius; } #endif // UTLGRAPH_H