[英]How can I make a recursive search for longest node more efficient?
我正試圖在有向非循環圖中找到最長的路徑。 目前,我的代碼似乎運行時間復雜度為O(n 3 ) 。
該圖是輸入{0: [1,2], 1: [2,3], 3: [4,5] }
#Input: dictionary: graph, int: start, list: path
#Output: List: the longest path in the graph (Recurrance)
# This is a modification of a depth first search
def find_longest_path(graph, start, path=[]):
path = path + [start]
paths = path
for node in graph[start]:
if node not in path:
newpaths = find_longest_path(graph, node, path)
#Only take the new path if its length is greater than the current path
if(len(newpaths) > len(paths)):
paths = newpaths
return paths
它返回表單中的節點列表,例如[0,1,3,5]
如何使這比O(n 3 )更有效? 遞歸是解決這個問題的正確方法,還是應該使用不同的循環?
您可以在O(n + e)中解決此問題(即節點數+線的線性)。
這個想法是你首先創建一個拓撲排序(我是Tarjan算法的粉絲)和反向邊集。 如果您可以分解問題以利用現有解決方案,那將始終有所幫助。
然后,您向后走拓撲排序,向子節點推送其子節點距離+ 1(如果有多條路徑,則保持最大值)。 跟蹤到目前為止看到的最大距離的節點。
當您完成帶有距離的所有節點的注釋后,您可以從具有最長距離的節點開始,該節點將是您最長的路徑根,然后選擇與當前節點相比正好少一個的子節點向下走圖表(因為他們躺在關鍵路徑上)。
通常,當試圖找到最優復雜度算法時,不要害怕一個接一個地運行多個階段。 順序運行的五個O(n)算法仍然是O(n),並且從復雜性的角度來看仍然優於O(n 2 ) (盡管實際運行時間可能更差,取決於恆定的成本/因子和n的大小) 。
ETA:我剛注意到你有一個開始節點。 這使得它只是進行深度優先搜索並保持迄今為止看到的最長解決方案的情況,無論如何都只是O(n + e) 。 遞歸很好,或者你可以保留一個列表/堆棧的訪問節點(每次回溯時找到下一個孩子時都要小心)。
當您從深度優先搜索回溯時,您需要存儲從該節點到葉子的最長路徑,這樣您就不會重新處理任何子樹。 這也將作為visited
標志(即除了執行node not in path
測試中的node not in subpath_cache
還有一個node not in subpath_cache
之前node not in subpath_cache
測試中)。 您可以存儲長度,而不是存儲子路徑,然后基於上面討論的順序值(關鍵路徑)重建路徑。
ETA2:這是一個解決方案。
def find_longest_path_rec(graph, parent, cache):
maxlen = 0
for node in graph[parent]:
if node in cache:
pass
elif node not in graph:
cache[node] = 1
else:
cache[node] = find_longest_path_rec(graph, node, cache)
maxlen = max(maxlen, cache[node])
return maxlen + 1
def find_longest_path(graph, start):
cache = {}
maxlen = find_longest_path_rec(graph, start, cache)
path = [start]
for i in range(maxlen-1, 0, -1):
for node in graph[path[-1]]:
if cache[node] == i:
path.append(node)
break
else:
assert(0)
return path
請注意,我已刪除了node not in path
測試中的node not in path
因為我假設您實際上正在提供所聲明的DAG。 如果你想要那個檢查,你應該提出錯誤而不是忽略它。 另請注意,我已將斷言添加到for
的else
子句中以記錄我們必須始終在路徑中找到有效的下一個(順序)節點。
ETA3:最后的for
循環有點令人困惑。 我們正在做的是考慮在關鍵路徑中所有節點距離必須是連續的。 考慮節點0是距離4,節點1是距離3而節點2是距離1.如果我們的路徑開始[0, 2, ...]
我們有一個矛盾,因為節點0離葉子不是2而不是2。
我建議有一些非算法改進(這些改進與Python代碼質量有關):
def find_longest_path_from(graph, start, path=None):
"""
Returns the longest path in the graph from a given start node
"""
if path is None:
path = []
path = path + [start]
max_path = path
nodes = graph.get(start, [])
for node in nodes:
if node not in path:
candidate_path = find_longest_path_from(graph, node, path)
if len(candidate_path) > len(max_path):
max_path = candidate_path
return max_path
def find_longest_path(graph):
"""
Returns the longest path in a graph
"""
max_path = []
for node in graph:
candidate_path = find_longest_path_from(graph, node)
if len(candidate_path) > len(max_path):
max_path = candidate_path
return max_path
變更說明:
def find_longest_path_from(graph, start, path=None):
if path is None:
path = []
我已將find_longest_path
重命名為find_longest_path_from
以更好地解釋它的作用。
將path
參數更改為默認參數值為None
而不是[]
。 除非您知道您將從中受益,否則您希望避免將可變對象用作Python中的默認參數。 這意味着您通常應該默認將path
設置為None
,然后在調用該函數時,檢查path is None
是否path is None
並相應地創建一個空列表。
max_path = path
...
candidate_path = find_longest_path_from(graph, node, path)
...
我已經將變量的名稱從paths
更新為max_path
,將newpaths
為candidate_path
。 這些是令人困惑的變量名稱,因為它們引用了復數路徑 - 暗示它們存儲的值由多條路徑組成 - 實際上它們每條只保持一條路徑。 我試圖給他們更多描述性的名字。
nodes = graph.get(start, [])
for node in nodes:
您的示例輸入中的代碼錯誤,因為圖形的葉節點不是dict
鍵,因此例如,當start
為2
, graph[start]
會引發KeyError
。 這通過返回空列表來處理start
不是graph
的鍵的情況。
def find_longest_path(graph):
"""
Returns the longest path in a graph
"""
max_path = []
for node in graph:
candidate_path = find_longest_path_from(graph, node)
if len(candidate_path) > len(max_path):
max_path = candidate_path
return max_path
一種在圖中查找迭代鍵的最長路徑的方法。 這與find_longest_path_from
的算法分析完全不同,但我想包含它。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.