樹(或有向無環圖)中根結點到所有葉子的路徑

問題:假設現在有一棵樹,注意這裏的樹不一定是二叉樹(也即可以是多叉樹),我們希望枚舉出從根結點到每一個葉子結點的路徑,這個算法該如何實現?

下面的例子主要採用Python來實現。爲了方便構建一棵樹(還有後面的有向圖),這裏直接使用Python中NetworkX包(NetworkX是一個用Python語言開發的圖論與複雜網絡建模工具)。首先引入必要的包。

import copy
import networkx as nx
from collections import deque

import matplotlib.pyplot as plt
%matplotlib inline

構建一棵多叉樹 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()

執行上述代碼,所得之結果如下圖所示(注意,樹也可以看成是一種特殊的有向圖。這裏使用的NetworkX主要是用來處理圖的,所以繪製出來的樹並不像通常我們看到的樹那麼層次分明,但是本質上它們是一致的):

顯然,從根節點a到葉子結點e、g、h、i的路徑有[a->b->c->d->e]、[a->b->f->g]、[a->b->f->h]、[a->b->i]。

 

具體(非遞歸)算法的設計思路可以從樹的深度優先遍歷中獲得啓示。唯一需要注意的是,如果在某結點處存在分叉,我們需要維護額外一個棧結構來保存分叉點前面已經得到路徑。下面給出具體實現的代碼,注意這是一個非遞歸的實現:

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)

執行上述代碼,所得之結果如下:

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

可見,我們的算法成功地找出了根節點到所有葉子結點的路徑。


下面,我們想把問題拓展一下,假設要從一個有向圖中找到從入口點到出口點的所有路徑該怎麼解決呢?注意,這裏的有向圖限定爲無環的。例如,這裏給出一個基於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()

執行上述代碼,所得之結果如下圖所示。可見從入口結點a到出口結點e、g、i、l的路徑有[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]。

基於上面給出的在樹中搜索路徑的算法,於有向無環圖中實現類似的功能其實是非常簡單的,我們只要添加一行代碼即可:

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)

執行上述代碼,所得之結果如下:

[['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']]

可見所得之結果跟我們預想的是一致的。

 

【本文完】

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章