Làm cách nào để kiểm tra xem một đồ thị có hướng có phải là hình tròn không? Và thuật toán được gọi như thế nào? Tôi sẽ đánh giá cao một tài liệu tham khảo.
Làm cách nào để kiểm tra xem một đồ thị có hướng có phải là hình tròn không? Và thuật toán được gọi như thế nào? Tôi sẽ đánh giá cao một tài liệu tham khảo.
Câu trả lời:
Tôi sẽ cố gắng sắp xếp biểu đồ theo cấu trúc liên kết , và nếu bạn không thể, thì nó có chu kỳ.
Thực hiện tìm kiếm theo chiều sâu đơn giản là không đủ tốt để tìm ra một chu trình. Có thể truy cập một nút nhiều lần trong DFS mà không có chu kỳ tồn tại. Tùy thuộc vào nơi bạn bắt đầu, bạn cũng có thể không truy cập toàn bộ biểu đồ.
Bạn có thể kiểm tra các chu kỳ trong một thành phần được kết nối của biểu đồ như sau. Tìm một nút chỉ có các cạnh đi ra. Nếu không có nút này, thì có một chu trình. Bắt đầu một DFS tại nút đó. Khi đi qua mỗi cạnh, hãy kiểm tra xem cạnh đó có quay lại một nút đã có trên ngăn xếp của bạn hay không. Điều này cho thấy sự tồn tại của một chu kỳ. Nếu bạn không tìm thấy cạnh nào như vậy, không có chu trình nào trong thành phần được kết nối đó.
Như Rutger Prins đã chỉ ra, nếu đồ thị của bạn không được kết nối, bạn cần lặp lại tìm kiếm trên từng thành phần được kết nối.
Như một tài liệu tham khảo, thuật toán thành phần được kết nối mạnh mẽ của Tarjan có liên quan chặt chẽ. Nó cũng sẽ giúp bạn tìm thấy các chu kỳ, không chỉ báo cáo liệu chúng có tồn tại hay không.
Bổ đề 22.11 trong Sách Introduction to Algorithms
(Tái bản lần thứ hai) nói rằng:
Đồ thị có hướng G là xoay chiều nếu và chỉ khi tìm kiếm theo chiều sâu về G không mang lại cạnh sau
Giải pháp1:Thuật toán Kahn để kiểm tra chu kỳ . Ý tưởng chính: Duy trì một hàng đợi trong đó nút có độ bằng 0 sẽ được thêm vào hàng đợi. Sau đó bóc từng nút một cho đến khi hàng đợi trống. Kiểm tra xem có tồn tại trong các cạnh của nút nào không.
Giải pháp 2 : Thuật toán Tarjan để kiểm tra Thành phần được kết nối mạnh mẽ.
Giải pháp 3 : DFS . Sử dụng mảng số nguyên để gắn thẻ trạng thái hiện tại của nút: tức là 0 - nghĩa là nút này chưa được truy cập trước đây. -1 - nghĩa là nút này đã được truy cập và các nút con của nó đang được truy cập. 1 - có nghĩa là nút này đã được truy cập và nó đã hoàn tất. Vì vậy, nếu trạng thái của một nút là -1 trong khi thực hiện DFS, điều đó có nghĩa là phải có một chu trình tồn tại.
Giải pháp do ShuggyCoUk đưa ra không hoàn chỉnh vì nó có thể không kiểm tra tất cả các nút.
def isDAG(nodes V):
while there is an unvisited node v in V:
bool cycleFound = dfs(v)
if cyclefound:
return false
return true
Điều này có thời gian giãn cách O (n + m) hoặc O (n ^ 2)
m = O(n^2)
vì đồ thị hoàn chỉnh có m=n^2
các cạnh chính xác . Vậy đó O(n+m) = O(n + n^2) = O(n^2)
.
Tôi biết đây là một chủ đề cũ nhưng đối với những người tìm kiếm trong tương lai, đây là một triển khai C # mà tôi đã tạo (không có khẳng định rằng nó hiệu quả nhất!). Điều này được thiết kế để sử dụng một số nguyên đơn giản để xác định mỗi nút. Bạn có thể trang trí theo cách bạn thích miễn là đối tượng nút của bạn được băm và cân bằng đúng cách.
Đối với đồ thị rất sâu, điều này có thể có chi phí cao, vì nó tạo ra một bộ băm ở mỗi nút ở độ sâu (chúng bị phá hủy theo chiều rộng).
Bạn nhập nút mà bạn muốn tìm kiếm từ đó và đường dẫn đến nút đó.
Khi kiểm tra các chu kỳ bên dưới bất kỳ nút nhất định nào, chỉ cần chuyển nút đó cùng với một bộ băm trống
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;
}
đây là một mã nhanh để tìm xem một biểu đồ có chu kỳ hay không:
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)
Ý tưởng là như thế này: một thuật toán dfs bình thường với một mảng để theo dõi các nút đã truy cập và một mảng bổ sung đóng vai trò là điểm đánh dấu cho các nút dẫn đến nút hiện tại, để khi nào chúng ta thực thi một dfs cho một nút chúng tôi đặt mục tương ứng của nó trong mảng đánh dấu là true, để khi nào một nút đã được truy cập gặp phải, chúng tôi kiểm tra xem mục tương ứng của nó trong mảng đánh dấu có đúng không, nếu nó đúng thì một trong các nút cho phép chính nó (do đó a chu kỳ), và mẹo là bất cứ khi nào một dfs của một nút trả về, chúng tôi đặt điểm đánh dấu tương ứng của nó trở lại thành false, để nếu chúng tôi truy cập lại nó từ một tuyến đường khác, chúng tôi sẽ không bị lừa.
Đây là phần triển khai ruby của tôi về thuật toán nút lá bóc .
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
Chỉ có câu hỏi này trong một cuộc phỏng vấn của Google.
Bạn có thể cố gắng sắp xếp theo cấu trúc liên kết, đó là O (V + E) trong đó V là số đỉnh và E là số cạnh. Một đồ thị có hướng là xoay chiều nếu và chỉ khi điều này có thể được thực hiện.
Loại bỏ đệ quy các nút lá cho đến khi không còn nút nào và nếu còn lại nhiều nút đơn thì bạn sẽ có một chu kỳ. Trừ khi tôi nhầm, đây là O (V ^ 2 + VE).
Tuy nhiên, một thuật toán DFS-esque hiệu quả, trường hợp xấu nhất là O (V + E), là:
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);
}
Bạn có thể sử dụng đảo ngược chu trình tìm kiếm từ câu trả lời của tôi tại đây https://stackoverflow.com/a/60196714/1763149
def is_acyclic(graph):
return not has_cycle(graph)