The path from the root node to all leaves in the tree (or directed acyclic graph)

Question : Suppose there is a tree now. Note that the tree here is not necessarily a binary tree (that is, it can be a polytree). We want to enumerate the path from the root node to each leaf node. How should this algorithm be implemented?

The following example is mainly implemented in Python. In order to facilitate the construction of a tree (and the following directed graph), the NetworkX package in Python is used here (NetworkX is a graph theory and complex network modeling tool developed in Python). First introduce the necessary packages.

import copy
import networkx as nx
from collections import deque

import matplotlib.pyplot as plt
%matplotlib inline

Construct a polytree T:

T = nx.DiGraph()
T.add_nodes_from(['a','b','c','d','e','f','g','i'])
T.add_edges_from([('a','b'),('b','c'),('c','d'),('d','e')])
T.add_edges_from([('b','f'),('f','g'),('f','h'),('b','i')])

nx.draw(T, with_labels=True)
plt.show()

Executing the above code, the result obtained is as shown in the figure below (note that the tree can also be regarded as a special directed graph. The NetworkX used here is mainly used to process the graph, so the drawn tree is not like our usual The trees you see are so distinct, but they are essentially the same):

Obviously, the path from the root node a to the leaf nodes e, g, h, i are [a->b->c->d->e], [a->b->f->g], [ a->b->f->h], [a->b->i].

 

The design ideas of specific (non-recursive) algorithms can be inspired by the depth-first traversal of the tree. The only thing to note is that if there is a fork at a node, we need to maintain an additional stack structure to save the path that has been obtained before the fork. The specific implementation code is given below. Note that this is a non-recursive implementation:

majorStack = deque([])
minorStack = deque([])

listoflists = []
a_list = []

entry_node = 'a'

def find_all_paths():
    max_times = 0
    
    minorStack.append(entry_node)

    popCount = {}
    
    while minorStack:
        minLast = minorStack.pop()
        majorStack.append(minLast)
        a_list.append(minLast)
    
        current_s_nodes = list(T.successors(minLast))
        if current_s_nodes:
            for element in current_s_nodes:
                minorStack.append(element)
    
        majLast = majorStack[-1]
        #Loop condition:tos is a leaf OR all children of tos have been accessed
        while( not list(T.successors(majLast)) or 
                ( popCount.get(majLast, -1) == len(list(T.successors(majLast))))):
            last = majorStack.pop()
        
            if majorStack:
                majLast = majorStack[-1]
            else: # if majorStack is empty, then finish
                return
        
            if popCount.get(majLast, -1) == -1:
                popCount[majLast]=1
            else:
                popCount[majLast]=popCount[majLast]+1
        
            # if last is a leaf, then find a path from the root to a leaf
            if not list(T.successors(last)):
                listoflists.append(copy.deepcopy(a_list))
            
            a_list.pop()
        

find_all_paths()

print(listoflists)

Executing the above code, the results obtained are as follows:

[['a', 'b', 'i'], ['a', 'b', 'f', 'h'], ['a', 'b', 'f', 'g'], ['a', 'b', 'c', 'd', 'e']]

It can be seen that our algorithm successfully found the path from the root node to all leaf nodes.


Next, we want to expand the problem. Suppose we want to find all paths from the entry point to the exit point from a directed graph, how to solve it? Note that the directed graph here is limited to acyclic. For example, here is an example of a directed acyclic graph generated based on NetworkX:

G = nx.DiGraph()

G.add_node('a')                  #添加一个节点1
G.add_nodes_from(['b','c','d','e','f','g','i'])    #加点集合
G.add_edges_from([('a','b'),('b','c'),('c','d'),('d','e')])
G.add_edges_from([('b','f'),('f','g'),('f','h'),('b','j')])
G.add_edges_from([('h','k'),('h','i'),('j','h'),('k','l')])

nx.draw(G, with_labels=True)
plt.show()

Executing the above code, the result obtained is shown in the figure below. It can be seen that the paths from the entry node a to the exit nodes e, g, i, l are [a->b->c->d->e], [a->b->f->g], [ a->b->f->h->i], [a->b->f->h->k->l], [a->b->j->h->i], [a->b->j->h->k->l].

Based on the algorithm for searching paths in the tree given above, it is actually very simple to implement similar functions in a directed acyclic graph. We only need to add a line of code:

majorStack = deque([])
minorStack = deque([])

listoflists = []
a_list = []

entry_node = 'a'

def find_all_paths():
    max_times = 0
    
    minorStack.append(entry_node)

    popCount = {}
    
    while minorStack:
        minLast = minorStack.pop()
        majorStack.append(minLast)
        a_list.append(minLast)
    
        current_s_nodes = list(G.successors(minLast))
        if current_s_nodes:
            for element in current_s_nodes:
                minorStack.append(element)
                popCount[element]= -1
    
        majLast = majorStack[-1]
        #Loop condition:tos is a leaf OR all children of tos have been accessed
        while( not list(G.successors(majLast)) or 
                ( popCount.get(majLast, -1) == len(list(G.successors(majLast))))):
            last = majorStack.pop()
        
            if majorStack:
                majLast = majorStack[-1]
            else: # if majorStack is empty, then finish
                return
        
            if popCount.get(majLast, -1) == -1:
                popCount[majLast]=1
            else:
                popCount[majLast]=popCount[majLast]+1
        
            # if last is a leaf, then find a path from the root to a leaf
            if not list(G.successors(last)):
                listoflists.append(copy.deepcopy(a_list))
            
            a_list.pop()
        

find_all_paths()
print(listoflists)

Executing the above code, the results obtained are as follows:

[['a', 'b', 'j', 'h', 'i'], ['a', 'b', 'j', 'h', 'k', 'l'], ['a', 'b', 'f', 'h', 'i'], ['a', 'b', 'f', 'h', 'k', 'l'], ['a', 'b', 'f', 'g'], ['a', 'b', 'c', 'd', 'e']]

It can be seen that the results obtained are consistent with our expectations.

 

[End of this article]

Guess you like

Origin blog.csdn.net/baimafujinji/article/details/50373550