From 66688879bc02bb7e4471bb9f08cabed31e289326 Mon Sep 17 00:00:00 2001 From: Joseph Tindall Date: Mon, 10 Jun 2024 15:09:32 -0400 Subject: [PATCH] Eulerian paths --- .../GraphsExtensions/src/GraphsExtensions.jl | 1 + src/lib/GraphsExtensions/src/traversal.jl | 42 +++++++++++++++++++ src/lib/GraphsExtensions/test/runtests.jl | 14 +++++++ 3 files changed, 57 insertions(+) create mode 100644 src/lib/GraphsExtensions/src/traversal.jl diff --git a/src/lib/GraphsExtensions/src/GraphsExtensions.jl b/src/lib/GraphsExtensions/src/GraphsExtensions.jl index b4d56ea..6a49147 100644 --- a/src/lib/GraphsExtensions/src/GraphsExtensions.jl +++ b/src/lib/GraphsExtensions/src/GraphsExtensions.jl @@ -6,6 +6,7 @@ include("neighbors.jl") include("shortestpaths.jl") include("symrcm.jl") include("partitioning.jl") +include("traversal.jl") include("trees_and_forests.jl") include("simplegraph.jl") end diff --git a/src/lib/GraphsExtensions/src/traversal.jl b/src/lib/GraphsExtensions/src/traversal.jl new file mode 100644 index 0000000..895bbdb --- /dev/null +++ b/src/lib/GraphsExtensions/src/traversal.jl @@ -0,0 +1,42 @@ + +#Given a graph, traverse it from start vertex to end vertex, covering each edge exactly once. +#Complexity is O(length(edges(g))) +function eulerian_path(g::AbstractGraph, start_vertex, end_vertex) + #Conditions on g for the required path to exist + if start_vertex != end_vertex + @assert isodd(degree(g, start_vertex) % 2) + @assert isodd(degree(g, end_vertex) % 2) + @assert all( + x -> iseven(x), degrees(g, setdiff(vertices(g), [start_vertex, end_vertex])) + ) + else + @assert all(x -> iseven(x), degrees(g, vertices(g))) + end + + path = [] + stack = [] + current_vertex = end_vertex + g_modified = copy(g) + while !isempty(stack) || !iszero(degree(g_modified, current_vertex)) + if iszero(degree(g_modified, current_vertex)) + append!(path, current_vertex) + last_vertex = pop!(stack) + current_vertex = last_vertex + else + append!(stack, current_vertex) + vn = first(neighbors(g_modified, current_vertex)) + rem_edge!(g_modified, edgetype(g_modified)(current_vertex, vn)) + current_vertex = vn + end + end + + append!(path, current_vertex) + + return edgetype(g_modified)[ + edgetype(g_modified)(path[i], path[i + 1]) for i in 1:(length(path) - 1) + ] +end + +function eulerian_cycle(g::AbstractGraph, start_vertex) + return eulerian_path(g, start_vertex, start_vertex) +end diff --git a/src/lib/GraphsExtensions/test/runtests.jl b/src/lib/GraphsExtensions/test/runtests.jl index 81f7230..f4966ab 100644 --- a/src/lib/GraphsExtensions/test/runtests.jl +++ b/src/lib/GraphsExtensions/test/runtests.jl @@ -52,6 +52,9 @@ using NamedGraphs.GraphsExtensions: directed_graph_type, disjoint_union, distance_to_leaves, + _eulerian_cycle, + eulerian_cycle, + eulerian_path, has_edges, has_leaf_neighbor, has_vertices, @@ -68,6 +71,7 @@ using NamedGraphs.GraphsExtensions: is_rooted, is_self_loop, leaf_vertices, + make_all_degrees_even, minimum_distance_to_leaves, next_nearest_neighbors, non_leaf_edges, @@ -588,5 +592,15 @@ using Test: @test, @test_broken, @test_throws, @testset @test only(vertices_at_distance(g, 1, L - 1)) == L @test only(next_nearest_neighbors(g, 1)) == 3 @test issetequal(vertices_at_distance(g, 5, 3), [2, 8]) + + #Eulerian paths + g = path_graph(L) + path = eulerian_path(g, 1, L) + @test path == [edgetype(g)(i, i + 1) for i in 1:(L - 1)] + + g = add_edge(g, L => 1) + cycle = eulerian_cycle(g, 1) + correct_cycle = vcat([edgetype(g)(i, i + 1) for i in 1:(L - 1)], [edgetype(g)(L, 1)]) + @test cycle == correct_cycle || reverse(reverse.(cycle)) == correct_cycle end end