Як перевірити, чи спрямований графік ациклічний? А як називається алгоритм? Буду вдячний за посилання.
Як перевірити, чи спрямований графік ациклічний? А як називається алгоритм? Буду вдячний за посилання.
Відповіді:
Я спробував би сортувати графік топологічно , а якщо ви не можете, то він має цикли.
Простий глибокий пошук - це не так досить хороший , щоб знайти цикл. Можна відвідати вузол кілька разів у DFS без циклу. Залежно від того, з чого ви починаєте, ви також можете не переглядати весь графік.
Ви можете перевірити наявність циклів у підключеному компоненті графіка наступним чином. Знайдіть вузол, який має лише вихідні ребра. Якщо такого вузла немає, то існує цикл. Запустіть DFS на цьому вузлі. Під час обходу кожного ребра перевірте, чи вказує край назад на вузол, що вже є у вашому стеку. Це свідчить про існування циклу. Якщо такого краю не виявлено, у підключеному компоненті немає циклів.
Як зазначає Рутгер Прінс, якщо ваш графік не підключений, вам потрібно повторити пошук по кожному підключеному компоненту.
Як посилання, сильно зв’язаний компонентний алгоритм Тар’яна тісно пов’язаний. Це також допоможе вам знайти цикли, а не просто повідомити, чи існують вони.
Лема 22.11 про книгу Introduction to Algorithms
(друге видання) стверджує, що:
Спрямований графік G є ациклічним тоді і лише тоді, коли пошук глибини G першого не дає зворотних ребер
Рішення1 : Алгоритм Кана для перевірки циклу . Основна ідея: вести чергу, де вузол з нульовим ступенем буде доданий до черги. Потім відклеюйте вузол по одному, поки черга не порожня. Перевірте, чи існують внутрішні краї вузла.
Рішення2 : Тар'я алгоритм для перевірки Strong приєднаного компонента.
Рішення3 : DFS . Використовуйте цілочисельний масив для позначення поточного стану вузла: тобто 0 - означає, що цей вузол ще не відвідувався. -1 - означає, що цей вузол відвідали, а його дочірні вузли відвідують. 1 - означає, що цей вузол відвідано, і це зроблено. Отже, якщо при виконанні DFS статус вузла дорівнює -1, це означає, що повинен існувати цикл.
Рішення, надане ShuggyCoUk, є неповним, оскільки воно може перевірити не всі вузли.
def isDAG(nodes V):
while there is an unvisited node v in V:
bool cycleFound = dfs(v)
if cyclefound:
return false
return true
Це має часову складність O (n + m) або O (n ^ 2)
m = O(n^2)
оскільки повний графік має точно m=n^2
ребра. Отож O(n+m) = O(n + n^2) = O(n^2)
.
Я знаю, що це стара тема, але для майбутніх пошукачів тут є реалізація C #, яку я створив (не заявляю, що це найефективніше!). Це призначено для використання простого цілого числа для ідентифікації кожного вузла. Ви можете прикрасити, як завгодно, за умови, що об'єкт вузла хеш і дорівнює належним чином.
Для дуже глибоких графіків це може мати великі накладні витрати, оскільки це створює хешсет на кожному вузлі в глибину (вони руйнуються вшир).
Ви вводите вузол, з якого ви хочете здійснити пошук, і шлях до цього вузла.
Під час перевірки циклів нижче будь-якого даного вузла, просто передайте цей вузол разом з порожнім хешем
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;
}
ось швидкий код, щоб визначити, чи є графік циклами:
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)
Ідея така: звичайний алгоритм dfs з масивом для відстеження відвіданих вузлів і додатковий масив, який служить маркером для вузлів, що призвели до поточного вузла, так що коли ми коли-небудь ми виконуємо dfs для вузла ми встановлюємо відповідний елемент у масиві маркера як true, так що, коли коли-небудь вже відвіданий вузол зустрічається, ми перевіряємо, чи відповідає його відповідний елемент в масиві маркера true, якщо його true, то це один з вузлів, який дозволяє собі (отже циклу), і фокус полягає в тому, що коли dfs вузла повертається, ми встановлюємо його відповідний маркер назад на false, так що якщо ми знову відвідали його з іншого маршруту, нас не обдурять.
Щойно це запитання було в інтерв’ю Google.
Ви можете спробувати сортувати топологічно, тобто O (V + E), де V - кількість вершин, а E - кількість ребер. Спрямований графік ациклічний тоді і лише тоді, коли це можна зробити.
Рекурсивно видаляйте листові вузли, поки їх не залишиться, і якщо залишилося більше одного вузла, у вас буде цикл. Якщо я не помиляюсь, це O (V ^ 2 + VE).
Однак ефективним алгоритмом DFS-esque, найгірший випадок O (V + E), є:
function isAcyclic (root) {
const previous = new Set();
function DFS (node) {
previous.add(node);
let isAcyclic = true;
for (let child of children) {
if (previous.has(node) || DFS(child)) {
isAcyclic = false;
break;
}
}
previous.delete(node);
return isAcyclic;
}
return DFS(root);
}
Ось моя рубінова реалізація алгоритму відшарування листового вузла .
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
Ви можете використовувати інверсію циклу пошуку з моєї відповіді тут https://stackoverflow.com/a/60196714/1763149
def is_acyclic(graph):
return not has_cycle(graph)