A Recursive Algorithm to Find all Paths Between Two Given Nodes in C++ and C#

Problem Outline

This I tackled previously when working on the design and implementation of routing optimization algorithms for telecommunications networks. Given that a wide area network with nodes and interconnecting links can be modelled as a graph with vertices and edges, the problem is to find all path combinations(containing no cycles) between selected pairs of communicating end nodes. Please note that this is not a problem of just finding the shortest paths between nodes, for which Dijkstra’s algorithm can be readily employed.

An additional factor in finding all paths is that the algorithm should be able to handle both directed graphs or graphs whose edges are assumed to be bi-directional.

Downloadable implementations of this algorithm are available in C++ and C#/.NET. See download link below.

The graph searching algorithm

The National Institute of Standards and Technology (NIST) online Dictionary of Algorithms and Data Structures describes this particular problem as “all simple paths” and recommends a depth-first search to find all non-cyclical paths between arbitrary pairs of nodes.

Modelling Networks

The network connectivity is modelled using the following Network class which uses a Sedgewick’s implementation of a Graph class as the means to uniquely identify individual links, as well as add new links and check for the presence of links. The Network class can also set the bi-directional status of the links that are added and store each path found by the graph searching algorithm:

#pragma once

#include "Graph.h"
#include <vector>
#include <map>
#include <string>

typedef std::pair< int, int> Link;
typedef std::vector<int> Path;
typedef std::vector<Path> PathSet;
typedef std::vector<Path>::const_iterator Paths;

class Network
{
private:
	 
	DenseGRAPH<Edge>* graph;
    PathSet pathset;        

public:

	Network() {}
    Network( const std::vector<Link>& links, 
             const bool& bi = false );
	~Network();

    void AddPath( const Path& p );
    void ShowPaths() const;	   

    std::vector<int> GetAdjNodeIDs( const int& n ) const;     	
};

The depth-first algorithm

As can be seen from the code sample, beginning from the start node, the algorithm examines all outgoing links and progresses by expanding the first child node of the search tree that appears, searching progressively deeper until a target node is found, or until it encounters a node that has no children. The search then backtracks, returning to the most recent node it hasn’t finished exploring.

The max_hops and min_hops arguments passed to DepthFirst method can be used to filter the sets of paths found by their maximum and minimum number of hops respectively; otherwise thse parameters are set to -1, then no such restrictions are imposed on the path searches, and all possible paths are found.

// Algorithm to recursively search for all paths between
// chosen source - destination nodes
void Algorithm::DepthFirst( Network* network, 
                             Path& visited, 
                             const int& end,
                             const int& max_hops,
							 const int& min_hops)
{
    const int back = visited.back();

    std::vector< int > adjNode = network->GetAdjNodeIDs( back );

    // Examine adjacent nodes
    for ( std::vector<int>::const_iterator node_it = adjNode.begin();
          node_it != adjNode.end();
          node_it++ )
    {	
        const int node = (*node_it);

		bool startEqualTarget = ContainsNode( visited, node ) &&
			startNodeId == end &&
			node == startNodeId;

        if ( ContainsNode( visited, node ) && !startEqualTarget ) continue;

        if ( node == end )
        {
            visited.push_back( *node_it );

            // Get hop count for this path
            const int size = (int) visited.size();
			const int hops = size - 1;
              
            if ( (  max_hops < 1 || hops <= max_hops ) && hops >= min_hops )
            {   
                Path path( visited.begin(), visited.begin() + size );
                network->AddPath( path );
            }           
           		            
            visited.erase( visited.begin() + hops );

            break;
        }			
    }


    // in depth-first, recursion needs to come after visiting adjacent nodes
    for ( std::vector<int>::const_iterator node_it = adjNode.begin();
          node_it != adjNode.end();
          node_it++ )
    { 
        int node = (*node_it);

        if ( ContainsNode( visited, node ) || node == end )		
            continue;

        visited.push_back( node );

        DepthFirst( network, visited, end, max_hops, min_hops );        

        int n = (int) visited.size() - 1;				
        visited.erase( visited.begin() + n );
    }         
}

Example 1: network topology with uni-directional links

Here is the graphical representation of a 5-node directed graph problem used in the example presented here:

In the main main program loop, the network was set as having directed edges which are inserted using calls to the Network object’s AddLink method. For example a directed edge exists between nodes [1,3], but not nodes [3,1], hence the single arrow between the node [1,3] pair. Directed edges exist between nodes [1,2] and nodes [2,1], hence the bi-directional link between them.

To find all possible combinations of paths between nodes [2,5] for example, we simply set the start and target nodes and feed the GetAllPaths method with them. The -1 value passed to GetAllPaths signifies that we do not wish to filter any of the search results for maximum number of hops, but return all possible paths it finds.

