3

I'm learning about pathfinding from a book, but I have spotted some weird statement.

For completeness, I will include most of the code, but feel free to skip to the second part (Search())

template <class graph_type >
class Graph_SearchDijkstra
{
private:

  //create typedefs for the node and edge types used by the graph
  typedef typename graph_type::EdgeType Edge;
  typedef typename graph_type::NodeType Node;

private:

  const graph_type &             m_Graph;

  //this vector contains the edges that comprise the shortest path tree -
  //a directed sub-tree of the graph that encapsulates the best paths from
  //every node on the SPT to the source node.
  std::vector<const Edge*>      m_ShortestPathTree;

  //this is indexed into by node index and holds the total cost of the best
  //path found so far to the given node. For example, m_CostToThisNode[5]
  //will hold the total cost of all the edges that comprise the best path
  //to node 5 found so far in the search (if node 5 is present and has
  //been visited of course).
  std::vector<double>           m_CostToThisNode;

  //this is an indexed (by node) vector of "parent" edges leading to nodes
  //connected to the SPT but that have not been added to the SPT yet.
  std::vector<const Edge*> m_SearchFrontier;

  int                           m_iSource;
  int                           m_iTarget;

  void Search();

public:

  Graph_SearchDijkstra(const graph_type& graph,
                       int               source,
                       int               target = -1):m_Graph(graph),
                                       m_ShortestPathTree(graph.NumNodes()),
                                       m_SearchFrontier(graph.NumNodes()),
                                       m_CostToThisNode(graph.NumNodes()),
                                       m_iSource(source),
                                       m_iTarget(target)
  {
    Search();
  }

  //returns the vector of edges defining the SPT. If a target is given
  //in the constructor, then this will be the SPT comprising all the nodes
  //examined before the target is found, else it will contain all the nodes
  //in the graph.
  std::vector<const Edge*> GetAllPaths()const;

  //returns a vector of node indexes comprising the shortest path
  //from the source to the target. It calculates the path by working
  //backward through the SPT from the target node.
  std::list<int>     GetPathToTarget()const;

  //returns the total cost to the target
  double             GetCostToTarget()const;

};

Search():

template <class graph_type>
void Graph_SearchDijkstra<graph_type>::Search()
{
  //create an indexed priority queue that sorts smallest to largest
  //(front to back). Note that the maximum number of elements the iPQ
  //may contain is NumNodes(). This is because no node can be represented
  // on the queue more than once.
  IndexedPriorityQLow<double> pq(m_CostToThisNode, m_Graph.NumNodes());

  //put the source node on the queue
  pq.insert(m_iSource);

  //while the queue is not empty
  while(!pq.empty())
  {
    //get the lowest cost node from the queue. Don't forget, the return value
    //is a *node index*, not the node itself. This node is the node not already
    //on the SPT that is the closest to the source node
    int NextClosestNode = pq.Pop();

    //move this edge from the search frontier to the shortest path tree
    m_ShortestPathTree[NextClosestNode] = m_SearchFrontier[NextClosestNode];

    //if the target has been found exit
    if (NextClosestNode == m_iTarget) return;

    //now to relax the edges. For each edge connected to the next closest node
    graph_type::ConstEdgeIterator ConstEdgeItr(m_Graph, NextClosestNode);
    for (const Edge* pE=ConstEdgeItr.begin();
        !ConstEdgeItr.end();
        pE=ConstEdgeItr.next())
    {
        //the total cost to the node this edge points to is the cost to the
        //current node plus the cost of the edge connecting them.
        double NewCost = m_CostToThisNode[NextClosestNode] + pE->Cost();

        //if this edge has never been on the frontier make a note of the cost
        //to reach the node it points to, then add the edge to the frontier
        //and the destination node to the PQ.
        if (m_SearchFrontier[pE->To()] == 0)
        {
          m_CostToThisNode[pE->To()] = NewCost;

          pq.insert(pE->To());

          m_SearchFrontier[pE->To()] = pE;
        }

        //else test to see if the cost to reach the destination node via the
        //current node is cheaper than the cheapest cost found so far. If
        //this path is cheaper we assign the new cost to the destination
        //node, update its entry in the PQ to reflect the change, and add the
        //edge to the frontier
        else if ( (NewCost < m_CostToThisNode[pE->To()]) &&
                  (m_ShortestPathTree[pE->To()] == 0) )
        {
          m_CostToThisNode[pE->To()] = NewCost;
        //because the cost is less than it was previously, the PQ must be
        //resorted to account for this.
        pq.ChangePriority(pE->To());

        m_SearchFrontier[pE->To()] = pE;
      }
    }
  }
}

