Jak sprawdzić, czy skierowany wykres jest acykliczny? A jak nazywa się ten algorytm? Byłbym wdzięczny za odniesienie.Jak sprawdzić, czy skierowany wykres jest acykliczny?
Odpowiedz
Chciałbym spróbować sort the graph topologically, a jeśli nie, to ma cykle.
Jak to nie miało głosów? Jest liniowy na węzłach + krawędzie, znacznie lepszy od rozwiązań O (n^2)! –
Właśnie odpowiedziałem na to :) – FryGuy
W wielu przypadkach DFS (patrz odpowiedź J.Conroda) może być łatwiejsze, szczególnie jeśli DFS i tak musi zostać wykonany. Ale oczywiście zależy to od kontekstu. – sleske
Rozwiązanie podane przez ShuggyCoUk jest niekompletne, ponieważ może nie sprawdzić wszystkich węzłów.
def isDAG(nodes V):
while there is an unvisited node v in V:
bool cycleFound = dfs(v)
if cyclefound:
return false
return true
Ma timecomplexity O (N + M) lub O (N^2)
moja jest rzeczywiście niepoprawna - usunąłem ją, więc twoje teraz wydaje się trochę poza kontekstem – ShuggyCoUk
O (n + m) <= O (n + n) = O (2n), O (2n)! = O (n^2) – Artru
@Artru O (n^2) podczas korzystania z macierzy sąsiedztwa, O (n + m) przy użyciu listy sąsiedztwa do reprezentowania wykresu. – 0x450
wykonanie prostego głębokość pierwszej wyszukiwania jest nie wystarczająco dobre, aby znaleźć cyklu. Możliwe jest wielokrotne odwiedzanie węzła w systemie plików DFS bez istniejącego cyklu. W zależności od tego, od czego zaczynasz, możesz również nie odwiedzać całego wykresu.
Można sprawdzić cykle w podłączonym komponencie wykresu w następujący sposób. Znajdź węzeł, który ma tylko wychodzące krawędzie. Jeśli nie ma takiego węzła, to jest cykl. Uruchom system DFS w tym węźle. Podczas przechodzenia przez każdą krawędź sprawdź, czy krawędź wskazuje na węzeł znajdujący się już na stosie. Wskazuje to na istnienie cyklu. Jeśli nie znajdziesz takiej krawędzi, w podłączonym komponencie nie ma cykli.
Jak zauważa Protein Rutger, jeśli wykres nie jest podłączony, należy powtórzyć wyszukiwanie na każdym podłączonym komponencie.
Jako odniesienie, Tarjan's strongly connected component algorithm jest blisko spokrewniona. Pomoże Ci również znaleźć cykle, a nie tylko zgłosić, czy istnieją.
BTW: Krawędź, która "wskazuje na węzeł znajdujący się już na stosie" jest często nazywana "tylną krawędzią" w literaturze, z oczywistych powodów. I tak, może to być prostsze niż sortowanie wykresu topologicznie, zwłaszcza jeśli i tak musisz zrobić DFS. – sleske
Aby wykres był acykliczny, mówimy, że każdy podłączony komponent musi zawierać węzeł z tylko wychodzącymi krawędziami. Czy możesz polecić algorytm znajdowania połączonych komponentów (w przeciwieństwie do "silnie" połączonych komponentów) skierowanego wykresu, do użycia przez twój główny algorytm? – kostmo
@kostmo, jeśli wykres zawiera więcej niż jeden podłączony komponent, nie będzie odwiedzany wszystkich węzłów w pierwszym systemie plików DFS. Śledź odwiedzone węzły i powtórz algorytm z nieodwiedzonymi węzłami, aż dotrzesz do wszystkich. Zasadniczo tak działa algorytm połączonych komponentów. –
Lemat 22.11 na książce Introduction to Algorithms
(Second Edition) stwierdza, że:
graf skierowany G jest acykliczny wtedy i tylko wtedy, gdy przeszukiwanie w głąb G daje brak tylnych krawędziach
Wiem, że jest to stary temat, ale dla przyszłych poszukiwaczy jest stworzona przeze mnie implementacja C# (nie twierdzę, że jest najbardziej wydajna!). Jest zaprojektowany do używania prostej liczby całkowitej do identyfikacji każdego węzła. Możesz udekorować to, co lubisz, pod warunkiem, że twoje obiekty węzłów są odpowiednio wartościowane i równe.
W przypadku bardzo głębokich wykresów może to mieć duży narzut, ponieważ tworzy hasz w każdym węźle dogłębnym (są one niszczone na całej szerokości).
Wprowadzasz węzeł, z którego chcesz szukać, a ścieżka do tego węzła.
- Na wykresie z pojedynczego węzła głównego wysłaniu tego węzła i pusty hashset
- Dla wykresu mającej wiele węzłów korzeniowych owinąć to w foreach nad tymi węzłami i przekazać nowy pusty hashset dla każdej iteracji
Podczas sprawdzania cykli poniżej danym węźle, po prostu przekazać ten węzeł wraz z pustym HashSet
private bool FindCycle(int node, HashSet<int> path) { if (path.Contains(node)) return true; var extendedPath = new HashSet<int>(path) {node}; foreach (var child in GetChildren(node)) { if (FindCycle(child, extendedPath)) return true; } return false; }
Oto mój rubin reali tacji peel off leaf node algorithm.
def detect_cycles(initial_graph, number_of_iterations=-1)
# If we keep peeling off leaf nodes, one of two things will happen
# A) We will eventually peel off all nodes: The graph is acyclic.
# B) We will get to a point where there is no leaf, yet the graph is not empty: The graph is cyclic.
graph = initial_graph
iteration = 0
loop do
iteration += 1
if number_of_iterations > 0 && iteration > number_of_iterations
raise "prevented infinite loop"
end
if graph.nodes.empty?
#puts "the graph is without cycles"
return false
end
leaf_nodes = graph.nodes.select { |node| node.leaving_edges.empty? }
if leaf_nodes.empty?
#puts "the graph contain cycles"
return true
end
nodes2 = graph.nodes.reject { |node| leaf_nodes.member?(node) }
edges2 = graph.edges.reject { |edge| leaf_nodes.member?(edge.destination) }
graph = Graph.new(nodes2, edges2)
end
raise "should not happen"
end
Porada1: algorytm Kahn, by sprawdzić cykl. Główna idea: Utrzymanie kolejki, w której węzeł o zerowym stopniu dokładności zostanie dodany do kolejki. Następnie odklejaj węzeł jeden po drugim, aż kolejka będzie pusta. Sprawdź, czy istnieją wewnętrzne krawędzie węzła.
Solution2: Algorytm Tarjana do sprawdzenia Silnie połączony komponent.
Rozwiązanie3: DFS. Użyj tablicy liczb całkowitych, aby oznaczyć aktualny status węzła: , tj. 0 - oznacza, że ten węzeł nie był wcześniej odwiedzany. -1 - oznacza, że ten węzeł został odwiedzony, a jego węzły podrzędne są odwiedzane. 1 - oznacza, że ten węzeł został odwiedzony i zakończył działanie. Więc jeśli status węzła wynosi -1 podczas wykonywania DFS, oznacza to, że musi istnieć cykl.
Nie powinno być żadnej tylnej krawędzi podczas wykonywania ścieżki DFS.Keep już odwiedzonych węzłów podczas wykonywania DFS, jeśli wystąpi krawędź między bieżącym węzłem a istniejącym węzłem, wówczas wykres ma cykl.
tutaj jest SWIFT CODE znaleźć jeśli wykres ma cykle:
func isCyclic(G : Dictionary<Int,Array<Int>>,root : Int , var visited : Array<Bool>,var breadCrumb : Array<Bool>)-> Bool
{
if(breadCrumb[root] == true)
{
return true;
}
if(visited[root] == true)
{
return false;
}
visited[root] = true;
breadCrumb[root] = true;
if(G[root] != nil)
{
for child : Int in G[root]!
{
if(isCyclic(G,root : child,visited : visited,breadCrumb : breadCrumb))
{
return true;
}
}
}
breadCrumb[root] = false;
return false;
}
let G = [0:[1,2,3],1:[4,5,6],2:[3,7,6],3:[5,7,8],5:[2]];
var visited = [false,false,false,false,false,false,false,false,false];
var breadCrumb = [false,false,false,false,false,false,false,false,false];
var isthereCycles = isCyclic(G,root : 0, visited : visited, breadCrumb : breadCrumb)
Pomysł jest tak: normalny DFS Algorytm z tablicą do śledzenia odwiedzanych węzłów oraz dodatkową tablicę, która służy jako znacznik dla węzłów, które doprowadziły do bieżącego węzła, tak, że gdy kiedykolwiek wykonamy dfs dla węzła, ustawiamy jego odpowiedni element w tablicy znaczników jako true, tak że gdy kiedykolwiek napotkany już węzeł napotka, sprawdzamy, czy odpowiadający mu węzeł pozycja w tablicy znaczników ma wartość true, jeśli jest prawdziwa, to jest to jeden z węzłów, które dałyby sobie (stąd cykl), a sztuczka jest taka, że gdy dfs węzła powraca, ustawiamy odpowiedni znacznik z powrotem do fałszywego, więc jeśli odwiedzimy go ponownie z innej trasy, nie damy się oszukać.
Inna sprawa na korzyść jakiejś metody "naprawienia" błędnych odpowiedzi na SO. – Sparr
Więc, umm, najbardziej interesuje mnie czas potrzebny na znalezienie. Potrzebuję abstrakcyjnego algorytmu. – nes1983
musisz przemierzyć wszystkie krawędzie i sprawdzić wszystkie wierzchołki, tak aby dolna granica była równa O (| V | + | E |). DFS i BFS są zarówno tej samej złożoności, ale DFS jest łatwiejszy do kodu, jeśli masz rekurencję, ponieważ zarządza stos dla ciebie ... – ShuggyCoUk