Example main.cpp usage shown in the following code:

#include "Algorithm.h"
#include <iostream>
#include <vector>

const Link<int> linkset[] = { Link<int>( 1, 2 ),      
                             Link<int>( 1, 3 ),      
                             Link<int>( 1, 4 ),      
                             Link<int>( 2, 1 ),      
                             Link<int>( 2, 4 ),      
                             Link<int>( 2, 3 ),      
                             Link<int>( 3, 5 ),      
                             Link<int>( 4, 5 ),      
                             Link<int>( 5, 3 ) };  
          
const size_t size = sizeof( linkset ) / sizeof( linkset[ 0 ] ) ;  
const std::vector<Link<int>> links (linkset, linkset + size );  

int main()
{
    // Create network from interconnections given    
    Network nw( links, false );
    
    // Create the algorithm object
    Algorithm algorithm( &nw );
         
    // Get all paths between nodes 2 and 5
    algorithm.GetAllPaths( &nw, 2, 5, -1, -1 );        
    
    // Output the set of paths found
    nw.ShowPaths();        

    return 0;
}

Giving the following output for finding all paths between nodes 2 and 5:

Example 2: network topology with bi-directional links

topology2

Using a very simple tweak we can also construct network topologies with link interconnections that are are bi-directional; that is, a link between [1,2] is the same as the link between [2,1]. In other words, adding link [2,1] when link [1,2] already exists will make no difference.

Simply, set the bi-directional flag in the Network class constructor to true, and build the network in the same way, as shown in this next main.cpp example:

#include "Algorithm.h"
#include <iostream>
#include <vector>

const Link<int> linkset[] = { Link<int>( 1, 2 ),    
                             Link<int>( 2, 1 ),  // [1,2] = [2,1]  
                             Link<int>( 1, 4 ),    
                             Link<int>( 1, 7 ),    
                             Link<int>( 2, 4 ),    
                             Link<int>( 2, 3 ),    
                             Link<int>( 3, 5 ),    
                             Link<int>( 3, 8 ),    
                             Link<int>( 4, 5 ),   
                             Link<int>( 4, 9 ),    
                             Link<int>( 5, 6 ),    
                             Link<int>( 5, 7 ),  
                             Link<int>( 5, 9 ),  
                             Link<int>( 6, 8 ),    
                             Link<int>( 6, 10 ),    
                             Link<int>( 7, 8 ),  
                             Link<int>( 8, 10 ),  
                             Link<int>( 9, 10 ) };  
      
const size_t size = sizeof( linkset ) / sizeof( linkset[ 0 ] ) ;  
const std::vector<Link<int>> links (linkset, linkset + size );  

int main()
{
    // Create network from interconnections given    
    Network nw( links, true );
    
    // Create the algorithm object
    Algorithm algorithm( &nw );
         
    // Get all paths between nodes 2 and 5
    algorithm.GetAllPaths( &nw, 2, 5, -1, -1 );        
    
    // Output the set of paths found
    nw.ShowPaths();        

    return 0;
}

Giving the following output for finding all paths between nodes 2 and 5:

BidirectionalPaths

Example 3: filtering by the maximum number of hops

We may wish to restrict paths found to those whose maximum hop count is no larger than a set maximum, say 4 hops.

By using another simple tweak we can achieve this. Simply set the max_hops argument that is passed to Algorithm::GetAllPaths to some value greater than zero. The min_hops argument is kept as default -1::

algorithm.GetAllPaths( &nw, 2, 5, 4, -1 );  

Re-running the program with the same network as in example 2, gives the following paths, restricted to a maximum of 4 hops:

MaxHopPaths

Example 4: Labeling network nodes arbitrarily

A recent improvement to the software has been to allow the user to label the nodes arbitrarily as standard strings, or as integers. Either way, it is no longer a requirement for the nodes to be labelled as an ascending order of integers of say 1-5 for a five node network. You can now call them what you like.

For example, Consider the following 9-node network representing towns and cities in the UK:

UKnetwork

It is no longer necessary to label the nodes using integers, but can be done using strings to give a more meaningful description. There now exists a two-way mapping between the integer used to uniquely identify each node created and the string/integer label used to describe it.

Supposing we wish to construct the above network and find all possible paths between Bristol and Edinburgh for an undirected representation of the graph, then the usage is as follows:

#include "Algorithm.h"  
#include <iostream>
#include <vector>

