From d1081b02c2a2d50ac0e4e95b1f84f17259b719d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matheus=20Di=C3=B3genes=20Andrade?= Date: Tue, 5 Mar 2024 11:09:19 +0100 Subject: [PATCH] Algorithm for finding the longest path of a DAG (#209) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Algorithm for finding the longest path of a DAG * Automated test issue * Including longest path test * Test fix * Simplify * Fix JET warning * Fixing doctest * Adjustments * Simplify --------- Co-authored-by: Matheus DiĆ³genes Andrade Co-authored-by: Guillaume Dalle <22795598+gdalle@users.noreply.github.com> --- docs/src/algorithms/shortestpaths.md | 3 ++- src/Graphs.jl | 7 +++++- src/shortestpaths/longest_path.jl | 35 ++++++++++++++++++++++++++++ src/shortestpaths/utils.jl | 9 +++++++ test/runtests.jl | 2 ++ test/shortestpaths/longest_path.jl | 20 ++++++++++++++++ test/shortestpaths/utils.jl | 9 +++++++ 7 files changed, 83 insertions(+), 2 deletions(-) create mode 100644 src/shortestpaths/longest_path.jl create mode 100644 src/shortestpaths/utils.jl create mode 100644 test/shortestpaths/longest_path.jl create mode 100644 test/shortestpaths/utils.jl diff --git a/docs/src/algorithms/shortestpaths.md b/docs/src/algorithms/shortestpaths.md index 8db4ad3d9..4c5ef5692 100644 --- a/docs/src/algorithms/shortestpaths.md +++ b/docs/src/algorithms/shortestpaths.md @@ -1,6 +1,6 @@ # Shortest paths -*Graphs.jl* includes standard algorithms for [shortest paths](https://en.wikipedia.org/wiki/Shortest_path_problem). +*Graphs.jl* includes standard algorithms for [shortest paths](https://en.wikipedia.org/wiki/Shortest_path_problem) and longest paths. ## Index @@ -19,6 +19,7 @@ Pages = [ "shortestpaths/dijkstra.jl", "shortestpaths/floyd-warshall.jl", "shortestpaths/johnson.jl", + "shortestpaths/longest_path.jl", "shortestpaths/spfa.jl", "shortestpaths/yen.jl", ] diff --git a/src/Graphs.jl b/src/Graphs.jl index e573f5d69..675db9eef 100644 --- a/src/Graphs.jl +++ b/src/Graphs.jl @@ -426,7 +426,10 @@ export independent_set, # vertexcover - vertex_cover + vertex_cover, + + # longestpaths + dag_longest_path """ Graphs @@ -496,6 +499,7 @@ include("traversals/eulerian.jl") include("connectivity.jl") include("distance.jl") include("editdist.jl") +include("shortestpaths/utils.jl") include("shortestpaths/astar.jl") include("shortestpaths/bellman-ford.jl") include("shortestpaths/dijkstra.jl") @@ -504,6 +508,7 @@ include("shortestpaths/desopo-pape.jl") include("shortestpaths/floyd-warshall.jl") include("shortestpaths/yen.jl") include("shortestpaths/spfa.jl") +include("shortestpaths/longest_path.jl") include("linalg/LinAlg.jl") include("operators.jl") include("persistence/common.jl") diff --git a/src/shortestpaths/longest_path.jl b/src/shortestpaths/longest_path.jl new file mode 100644 index 000000000..b610102ab --- /dev/null +++ b/src/shortestpaths/longest_path.jl @@ -0,0 +1,35 @@ +""" + dag_longest_path(g, distmx=weights(g); topological_order=topological_sort_by_dfs(g)) + +Return a longest path within the directed acyclic graph `g`, with distance matrix `distmx` and using `topological_order` to iterate on vertices. +""" +function dag_longest_path end + +@traitfn function dag_longest_path( + g::::IsDirected, + distmx::AbstractMatrix=weights(g); + topological_order=topological_sort_by_dfs(g), +) + U = eltype(g) + T = eltype(distmx) + + dists = zeros(T, nv(g)) + parents = zeros(U, nv(g)) + + for v in topological_order + for u in inneighbors(g, v) + newdist = dists[u] + distmx[u, v] + if newdist > dists[v] + dists[v] = newdist + parents[v] = u + end + end + end + + if isempty(dists) + return U[] + else + v = argmax(dists) + return path_from_parents(v, parents) + end +end diff --git a/src/shortestpaths/utils.jl b/src/shortestpaths/utils.jl new file mode 100644 index 000000000..585e4ad96 --- /dev/null +++ b/src/shortestpaths/utils.jl @@ -0,0 +1,9 @@ +function path_from_parents(target::Integer, parents::AbstractVector) + v = target + path = [v] + while parents[v] != v && parents[v] != zero(v) + v = parents[v] + pushfirst!(path, v) + end + return path +end diff --git a/test/runtests.jl b/test/runtests.jl index 855ebda58..df304b74f 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -91,6 +91,7 @@ tests = [ "edit_distance", "connectivity", "persistence/persistence", + "shortestpaths/utils", "shortestpaths/astar", "shortestpaths/bellman-ford", "shortestpaths/desopo-pape", @@ -99,6 +100,7 @@ tests = [ "shortestpaths/floyd-warshall", "shortestpaths/yen", "shortestpaths/spfa", + "shortestpaths/longest_path", "traversals/bfs", "traversals/bipartition", "traversals/greedy_color", diff --git a/test/shortestpaths/longest_path.jl b/test/shortestpaths/longest_path.jl new file mode 100644 index 000000000..29a99c933 --- /dev/null +++ b/test/shortestpaths/longest_path.jl @@ -0,0 +1,20 @@ +@testset "Longest path" begin + # empty DAG + g = DiGraph() + @test dag_longest_path(g) == Int[] + + # unweighted DAG + g = SimpleDiGraphFromIterator(Edge.([(1, 2), (2, 3), (2, 4), (3, 5), (5, 6), (3, 7)])) + @test dag_longest_path(g) == [1, 2, 3, 5, 6] + + # weighted DAG + n = 6 + g = DiGraph(n) + A = [(1, 2, -5), (2, 3, 1), (3, 4, 1), (4, 5, 0), (3, 5, 4), (1, 6, 2)] + distmx = fill(NaN, n, n) + for (i, j, dist) in A + add_edge!(g, (i, j)) + distmx[i, j] = dist + end + @test dag_longest_path(g, distmx) == [2, 3, 5] +end diff --git a/test/shortestpaths/utils.jl b/test/shortestpaths/utils.jl new file mode 100644 index 000000000..adfe54ea4 --- /dev/null +++ b/test/shortestpaths/utils.jl @@ -0,0 +1,9 @@ +@testset "Path from parents" begin + using Graphs: path_from_parents + parents = [3, 0, 2, 5, 5] + @test path_from_parents(1, parents) == [2, 3, 1] + @test path_from_parents(2, parents) == [2] + @test path_from_parents(3, parents) == [2, 3] + @test path_from_parents(4, parents) == [5, 4] + @test path_from_parents(5, parents) == [5] +end