What I don't get is this part:

//else test to see if the cost to reach the destination node via the
            //current node is cheaper than the cheapest cost found so far. If
            //this path is cheaper we assign the new cost to the destination
            //node, update its entry in the PQ to reflect the change, and add the
            //edge to the frontier
            else if ( (NewCost < m_CostToThisNode[pE->To()]) &&
                      (m_ShortestPathTree[pE->To()] == 0) )

if the new cost is lower than the cost already found, then why do we also test if the node has not already been added to the SPT? This seems to beat the purpose of the check?

FYI, in m_ShortestPathTree[pE->To()] == 0 the container is a vector that has a pointer to an edge (or NULL) for each index (the index represents a node)

5
  • Could be that it is trying to avoid an infinite loop in case a loop with negative value exists in the graph? That is a bad input for Dijkstra's algorithm that perhaps this code is trying to handle. Commented Mar 12, 2012 at 17:18
  • @Shahbaz can you elaborate on that further? It's a bit vague to me what you're implying. if m_ShortestPathTree[pE->To()] == 0, that means that no edge has been added yet to the SPT at index pE->To() (nullptr) Commented Mar 12, 2012 at 17:36
  • I wrote the explanation as an answer Commented Mar 12, 2012 at 17:53
  • Also, too much comment makes your code even more unreadable than no comment. Commented Mar 12, 2012 at 17:53
  • @Shahbaz I know, it is not my code :) But I thought it would be good to leave the comments there, so SO readers would know what is happening without reading all the texts leading up to it Commented Mar 12, 2012 at 17:57

1 Answer 1

3

Imagine the following graph:

S --5-- A --2-- F
\      /
 -3  -4
  \ /
   B

And you want to go from S to F. First, let me tell you the Dijkstra's algorithm assumes there are no loops in the graph with a negative weight. In my example, this loop is S -> B -> A -> S or simpler yet, S -> B -> S

If you have such a loop, you can infinitely loop in it, and your cost to F gets lower and lower. That is why this is not acceptable by Dijkstra's algorithm.

Now, how do you check that? It is as the code you posted does. Every time you want to update the weight of a node, besides checking whether it gets smaller weight, you check if it's not in the accepted list. If it is, then you must have had a negative weight loop. Otherwise, how can you end up going forward and reaching an already accepted node with smaller weight?

Let's follow the algorithm on the example graph (nodes in [] are accepted):

Without the if in question:

Starting Weights: S(0), A(inf), B(inf), F(inf)
- Accept S
New weights: [S(0)], A(5), B(-3), F(inf)
- Accept B
New weights: [S(-3)], A(-7), [B(-3)], F(inf)
- Accept A
New weights: [S(-3)], [A(-7)], [B(-11)], F(-5)
- Accept B again
New weights: [S(-14)], [A(-18)], [B(-11)], F(-5)
- Accept A again
... infinite loop

With the if in question:

Starting Weights: S(0), A(inf), B(inf), F(inf)
- Accept S
New weights: [S(0)], A(5), B(-3), F(inf)
- Accept B (doesn't change S)
New weights: [S(0)], A(-7), [B(-3)], F(inf)
- Accept A (doesn't change S or B
New weights: [S(0)], [A(-7)], [B(-3)], F(-5)
- Accept F
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.