const Link<std::string> linkset[] = 
                       { Link<std::string>( "Bristol", "London" ),
	                     Link<std::string>( "Bristol", "Cardiff" ),                         			
                         Link<std::string>( "Oxford", "Cardiff" ),
                         Link<std::string>( "Glasgow", "Manchester" ),
						 Link<std::string>( "Manchester", "Leeds" ),
						 Link<std::string>( "Manchester", "Cardiff" ),
						 Link<std::string>( "Glasgow", "Edinburgh" ),
                         Link<std::string>( "Edinburgh", "Newcastle" ),
                         Link<std::string>( "Leeds", "London" ),
                         Link<std::string>( "Newcastle", "Leeds" ) };
  
const size_t size = sizeof( linkset ) / sizeof( linkset[ 0 ] ) ;  
const std::vector<Link<std::string>> links (linkset, linkset + size );   
  
int main()  
{  
    // Create network from interconnections given      
    Network nw( links, true );  
      
    // Create the algorithm object  
    Algorithm algorithm( &nw );  
           
    // Get all paths between nodes Bristol and Bath  
    algorithm.GetAllPaths( &nw, "Bristol", "Edinburgh", -1, -1 ); 
	
    // Output the set of paths found  
    nw.ShowPaths();          
  
    return 0;  
} 

Giving the following output:

AllPaths

Example 5: Finding all paths between the same node

You may wish to find all paths between the same node. Take the following network with bi-directional links:

FiveNodeNetwork

You my wish to find all paths that start from the node 2 that return to node 2. Example usage is shown here:

#include "Algorithm.h"  
#include <iostream>
#include <vector>
      
const Link<int> linkset[] = { Link<int>( 1, 2 ),        
                                Link<int>( 1, 3 ),        
                                Link<int>( 1, 4 ),        
                                Link<int>( 2, 1 ),        
                                Link<int>( 2, 4 ),        
                                Link<int>( 2, 3 ),        
                                Link<int>( 3, 5 ),        
                                Link<int>( 4, 5 ),        
                                Link<int>( 5, 3 ) };    
                
const size_t size = sizeof( linkset ) / sizeof( linkset[ 0 ] ) ;    
const std::vector<Link<int>> links (linkset, linkset + size );    
      
int main()  
{  
    // Create network from interconnections given      
    Network nw( links, true );  
          
    // Create the algorithm object  
    Algorithm algorithm( &nw );  
               
    // Get all paths between nodes 2 and 5  
    algorithm.GetAllPaths( &nw, 2, 2, -1, -1 );          
          
    // Output the set of paths found  
    nw.ShowPaths();          
      
    return 0;  
}  

Giving the list of all found paths to and from node labelled 2:

SameStartEndNode1

If you would rather not have paths such as 2 – 1 – 2 then just specify a minimum hop count of 3:

algorithm.GetAllPaths( &nw, 2, 2, -1, 3 );

Which would give the following path list:

SameStartEndNode2

Download C++ (Visual Studio 2010 and Linux NetBeans) and C#/.NET implementations:

Comments and feedback welcome. Some testimonials:

“Hi Andy,
The program ran beautifully with my file input today. It traced all possible paths for 11,169 source-destination pairs in well under a minute … I still have more to do on the project, but you saved me so much time, and I deeply appreciate that. Sincerely, Julia”

Julia L. Chariker
Professor of Psychology, University of Louisville, KY

“Thank you [Andrew]. It works perfectly …”
Jeandre Prinsloo

“Hi Andrew,
Sorry for the late reply, but I had a hard week of work and I forgot to reply…
I want to thank you for the code, it really is very efficient and easy to use, I’ve solved a big problem of searching nodes!
Thank you very much, your work is great and help me a lot!”

Michele Condò


Latest Comments

  1. Joshua 16 November 2012
    • Andy 16 December 2012
    • alireza 14 January 2014
    • happyuk 7 April 2015
      • d0m4 31 May 2016
        • Andy 1 June 2016
  2. Moses 11 December 2012
    • Andy 16 December 2012
  3. nilay 30 January 2013
  4. janey 8 February 2013
    • Andy 8 February 2013
  5. Himanshu Ranjan 4 March 2013
    • happyuk 22 January 2014
  6. Thao 14 April 2013
    • happyuk 14 April 2013
      • Thao 14 April 2013
  7. Hoda 7 July 2013
    • happyuk 7 July 2013
      • Hoda 8 July 2013
        • happyuk 27 July 2013
        • hamidreza 11 October 2014
        • hamidreza 11 October 2014
  8. Adrian 16 September 2014
    • happyuk 21 September 2014
  9. shruthi 9 December 2014
    • happyuk 7 April 2015
  10. abd 24 April 2015
  11. lee 16 November 2015
  12. AJAYI O 26 June 2016
    • Andy 29 June 2016
      • Yogesh 27 October 2016
  13. Akhil Reddy 30 July 2016
  14. milad 11 February 2017
    • Andy 11 February 2017
  15. Alex A. 29 March 2017
    • Andy 29 March 2017

Leave a Reply to Hoda Cancel reply