Bu yanıtta verilen mevcut yinelemeli olmayan DFS uygulaması bozuk göründüğü için, gerçekten çalışan bir tane sunmama izin verin.
Bunu Python'da yazdım, çünkü uygulama detaylarına göre oldukça okunaklı ve düzenli buluyorum (ve jeneratörleriyield
uygulamak için kullanışlı bir anahtar kelimeye sahip olduğu için ), ancak diğer dillere aktarılması oldukça kolay olmalı.
# a generator function to find all simple paths between two nodes in a
# graph, represented as a dictionary that maps nodes to their neighbors
def find_simple_paths(graph, start, end):
visited = set()
visited.add(start)
nodestack = list()
indexstack = list()
current = start
i = 0
while True:
# get a list of the neighbors of the current node
neighbors = graph[current]
# find the next unvisited neighbor of this node, if any
while i < len(neighbors) and neighbors[i] in visited: i += 1
if i >= len(neighbors):
# we've reached the last neighbor of this node, backtrack
visited.remove(current)
if len(nodestack) < 1: break # can't backtrack, stop!
current = nodestack.pop()
i = indexstack.pop()
elif neighbors[i] == end:
# yay, we found the target node! let the caller process the path
yield nodestack + [current, end]
i += 1
else:
# push current node and index onto stacks, switch to neighbor
nodestack.append(current)
indexstack.append(i+1)
visited.add(neighbors[i])
current = neighbors[i]
i = 0
Bu kod, iki paralel yığın tutar: biri geçerli yoldaki önceki düğümleri içerir, diğeri düğüm yığınındaki her düğüm için geçerli komşu dizinini içerir (böylece bir düğümün komşuları arasında yinelemeye devam edebiliriz. yığın). Tek bir (düğüm, dizin) çifti yığınını eşit derecede iyi kullanabilirdim, ancak iki yığın yönteminin daha okunabilir ve belki de diğer dilleri kullanan kullanıcılar için daha kolay uygulanabileceğini düşündüm.
Bu kod ayrıca, bir visited
düğümün zaten geçerli yolun bir parçası olup olmadığını verimli bir şekilde kontrol etmeme izin vermek için her zaman geçerli düğümü ve yığındaki düğümleri içeren ayrı bir küme kullanır . Diliniz, hem verimli yığın benzeri push / pop işlemleri hem de verimli üyelik sorguları sağlayan "sıralı küme" veri yapısına sahipse , bunu düğüm yığını için kullanabilir ve ayrı visited
kümeden kurtulabilirsiniz .
Alternatif olarak, düğümleriniz için özel bir değiştirilebilir sınıf / yapı kullanıyorsanız, geçerli arama yolunun bir parçası olarak ziyaret edilip edilmediğini belirtmek için her düğümde bir boole bayrağı depolayabilirsiniz. Elbette, bu yöntem, herhangi bir nedenle bunu yapmak istemeniz durumunda, aynı grafik üzerinde paralel olarak iki arama yapmanıza izin vermez.
İşte yukarıda verilen fonksiyonun nasıl çalıştığını gösteren bazı test kodları:
# test graph:
# ,---B---.
# A | D
# `---C---'
graph = {
"A": ("B", "C"),
"B": ("A", "C", "D"),
"C": ("A", "B", "D"),
"D": ("B", "C"),
}
# find paths from A to D
for path in find_simple_paths(graph, "A", "D"): print " -> ".join(path)
Bu kodu verilen örnek grafikte çalıştırmak aşağıdaki çıktıyı üretir:
A -> B -> C -> D
A -> B -> D
A -> C -> B -> D
A -> C -> D
Bu örnek grafiğin yönsüz olmasına rağmen (yani, tüm kenarları her iki yöne gitmektedir), algoritmanın rastgele yönlendirilmiş grafikler için de çalıştığını unutmayın. Örneğin, C -> B
kenarın kaldırılması ( B
komşu listesinden kaldırılarak ), artık mümkün olmayan C
üçüncü yol ( A -> C -> B -> D
) dışında aynı çıktıyı verir .
Ps. Bunun gibi basit arama algoritmalarının (ve bu başlıkta verilen diğerlerinin) çok zayıf performans gösterdiği grafikler oluşturmak kolaydır.
Örneğin, başlangıç düğümü A'nın iki komşusu olduğu yönsüz bir grafikte A'dan B'ye tüm yolları bulma görevini düşünün: hedef düğüm B (A'dan başka komşusu olmayan) ve bir kliğin parçası olan bir C düğümü. ve n, böyle + 1 düğümleri:
graph = {
"A": ("B", "C"),
"B": ("A"),
"C": ("A", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O"),
"D": ("C", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O"),
"E": ("C", "D", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O"),
"F": ("C", "D", "E", "G", "H", "I", "J", "K", "L", "M", "N", "O"),
"G": ("C", "D", "E", "F", "H", "I", "J", "K", "L", "M", "N", "O"),
"H": ("C", "D", "E", "F", "G", "I", "J", "K", "L", "M", "N", "O"),
"I": ("C", "D", "E", "F", "G", "H", "J", "K", "L", "M", "N", "O"),
"J": ("C", "D", "E", "F", "G", "H", "I", "K", "L", "M", "N", "O"),
"K": ("C", "D", "E", "F", "G", "H", "I", "J", "L", "M", "N", "O"),
"L": ("C", "D", "E", "F", "G", "H", "I", "J", "K", "M", "N", "O"),
"M": ("C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "N", "O"),
"N": ("C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "O"),
"O": ("C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N"),
}
A ve B arasındaki tek yolun doğrudan yol olduğunu görmek kolaydır, ancak A düğümünden başlatılan saf bir DFS , klik içindeki yolları keşfetmek için O ( n !) Zamanını boşa harcayacaktır (bir insan için) bu yollardan hiçbiri muhtemelen B'ye götürmez.
Bir de gerçekleştirebilmesi DAG'leri başlangıç düğümü, A bağlantı hedef düğüm B ve diğer iki düğüm C olan örn benzer özelliklere sahip 1 ve C 2 düğümlerine bağlanmak her ikisi de, D 1 ve D, 2 E bağlanmak her ikisi de, 1 ve E 2 vb. Bunun gibi düzenlenmiş n düğüm katmanı için, A'dan B'ye tüm yollar için saf bir arama, pes etmeden önce tüm olası çıkmazları inceleyerek O (2 n ) zamanı boşa harcayacaktır .
Tabii ki, (C dışındaki) klik düğümlerin biri hedef düğüm B'ye bir kenar ekleme veya DAG son tabakadan, olur katlanarak büyük B etmek üzere olası yolların sayısı ve a oluşturmak tamamen yerel arama algoritması, böyle bir avantaj bulup bulmayacağını önceden söyleyemez. Dolayısıyla, bir anlamda, bu tür saf aramaların zayıf çıktı duyarlılığı , grafiğin küresel yapısına ilişkin farkındalık eksikliğinden kaynaklanmaktadır.
Bu "üstel zaman çıkmazlarından" bazılarından kaçınmak için kullanılabilecek çeşitli ön işleme yöntemleri (yaprak düğümlerini yinelemeli olarak ortadan kaldırmak, tek düğümlü köşe ayırıcıları aramak vb.) Varken, herhangi bir genel her durumda onları ortadan kaldırabilecek ön işleme hilesi Genel bir çözüm, aramanın her adımında hedef düğümün hala erişilebilir olup olmadığını kontrol etmek (bir alt arama kullanarak) ve değilse erken geri izlemek olabilir - ancak ne yazık ki bu, aramayı önemli ölçüde yavaşlatacaktır (en kötü durumda orantılı olarak çok grafikler için grafik boyutu) yok gibi patolojik çıkmaz içerir.