From 5ee514cc7f5f8dfede23a8e99ee035e6b533748a Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 30 Dec 2022 22:08:58 +0100 Subject: [PATCH 01/28] add is_tree func + exports --- src/Graphs.jl | 10 +++++- src/prufer/prufer_coding.jl | 67 +++++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 src/prufer/prufer_coding.jl diff --git a/src/Graphs.jl b/src/Graphs.jl index 171204112..c85f8f52b 100644 --- a/src/Graphs.jl +++ b/src/Graphs.jl @@ -21,7 +21,9 @@ using DataStructures: in_same_set, peek, union!, - find_root! + find_root!, + BinaryMaxHeap, + BinaryMinHeap using LinearAlgebra: I, Symmetric, diagm, eigen, eigvals, norm, rmul!, tril, triu import LinearAlgebra: Diagonal, issymmetric, mul! using Random: @@ -392,6 +394,11 @@ export kruskal_mst, prim_mst, + # prufer coding + is_tree, + prufer_encode, + prufer_decode, + # steinertree steiner_tree, @@ -514,6 +521,7 @@ include("community/rich_club.jl") include("spanningtrees/boruvka.jl") include("spanningtrees/kruskal.jl") include("spanningtrees/prim.jl") +include("prufer/prufer_coding.jl") include("steinertree/steiner_tree.jl") include("biconnectivity/articulation.jl") include("biconnectivity/biconnect.jl") diff --git a/src/prufer/prufer_coding.jl b/src/prufer/prufer_coding.jl new file mode 100644 index 000000000..7c4351116 --- /dev/null +++ b/src/prufer/prufer_coding.jl @@ -0,0 +1,67 @@ +""" + name of func + blabla +""" + +function is_tree(g::SimpleGraph) + return is_connected(g) && ne(g)==nv(g)-1 +end + +function _prufer_degree_sequence(c::Vector{Int64}) + """ + Degree sequence from prufer code. + Returns d such that d[i] = 1 + number of occurences of i in c + """ + n = length(c)+2 + [count(==(i), c) for i in 1:n] .+ 1 +end + +function degree_sequence(g::Graph) + degree(g) +end + +function test_degree_sequence() + a = [1,2,3,4,4,4] # 8 nodes + _prufer_degree_sequence(a) == [2, 2, 2, 4, 1, 1, 1, 1] +end + +""" + prufer_decoding() +""" + +function prufer_decode(c)::Graph + n = length(c) + 2 + d = _prufer_degree_sequence(c) + L = BinaryMaxHeap(findall(==(1),d)) + g = Graph(n, 0) + + for i in 1:n-2 + l = pop!(L) # extract leaf with priority rule (max) + d[l] -= 1 # update degree sequence + add_edge!(g, l,c[i]) # add edge + d[c[i]] -= 1 # update degree sequence + d[c[i]]==1 && push!(L, c[i]) # add new leaf if any + end + + add_edge!(g, pop!(L), pop!(L)) # add last leaf + + g +end + +function prufer_encode(G::Graph) + g = copy(G) + n = nv(g) + c = zeros(Int, n-2) + d = degree_sequence(g) + L = BinaryMaxHeap(findall(==(1),d)) + for i in 1:n-2 + l = pop!(L) + v = neighbors(g,l)[1] + rem_edge!(g,l,v) + d[l] -= 1 + d[v] -= 1 + d[v] == 1 && push!(L,v) + c[i] = v + end + c +end \ No newline at end of file From 1567bfc6e0902bae6a7828b08376d309b28b8d77 Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 30 Dec 2022 22:56:33 +0100 Subject: [PATCH 02/28] add doc for exported functions --- src/prufer/prufer_coding.jl | 65 +++++++++++++++++++++++++------------ 1 file changed, 44 insertions(+), 21 deletions(-) diff --git a/src/prufer/prufer_coding.jl b/src/prufer/prufer_coding.jl index 7c4351116..9e729b0c3 100644 --- a/src/prufer/prufer_coding.jl +++ b/src/prufer/prufer_coding.jl @@ -1,13 +1,20 @@ """ - name of func - blabla + is_tree(g) + +Returns true if g is a simple tree: that is, a simple connected graph, with nv-1 edges (nv = number of vertices). Trees are the minimal connected graphs; equivalently they have no cycles. + +This is only for undirected graphs. """ function is_tree(g::SimpleGraph) return is_connected(g) && ne(g)==nv(g)-1 end -function _prufer_degree_sequence(c::Vector{Int64}) +function _is_prufer(c) + ndims(c)==1 && maximum(c) <= length(c)+2 +end + +function _degree_from_prufer(c::Vector{Int64}) """ Degree sequence from prufer code. Returns d such that d[i] = 1 + number of occurences of i in c @@ -16,44 +23,60 @@ function _prufer_degree_sequence(c::Vector{Int64}) [count(==(i), c) for i in 1:n] .+ 1 end -function degree_sequence(g::Graph) - degree(g) -end - function test_degree_sequence() a = [1,2,3,4,4,4] # 8 nodes _prufer_degree_sequence(a) == [2, 2, 2, 4, 1, 1, 1, 1] end """ - prufer_decoding() + prufer_decode(code) + +Returns the unique tree associated with the given (Prüfer) code. +Each tree of size n is associated with a Prüfer sequence (a[1], ..., a[n-2]) with 1 ⩽ a[i] ⩽ n. The sequence is constructed recursively by the leaf removal algoritm. At step k, the leaf with largest index is removed and its unique neighbor is added to the Prüfer sequence, giving a[k]. The decoding algorithm goes backward. +Ref: [Prüfer sequence on Wikipedia](https://en.wikipedia.org/wiki/Pr%C3%BCfer_sequence) """ -function prufer_decode(c)::Graph - n = length(c) + 2 - d = _prufer_degree_sequence(c) +function prufer_decode(code::Array{T,1})::Graph where {T<:Integer} + + !_is_prufer(code) && throw(ArgumentError("The code must be an Array{T,1} and must be a Prufer sequence. ")) + n = length(code) + 2 + d = _degree_from_prufer(code) L = BinaryMaxHeap(findall(==(1),d)) g = Graph(n, 0) for i in 1:n-2 l = pop!(L) # extract leaf with priority rule (max) d[l] -= 1 # update degree sequence - add_edge!(g, l,c[i]) # add edge - d[c[i]] -= 1 # update degree sequence - d[c[i]]==1 && push!(L, c[i]) # add new leaf if any + add_edge!(g, l,code[i]) # add edge + d[code[i]] -= 1 # update degree sequence + d[code[i]]==1 && push!(L, code[i]) # add new leaf if any end add_edge!(g, pop!(L), pop!(L)) # add last leaf - g + return g end -function prufer_encode(G::Graph) +""" + prufer_encode(g) + +Given a tree (a connected minimal undirected graph), returns the unique Prüfer sequence associated with this tree. + +Each tree of size n is associated with a Prüfer sequence (a[1], ..., a[n-2]) with 1 ⩽ a[i] ⩽ n. The sequence is constructed recursively by the leaf removal algoritm. At step k, the leaf with largest index is removed and its unique neighbor is added to the Prüfer sequence, giving a[k]. + +Ref: [Prüfer sequence on Wikipedia](https://en.wikipedia.org/wiki/Pr%C3%BCfer_sequence) + +""" + +function prufer_encode(G::Graph)::Array{Integer,1} + + !is_tree(g) && throw(ArgumentError("The graph must be a tree. ")) g = copy(G) n = nv(g) - c = zeros(Int, n-2) - d = degree_sequence(g) + code = zeros(Int, n-2) + d = degree(g) L = BinaryMaxHeap(findall(==(1),d)) + for i in 1:n-2 l = pop!(L) v = neighbors(g,l)[1] @@ -61,7 +84,7 @@ function prufer_encode(G::Graph) d[l] -= 1 d[v] -= 1 d[v] == 1 && push!(L,v) - c[i] = v + code[i] = v end - c -end \ No newline at end of file + + return code \ No newline at end of file From 3bee0c1873b4f613b92cda737c63883ba9f2acd7 Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 30 Dec 2022 22:56:45 +0100 Subject: [PATCH 03/28] add test structure (wip) --- test/prufer/prufer.jl | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 test/prufer/prufer.jl diff --git a/test/prufer/prufer.jl b/test/prufer/prufer.jl new file mode 100644 index 000000000..ac53282c0 --- /dev/null +++ b/test/prufer/prufer.jl @@ -0,0 +1,3 @@ +@testset "Prufer" begin + @test true == true +end \ No newline at end of file From 8cf308a6c53c5de2669ad771a0dad63510fe70a4 Mon Sep 17 00:00:00 2001 From: Simon Date: Sat, 31 Dec 2022 11:29:08 +0100 Subject: [PATCH 04/28] treesize must be >2 --- src/prufer/prufer_coding.jl | 61 ++++++++++++++++++++----------------- test/prufer/prufer.jl | 16 ++++++++-- 2 files changed, 47 insertions(+), 30 deletions(-) diff --git a/src/prufer/prufer_coding.jl b/src/prufer/prufer_coding.jl index 9e729b0c3..f12c32e55 100644 --- a/src/prufer/prufer_coding.jl +++ b/src/prufer/prufer_coding.jl @@ -7,49 +7,53 @@ This is only for undirected graphs. """ function is_tree(g::SimpleGraph) - return is_connected(g) && ne(g)==nv(g)-1 + return is_connected(g) && ne(g) == nv(g) - 1 end function _is_prufer(c) - ndims(c)==1 && maximum(c) <= length(c)+2 + ℓ = length(c) + return ndims(c) == 1 && maximum(c) <= ℓ + 2 && ℓ >= 1 end -function _degree_from_prufer(c::Vector{Int64}) +function _degree_from_prufer(c::Vector{T}) where {T<:Integer} """ Degree sequence from prufer code. Returns d such that d[i] = 1 + number of occurences of i in c """ - n = length(c)+2 - [count(==(i), c) for i in 1:n] .+ 1 + n = length(c) + 2 + return [count(==(i), c) for i in 1:n] .+ 1 end function test_degree_sequence() - a = [1,2,3,4,4,4] # 8 nodes - _prufer_degree_sequence(a) == [2, 2, 2, 4, 1, 1, 1, 1] + a = [1, 2, 3, 4, 4, 4] # 8 nodes + return _prufer_degree_sequence(a) == [2, 2, 2, 4, 1, 1, 1, 1] end """ prufer_decode(code) Returns the unique tree associated with the given (Prüfer) code. -Each tree of size n is associated with a Prüfer sequence (a[1], ..., a[n-2]) with 1 ⩽ a[i] ⩽ n. The sequence is constructed recursively by the leaf removal algoritm. At step k, the leaf with largest index is removed and its unique neighbor is added to the Prüfer sequence, giving a[k]. The decoding algorithm goes backward. +Each tree of size n is associated with a Prüfer sequence (a[1], ..., a[n-2]) with 1 ⩽ a[i] ⩽ n. The sequence is constructed recursively by the leaf removal algoritm. At step k, the leaf with smallest index is removed and its unique neighbor is added to the Prüfer sequence, giving a[k]. The decoding algorithm goes backward. Ref: [Prüfer sequence on Wikipedia](https://en.wikipedia.org/wiki/Pr%C3%BCfer_sequence) """ -function prufer_decode(code::Array{T,1})::Graph where {T<:Integer} - - !_is_prufer(code) && throw(ArgumentError("The code must be an Array{T,1} and must be a Prufer sequence. ")) +function prufer_decode(code::Array{T,1})::Graph{T} where {T<:Integer} n = length(code) + 2 + !_is_prufer(code) && throw( + ArgumentError( + "The code must be an Array{T,1} and must be a Prufer sequence with length ⩾1. ", + ), + ) d = _degree_from_prufer(code) - L = BinaryMaxHeap(findall(==(1),d)) + L = BinaryMinHeap(findall(==(1), d)) g = Graph(n, 0) - for i in 1:n-2 + for i in 1:(n - 2) l = pop!(L) # extract leaf with priority rule (max) d[l] -= 1 # update degree sequence - add_edge!(g, l,code[i]) # add edge + add_edge!(g, l, code[i]) # add edge d[code[i]] -= 1 # update degree sequence - d[code[i]]==1 && push!(L, code[i]) # add new leaf if any + d[code[i]] == 1 && push!(L, code[i]) # add new leaf if any end add_edge!(g, pop!(L), pop!(L)) # add last leaf @@ -60,31 +64,32 @@ end """ prufer_encode(g) -Given a tree (a connected minimal undirected graph), returns the unique Prüfer sequence associated with this tree. +Given a tree (a connected minimal undirected graph) of size n⩾2, returns the unique Prüfer sequence associated with this tree. -Each tree of size n is associated with a Prüfer sequence (a[1], ..., a[n-2]) with 1 ⩽ a[i] ⩽ n. The sequence is constructed recursively by the leaf removal algoritm. At step k, the leaf with largest index is removed and its unique neighbor is added to the Prüfer sequence, giving a[k]. +Each tree of size n ⩾ 2 is associated with a Prüfer sequence (a[1], ..., a[n-2]) with 1 ⩽ a[i] ⩽ n. The sequence is constructed recursively by the leaf removal algoritm. At step k, the leaf with smallest index is removed and its unique neighbor is added to the Prüfer sequence, giving a[k]. Ref: [Prüfer sequence on Wikipedia](https://en.wikipedia.org/wiki/Pr%C3%BCfer_sequence) """ -function prufer_encode(G::Graph)::Array{Integer,1} - - !is_tree(g) && throw(ArgumentError("The graph must be a tree. ")) +function prufer_encode(G::Graph{T})::Array{T,1} where {T<:Integer} + n = nv(G) + (!is_tree(G) || n <= 2) && + throw(ArgumentError("The graph must be a tree with n ⩾ 3 vertices. ")) g = copy(G) - n = nv(g) - code = zeros(Int, n-2) + code = zeros(Int, n - 2) d = degree(g) - L = BinaryMaxHeap(findall(==(1),d)) + L = BinaryMinHeap(findall(==(1), d)) - for i in 1:n-2 + for i in 1:(n - 2) l = pop!(L) - v = neighbors(g,l)[1] - rem_edge!(g,l,v) + v = neighbors(g, l)[1] + rem_edge!(g, l, v) d[l] -= 1 d[v] -= 1 - d[v] == 1 && push!(L,v) + d[v] == 1 && push!(L, v) code[i] = v end - return code \ No newline at end of file + return code +end \ No newline at end of file diff --git a/test/prufer/prufer.jl b/test/prufer/prufer.jl index ac53282c0..76331e007 100644 --- a/test/prufer/prufer.jl +++ b/test/prufer/prufer.jl @@ -1,3 +1,15 @@ -@testset "Prufer" begin - @test true == true +@testset "Prufer trees" begin + g1 = Graph(6) + for e in [(1, 4), (2, 4), (3, 4), (4, 5), (5, 6)] + add_edge!(g1) + end + + g2 = path_graph(10) + + for g in testgraphs(g1) + @test is_tree(g) + code = prufer_encode(g) + @test code = [4, 4, 4, 5] + @test prufer_decode(code) == g + end end \ No newline at end of file From 8cb40694536c13ad9020888940ea7b52a15267be Mon Sep 17 00:00:00 2001 From: Simon Date: Sat, 31 Dec 2022 15:21:43 +0100 Subject: [PATCH 05/28] fixed types --- src/prufer/prufer_coding.jl | 36 ++++++++++++++++-------------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/src/prufer/prufer_coding.jl b/src/prufer/prufer_coding.jl index f12c32e55..9d8ad3e16 100644 --- a/src/prufer/prufer_coding.jl +++ b/src/prufer/prufer_coding.jl @@ -1,32 +1,28 @@ """ is_tree(g) -Returns true if g is a simple tree: that is, a simple connected graph, with nv-1 edges (nv = number of vertices). Trees are the minimal connected graphs; equivalently they have no cycles. +Returns true if g is a tree: that is, a simple, connected undirected graph, with nv-1 edges (nv = number of vertices). Trees are the minimal connected graphs; equivalently they have no cycles. + +This function does not apply to directed graphs. Directed trees are sometimes called [polytrees](https://en.wikipedia.org/wiki/Polytree)). -This is only for undirected graphs. """ -function is_tree(g::SimpleGraph) +function is_tree(g::AbstractGraph) + is_directed(g) && throw(ArgumentError("g must be undirected. ")) return is_connected(g) && ne(g) == nv(g) - 1 end function _is_prufer(c) - ℓ = length(c) - return ndims(c) == 1 && maximum(c) <= ℓ + 2 && ℓ >= 1 + return ndims(c) == 1 && !isempty(c) && maximum(c) <= length(c) + 2 end -function _degree_from_prufer(c::Vector{T}) where {T<:Integer} +function _degree_from_prufer(c::Vector{T})::Vector{T} where {T<:Integer} """ Degree sequence from prufer code. Returns d such that d[i] = 1 + number of occurences of i in c """ n = length(c) + 2 - return [count(==(i), c) for i in 1:n] .+ 1 -end - -function test_degree_sequence() - a = [1, 2, 3, 4, 4, 4] # 8 nodes - return _prufer_degree_sequence(a) == [2, 2, 2, 4, 1, 1, 1, 1] + return [T(count(==(i), c) + 1) for i in 1:n] end """ @@ -38,15 +34,15 @@ Ref: [Prüfer sequence on Wikipedia](https://en.wikipedia.org/wiki/Pr%C3%BCfer_s """ function prufer_decode(code::Array{T,1})::Graph{T} where {T<:Integer} - n = length(code) + 2 !_is_prufer(code) && throw( ArgumentError( "The code must be an Array{T,1} and must be a Prufer sequence with length ⩾1. ", ), ) + n = length(code) + 2 d = _degree_from_prufer(code) - L = BinaryMinHeap(findall(==(1), d)) - g = Graph(n, 0) + L = BinaryMinHeap{T}(findall(==(1), d)) + g = Graph{T}(n, 0) for i in 1:(n - 2) l = pop!(L) # extract leaf with priority rule (max) @@ -62,11 +58,11 @@ function prufer_decode(code::Array{T,1})::Graph{T} where {T<:Integer} end """ - prufer_encode(g) + prufer_encode(g::SimpleGraph) -Given a tree (a connected minimal undirected graph) of size n⩾2, returns the unique Prüfer sequence associated with this tree. +Given a tree (a connected minimal undirected graph) of size n⩾3, returns the unique Prüfer sequence associated with this tree. -Each tree of size n ⩾ 2 is associated with a Prüfer sequence (a[1], ..., a[n-2]) with 1 ⩽ a[i] ⩽ n. The sequence is constructed recursively by the leaf removal algoritm. At step k, the leaf with smallest index is removed and its unique neighbor is added to the Prüfer sequence, giving a[k]. +Each tree of size n ⩾ 3 is associated with a Prüfer sequence (a[1], ..., a[n-2]) with 1 ⩽ a[i] ⩽ n. The sequence is constructed recursively by the leaf removal algoritm. At step k, the leaf with smallest index is removed and its unique neighbor is added to the Prüfer sequence, giving a[k]. Ref: [Prüfer sequence on Wikipedia](https://en.wikipedia.org/wiki/Pr%C3%BCfer_sequence) @@ -77,7 +73,7 @@ function prufer_encode(G::Graph{T})::Array{T,1} where {T<:Integer} (!is_tree(G) || n <= 2) && throw(ArgumentError("The graph must be a tree with n ⩾ 3 vertices. ")) g = copy(G) - code = zeros(Int, n - 2) + code = zeros(T, n - 2) d = degree(g) L = BinaryMinHeap(findall(==(1), d)) @@ -92,4 +88,4 @@ function prufer_encode(G::Graph{T})::Array{T,1} where {T<:Integer} end return code -end \ No newline at end of file +end From 887f8e073f4cf33768a6f38928d78870a289e3bd Mon Sep 17 00:00:00 2001 From: Simon Date: Sat, 31 Dec 2022 15:21:50 +0100 Subject: [PATCH 06/28] prufer tests --- test/prufer/prufer.jl | 64 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 55 insertions(+), 9 deletions(-) diff --git a/test/prufer/prufer.jl b/test/prufer/prufer.jl index 76331e007..380fdf043 100644 --- a/test/prufer/prufer.jl +++ b/test/prufer/prufer.jl @@ -1,15 +1,61 @@ +function _harmonize_type(g::AbstractGraph{T}, c::Vector{S}) where {T,S<:Integer} + return convert(Vector{T}, c) +end + @testset "Prufer trees" begin - g1 = Graph(6) + t1 = Graph(6) for e in [(1, 4), (2, 4), (3, 4), (4, 5), (5, 6)] - add_edge!(g1) + add_edge!(t1, e...) end + code = [4, 4, 4, 5] + + t2 = path_graph(2) + t3 = star_graph(10) + t4 = binary_tree(3) + + g1 = cycle_graph(8) + g2 = complete_graph(4) + g3 = Graph(5) + + d1 = cycle_digraph(5) + d2 = path_digraph(5) + d3 = DiGraph(2) + add_edge!(d3, 1, 2) + add_edge!(d3, 2, 1) + + @testset "tree_check" begin + for t in testgraphs(t1, t2, t3) + @test is_tree(t) + end + for g in testgraphs(g1, g2, g3) + @test !is_tree(g) + end + for g in testgraphs(d1, d2, d3) + @test_throws ArgumentError is_tree(g) + end + end + + @testset "encode/decode" begin + @test prufer_decode(code) == t1 + @test prufer_encode(t1) == code + for g in testgraphs(t3, t4) + ret_code = prufer_encode(g) + @test prufer_decode(ret_code) == g + end + end + + @testset "errors" begin + b1 = [5, 8, 10, 1, 2] + b2 = Vector{Int}() + @test_throws ArgumentError prufer_decode(b1) + @test_throws ArgumentError prufer_decode(b2) - g2 = path_graph(10) + for g in testgraphs(g1, g2, g3) + @test_throws ArgumentError prufer_encode(g) + end - for g in testgraphs(g1) - @test is_tree(g) - code = prufer_encode(g) - @test code = [4, 4, 4, 5] - @test prufer_decode(code) == g + for g in testgraphs(d1, d2, d3) + @test_throws MethodError prufer_encode(g) + end end -end \ No newline at end of file +end From 10781437b2b840c306b21a8235499f1c5b87dce4 Mon Sep 17 00:00:00 2001 From: Simon Date: Sat, 31 Dec 2022 18:24:39 +0100 Subject: [PATCH 07/28] renaming prufer to trees --- src/Graphs.jl | 4 ++-- src/{prufer/prufer_coding.jl => trees/prufer.jl} | 0 test/runtests.jl | 1 + test/{prufer => trees}/prufer.jl | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) rename src/{prufer/prufer_coding.jl => trees/prufer.jl} (100%) rename test/{prufer => trees}/prufer.jl (97%) diff --git a/src/Graphs.jl b/src/Graphs.jl index c85f8f52b..91ac4703e 100644 --- a/src/Graphs.jl +++ b/src/Graphs.jl @@ -394,7 +394,7 @@ export kruskal_mst, prim_mst, - # prufer coding + # trees and prufer is_tree, prufer_encode, prufer_decode, @@ -521,7 +521,7 @@ include("community/rich_club.jl") include("spanningtrees/boruvka.jl") include("spanningtrees/kruskal.jl") include("spanningtrees/prim.jl") -include("prufer/prufer_coding.jl") +include("trees/prufer.jl") include("steinertree/steiner_tree.jl") include("biconnectivity/articulation.jl") include("biconnectivity/biconnect.jl") diff --git a/src/prufer/prufer_coding.jl b/src/trees/prufer.jl similarity index 100% rename from src/prufer/prufer_coding.jl rename to src/trees/prufer.jl diff --git a/test/runtests.jl b/test/runtests.jl index 786bdb949..de706154c 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -116,6 +116,7 @@ tests = [ "independentset/maximal_ind_set", "vertexcover/degree_vertex_cover", "vertexcover/random_vertex_cover", + "trees/prufer", "experimental/experimental", ] diff --git a/test/prufer/prufer.jl b/test/trees/prufer.jl similarity index 97% rename from test/prufer/prufer.jl rename to test/trees/prufer.jl index 380fdf043..f0cd826b7 100644 --- a/test/prufer/prufer.jl +++ b/test/trees/prufer.jl @@ -2,7 +2,7 @@ function _harmonize_type(g::AbstractGraph{T}, c::Vector{S}) where {T,S<:Integer} return convert(Vector{T}, c) end -@testset "Prufer trees" begin +@testset "Tree utilities" begin t1 = Graph(6) for e in [(1, 4), (2, 4), (3, 4), (4, 5), (5, 6)] add_edge!(t1, e...) From 56c00f6a11c7c628515aafed6e9c4983f8e16033 Mon Sep 17 00:00:00 2001 From: Simon Date: Sat, 31 Dec 2022 18:24:48 +0100 Subject: [PATCH 08/28] added docs --- docs/make.jl | 1 + docs/src/algorithms/cycles.md | 2 +- docs/src/algorithms/trees.md | 19 +++++++++++++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 docs/src/algorithms/trees.md diff --git a/docs/make.jl b/docs/make.jl index 7e40b3a68..d7f62b704 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -52,6 +52,7 @@ pages_files = [ "algorithms/connectivity.md", "algorithms/cut.md", "algorithms/cycles.md", + "algorithms/trees.md", "algorithms/degeneracy.md", "algorithms/digraph.md", "algorithms/distance.md", diff --git a/docs/src/algorithms/cycles.md b/docs/src/algorithms/cycles.md index 274eb6875..05269a7ae 100644 --- a/docs/src/algorithms/cycles.md +++ b/docs/src/algorithms/cycles.md @@ -1,6 +1,6 @@ # Cycles -_Graphs.jl_ contains numerous algorithms related to [cycles](https://en.wikipedia.org/wiki/Cycle_(graph_theory)). +_Graphs.jl_ contains XXXX numerous algorithms related to [cycles](https://en.wikipedia.org/wiki/Cycle_(graph_theory)). ## Index diff --git a/docs/src/algorithms/trees.md b/docs/src/algorithms/trees.md new file mode 100644 index 000000000..2e1779bda --- /dev/null +++ b/docs/src/algorithms/trees.md @@ -0,0 +1,19 @@ +# Trees + +_Graphs.jl_ algorithms related to [trees](https://en.wikipedia.org/wiki/Tree_(graph_theory)). + +## Index + +```@index +Pages = ["trees.md"] +``` + +## Full docs + +```@autodocs +Modules = [Graphs] +Pages = [ + "trees/prufer.jl", +] + +``` From 98fdf290d384e89e0bfd420696ddbbbc72969b38 Mon Sep 17 00:00:00 2001 From: Simon Date: Sun, 1 Jan 2023 18:10:03 +0100 Subject: [PATCH 09/28] @traitfn for is_tree --- src/trees/prufer.jl | 7 ++++--- test/trees/prufer.jl | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/trees/prufer.jl b/src/trees/prufer.jl index 9d8ad3e16..8fe93067f 100644 --- a/src/trees/prufer.jl +++ b/src/trees/prufer.jl @@ -7,9 +7,10 @@ This function does not apply to directed graphs. Directed trees are sometimes ca """ -function is_tree(g::AbstractGraph) - is_directed(g) && throw(ArgumentError("g must be undirected. ")) - return is_connected(g) && ne(g) == nv(g) - 1 +function is_tree end + +@traitfn function is_tree(g::::(!IsDirected)) + return ne(g) == nv(g) - 1 && is_connected(g) end function _is_prufer(c) diff --git a/test/trees/prufer.jl b/test/trees/prufer.jl index f0cd826b7..d20469b0a 100644 --- a/test/trees/prufer.jl +++ b/test/trees/prufer.jl @@ -31,7 +31,7 @@ end @test !is_tree(g) end for g in testgraphs(d1, d2, d3) - @test_throws ArgumentError is_tree(g) + @test_throws MethodError is_tree(g) end end From 6f1eb44377f64490f7ed702152ae6104891134b7 Mon Sep 17 00:00:00 2001 From: SimonCoste Date: Sun, 1 Jan 2023 18:17:30 +0100 Subject: [PATCH 10/28] Update src/trees/prufer.jl MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Simon Schölly --- src/trees/prufer.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/trees/prufer.jl b/src/trees/prufer.jl index 8fe93067f..8e4e3fc08 100644 --- a/src/trees/prufer.jl +++ b/src/trees/prufer.jl @@ -34,7 +34,7 @@ Each tree of size n is associated with a Prüfer sequence (a[1], ..., a[n-2]) wi Ref: [Prüfer sequence on Wikipedia](https://en.wikipedia.org/wiki/Pr%C3%BCfer_sequence) """ -function prufer_decode(code::Array{T,1})::Graph{T} where {T<:Integer} +function prufer_decode(code::AbstractVector{T})::SimpleGraph{T} where {T<:Integer} !_is_prufer(code) && throw( ArgumentError( "The code must be an Array{T,1} and must be a Prufer sequence with length ⩾1. ", From 1cf22a8c238fd9493db8a15e514615ebd9a29378 Mon Sep 17 00:00:00 2001 From: Simon Date: Sun, 1 Jan 2023 19:07:16 +0100 Subject: [PATCH 11/28] better input types --- src/trees/prufer.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/trees/prufer.jl b/src/trees/prufer.jl index 8e4e3fc08..e89207e54 100644 --- a/src/trees/prufer.jl +++ b/src/trees/prufer.jl @@ -69,7 +69,7 @@ Ref: [Prüfer sequence on Wikipedia](https://en.wikipedia.org/wiki/Pr%C3%BCfer_s """ -function prufer_encode(G::Graph{T})::Array{T,1} where {T<:Integer} +function prufer_encode(G::SimpleGraph{T})::AbstractVector{T} where {T<:Integer} n = nv(G) (!is_tree(G) || n <= 2) && throw(ArgumentError("The graph must be a tree with n ⩾ 3 vertices. ")) From a8fa5270b9e930035a97ae269c7b263c3b7947f2 Mon Sep 17 00:00:00 2001 From: Simon Date: Sun, 1 Jan 2023 19:30:50 +0100 Subject: [PATCH 12/28] Support for empty sequence / 1-edge tree --- src/trees/prufer.jl | 14 +++++++++----- test/trees/prufer.jl | 6 +++--- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/trees/prufer.jl b/src/trees/prufer.jl index e89207e54..f371c5195 100644 --- a/src/trees/prufer.jl +++ b/src/trees/prufer.jl @@ -14,12 +14,12 @@ function is_tree end end function _is_prufer(c) - return ndims(c) == 1 && !isempty(c) && maximum(c) <= length(c) + 2 + return ndims(c) == 1 && eltype(c)<:Integer && (isempty(c) || maximum(c) <= length(c) + 2) end function _degree_from_prufer(c::Vector{T})::Vector{T} where {T<:Integer} """ - Degree sequence from prufer code. + Degree sequence from Prüfer code. Returns d such that d[i] = 1 + number of occurences of i in c """ n = length(c) + 2 @@ -37,9 +37,11 @@ Ref: [Prüfer sequence on Wikipedia](https://en.wikipedia.org/wiki/Pr%C3%BCfer_s function prufer_decode(code::AbstractVector{T})::SimpleGraph{T} where {T<:Integer} !_is_prufer(code) && throw( ArgumentError( - "The code must be an Array{T,1} and must be a Prufer sequence with length ⩾1. ", + "The code must have one dimension and must be a Prüfer sequence. ", ), ) + isempty(code) && return path_graph(T(2)) # the empty Prüfer sequence codes for the one-edge tree + n = length(code) + 2 d = _degree_from_prufer(code) L = BinaryMinHeap{T}(findall(==(1), d)) @@ -70,9 +72,11 @@ Ref: [Prüfer sequence on Wikipedia](https://en.wikipedia.org/wiki/Pr%C3%BCfer_s """ function prufer_encode(G::SimpleGraph{T})::AbstractVector{T} where {T<:Integer} + !is_tree(G) && + throw(ArgumentError("The graph must be a tree with n ⩾ 2 vertices. ")) + n = nv(G) - (!is_tree(G) || n <= 2) && - throw(ArgumentError("The graph must be a tree with n ⩾ 3 vertices. ")) + n==0 && return Vector{T}() # empty Prüfer sequence g = copy(G) code = zeros(T, n - 2) d = degree(g) diff --git a/test/trees/prufer.jl b/test/trees/prufer.jl index d20469b0a..3ecb819e1 100644 --- a/test/trees/prufer.jl +++ b/test/trees/prufer.jl @@ -38,7 +38,7 @@ end @testset "encode/decode" begin @test prufer_decode(code) == t1 @test prufer_encode(t1) == code - for g in testgraphs(t3, t4) + for g in testgraphs(t2, t3, t4) ret_code = prufer_encode(g) @test prufer_decode(ret_code) == g end @@ -46,9 +46,9 @@ end @testset "errors" begin b1 = [5, 8, 10, 1, 2] - b2 = Vector{Int}() + b2 = [0.5, 1.1] @test_throws ArgumentError prufer_decode(b1) - @test_throws ArgumentError prufer_decode(b2) + @test_throws MethodError prufer_decode(b2) for g in testgraphs(g1, g2, g3) @test_throws ArgumentError prufer_encode(g) From 18765b5be5179d4f06cc0ac76a3dbd4024094dca Mon Sep 17 00:00:00 2001 From: Simon Date: Sun, 1 Jan 2023 19:36:39 +0100 Subject: [PATCH 13/28] update doc for the 1-edge tree --- src/trees/prufer.jl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/trees/prufer.jl b/src/trees/prufer.jl index f371c5195..d237be32c 100644 --- a/src/trees/prufer.jl +++ b/src/trees/prufer.jl @@ -30,8 +30,9 @@ end prufer_decode(code) Returns the unique tree associated with the given (Prüfer) code. -Each tree of size n is associated with a Prüfer sequence (a[1], ..., a[n-2]) with 1 ⩽ a[i] ⩽ n. The sequence is constructed recursively by the leaf removal algoritm. At step k, the leaf with smallest index is removed and its unique neighbor is added to the Prüfer sequence, giving a[k]. The decoding algorithm goes backward. +Each tree of size n is associated with a Prüfer sequence (a[1], ..., a[n-2]) with 1 ⩽ a[i] ⩽ n. The sequence is constructed recursively by the leaf removal algoritm. At step k, the leaf with smallest index is removed and its unique neighbor is added to the Prüfer sequence, giving a[k]. The decoding algorithm goes backward. The tree associated with the empty sequence is the 2-vertices tree with one edge. Ref: [Prüfer sequence on Wikipedia](https://en.wikipedia.org/wiki/Pr%C3%BCfer_sequence) + """ function prufer_decode(code::AbstractVector{T})::SimpleGraph{T} where {T<:Integer} @@ -65,7 +66,7 @@ end Given a tree (a connected minimal undirected graph) of size n⩾3, returns the unique Prüfer sequence associated with this tree. -Each tree of size n ⩾ 3 is associated with a Prüfer sequence (a[1], ..., a[n-2]) with 1 ⩽ a[i] ⩽ n. The sequence is constructed recursively by the leaf removal algoritm. At step k, the leaf with smallest index is removed and its unique neighbor is added to the Prüfer sequence, giving a[k]. +Each tree of size n ⩾ 2 is associated with a Prüfer sequence (a[1], ..., a[n-2]) with 1 ⩽ a[i] ⩽ n. The sequence is constructed recursively by the leaf removal algoritm. At step k, the leaf with smallest index is removed and its unique neighbor is added to the Prüfer sequence, giving a[k]. The Prüfer sequence of the tree with only one edge is the empty sequence. Ref: [Prüfer sequence on Wikipedia](https://en.wikipedia.org/wiki/Pr%C3%BCfer_sequence) From 3c88fd37ecff23ae6745b89ee981f21a8aa97fe8 Mon Sep 17 00:00:00 2001 From: Simon Date: Mon, 2 Jan 2023 17:10:29 +0100 Subject: [PATCH 14/28] formatting --- src/trees/prufer.jl | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/trees/prufer.jl b/src/trees/prufer.jl index d237be32c..602755f02 100644 --- a/src/trees/prufer.jl +++ b/src/trees/prufer.jl @@ -14,7 +14,9 @@ function is_tree end end function _is_prufer(c) - return ndims(c) == 1 && eltype(c)<:Integer && (isempty(c) || maximum(c) <= length(c) + 2) + return ndims(c) == 1 && + eltype(c) <: Integer && + (isempty(c) || maximum(c) <= length(c) + 2) end function _degree_from_prufer(c::Vector{T})::Vector{T} where {T<:Integer} @@ -37,9 +39,7 @@ Ref: [Prüfer sequence on Wikipedia](https://en.wikipedia.org/wiki/Pr%C3%BCfer_s function prufer_decode(code::AbstractVector{T})::SimpleGraph{T} where {T<:Integer} !_is_prufer(code) && throw( - ArgumentError( - "The code must have one dimension and must be a Prüfer sequence. ", - ), + ArgumentError("The code must have one dimension and must be a Prüfer sequence. "), ) isempty(code) && return path_graph(T(2)) # the empty Prüfer sequence codes for the one-edge tree @@ -73,11 +73,10 @@ Ref: [Prüfer sequence on Wikipedia](https://en.wikipedia.org/wiki/Pr%C3%BCfer_s """ function prufer_encode(G::SimpleGraph{T})::AbstractVector{T} where {T<:Integer} - !is_tree(G) && - throw(ArgumentError("The graph must be a tree with n ⩾ 2 vertices. ")) + !is_tree(G) && throw(ArgumentError("The graph must be a tree with n ⩾ 2 vertices. ")) n = nv(G) - n==0 && return Vector{T}() # empty Prüfer sequence + n == 0 && return Vector{T}() # empty Prüfer sequence g = copy(G) code = zeros(T, n - 2) d = degree(g) From b048c2f284a4d1a9eca1c152f523cfd3b6dcbd0d Mon Sep 17 00:00:00 2001 From: SimonCoste Date: Mon, 2 Jan 2023 21:15:03 +0100 Subject: [PATCH 15/28] fix bug for 2-nodes trees MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Simon Schölly --- src/trees/prufer.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/trees/prufer.jl b/src/trees/prufer.jl index 602755f02..ad1400309 100644 --- a/src/trees/prufer.jl +++ b/src/trees/prufer.jl @@ -73,10 +73,10 @@ Ref: [Prüfer sequence on Wikipedia](https://en.wikipedia.org/wiki/Pr%C3%BCfer_s """ function prufer_encode(G::SimpleGraph{T})::AbstractVector{T} where {T<:Integer} - !is_tree(G) && throw(ArgumentError("The graph must be a tree with n ⩾ 2 vertices. ")) + (nv(G) < 2 || !is_tree(G)) && throw(ArgumentError("The graph must be a tree with n ⩾ 2 vertices. ")) n = nv(G) - n == 0 && return Vector{T}() # empty Prüfer sequence + n == 2 && return Vector{T}() # empty Prüfer sequence g = copy(G) code = zeros(T, n - 2) d = degree(g) From 9e44c46f7b89839edbe46f286768421d57efc0a9 Mon Sep 17 00:00:00 2001 From: SimonCoste Date: Mon, 2 Jan 2023 21:15:31 +0100 Subject: [PATCH 16/28] strengthen return types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Simon Schölly --- src/trees/prufer.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/trees/prufer.jl b/src/trees/prufer.jl index ad1400309..4781ea0e6 100644 --- a/src/trees/prufer.jl +++ b/src/trees/prufer.jl @@ -72,7 +72,7 @@ Ref: [Prüfer sequence on Wikipedia](https://en.wikipedia.org/wiki/Pr%C3%BCfer_s """ -function prufer_encode(G::SimpleGraph{T})::AbstractVector{T} where {T<:Integer} +function prufer_encode(G::SimpleGraph{T})::Vector{T} where {T<:Integer} (nv(G) < 2 || !is_tree(G)) && throw(ArgumentError("The graph must be a tree with n ⩾ 2 vertices. ")) n = nv(G) From f1628bb9b9eeca507750fad4da3a4d7f785bac25 Mon Sep 17 00:00:00 2001 From: SimonCoste Date: Mon, 2 Jan 2023 21:16:10 +0100 Subject: [PATCH 17/28] convention for naming vertices MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Simon Schölly --- src/trees/prufer.jl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/trees/prufer.jl b/src/trees/prufer.jl index 4781ea0e6..bf0d00ed1 100644 --- a/src/trees/prufer.jl +++ b/src/trees/prufer.jl @@ -83,10 +83,10 @@ function prufer_encode(G::SimpleGraph{T})::Vector{T} where {T<:Integer} L = BinaryMinHeap(findall(==(1), d)) for i in 1:(n - 2) - l = pop!(L) - v = neighbors(g, l)[1] - rem_edge!(g, l, v) - d[l] -= 1 + u = pop!(L) + v = neighbors(g, u)[1] + rem_edge!(g, u, v) + d[u] -= 1 d[v] -= 1 d[v] == 1 && push!(L, v) code[i] = v From e81f6b7867017c80dae4cb37523b72f209915d3b Mon Sep 17 00:00:00 2001 From: SimonCoste Date: Mon, 2 Jan 2023 21:18:12 +0100 Subject: [PATCH 18/28] Initialize empty graphs with zero edges MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Simon Schölly --- src/trees/prufer.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/trees/prufer.jl b/src/trees/prufer.jl index bf0d00ed1..bb49ece7c 100644 --- a/src/trees/prufer.jl +++ b/src/trees/prufer.jl @@ -46,7 +46,7 @@ function prufer_decode(code::AbstractVector{T})::SimpleGraph{T} where {T<:Intege n = length(code) + 2 d = _degree_from_prufer(code) L = BinaryMinHeap{T}(findall(==(1), d)) - g = Graph{T}(n, 0) + g = Graph{T}(n) for i in 1:(n - 2) l = pop!(L) # extract leaf with priority rule (max) From fe6303eaab01078d0c45bcada5521a3dc78ec861 Mon Sep 17 00:00:00 2001 From: Simon Coste Date: Wed, 4 Jan 2023 14:13:24 +0100 Subject: [PATCH 19/28] various small improvements --- src/trees/prufer.jl | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/trees/prufer.jl b/src/trees/prufer.jl index bb49ece7c..e453fb6f6 100644 --- a/src/trees/prufer.jl +++ b/src/trees/prufer.jl @@ -6,17 +6,14 @@ Returns true if g is a tree: that is, a simple, connected undirected graph, wit This function does not apply to directed graphs. Directed trees are sometimes called [polytrees](https://en.wikipedia.org/wiki/Polytree)). """ - function is_tree end @traitfn function is_tree(g::::(!IsDirected)) return ne(g) == nv(g) - 1 && is_connected(g) end -function _is_prufer(c) - return ndims(c) == 1 && - eltype(c) <: Integer && - (isempty(c) || maximum(c) <= length(c) + 2) +function _is_prufer(c::AbstractVector{T}) where {T<:Integer} + return isempty(c) || (minimum(c) >= 1 && maximum(c) <= length(c) + 2) end function _degree_from_prufer(c::Vector{T})::Vector{T} where {T<:Integer} @@ -25,7 +22,11 @@ function _degree_from_prufer(c::Vector{T})::Vector{T} where {T<:Integer} Returns d such that d[i] = 1 + number of occurences of i in c """ n = length(c) + 2 - return [T(count(==(i), c) + 1) for i in 1:n] + d = ones(T, n) + for value in c + d[value] += 1 + end + return d end """ @@ -46,7 +47,7 @@ function prufer_decode(code::AbstractVector{T})::SimpleGraph{T} where {T<:Intege n = length(code) + 2 d = _degree_from_prufer(code) L = BinaryMinHeap{T}(findall(==(1), d)) - g = Graph{T}(n) + g = Graph{T}(n, 0) for i in 1:(n - 2) l = pop!(L) # extract leaf with priority rule (max) @@ -73,7 +74,8 @@ Ref: [Prüfer sequence on Wikipedia](https://en.wikipedia.org/wiki/Pr%C3%BCfer_s """ function prufer_encode(G::SimpleGraph{T})::Vector{T} where {T<:Integer} - (nv(G) < 2 || !is_tree(G)) && throw(ArgumentError("The graph must be a tree with n ⩾ 2 vertices. ")) + (nv(G) < 2 || !is_tree(G)) && + throw(ArgumentError("The graph must be a tree with n ⩾ 2 vertices. ")) n = nv(G) n == 2 && return Vector{T}() # empty Prüfer sequence From 27f497f2e9e524b3884fc4f9b4ec33fd5900afd6 Mon Sep 17 00:00:00 2001 From: Simon Coste Date: Wed, 4 Jan 2023 14:13:48 +0100 Subject: [PATCH 20/28] added edge tests (forest, empty trees, self-loops) --- test/trees/prufer.jl | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/test/trees/prufer.jl b/test/trees/prufer.jl index 3ecb819e1..05c3b63f6 100644 --- a/test/trees/prufer.jl +++ b/test/trees/prufer.jl @@ -12,10 +12,18 @@ end t2 = path_graph(2) t3 = star_graph(10) t4 = binary_tree(3) + + f1 = Graph(8,0) #forest with 4 tree components + for e in [(1,2),(2,3),(4,5),(6,7)] + add_edge!(f1,e...) + end g1 = cycle_graph(8) g2 = complete_graph(4) - g3 = Graph(5) + g3 = Graph(1,0) + g4 = Graph(2,0) + g5 = Graph(1,0) + add_edge!(g5,1,1) # loop d1 = cycle_digraph(5) d2 = path_digraph(5) @@ -24,10 +32,10 @@ end add_edge!(d3, 2, 1) @testset "tree_check" begin - for t in testgraphs(t1, t2, t3) + for t in testgraphs(t1, t2, t3, t4, g3) @test is_tree(t) end - for g in testgraphs(g1, g2, g3) + for g in testgraphs(g1, g2, g4, g5, f1) @test !is_tree(g) end for g in testgraphs(d1, d2, d3) @@ -50,7 +58,7 @@ end @test_throws ArgumentError prufer_decode(b1) @test_throws MethodError prufer_decode(b2) - for g in testgraphs(g1, g2, g3) + for g in testgraphs(g1, g2, g3, g4, g5, f1) @test_throws ArgumentError prufer_encode(g) end From fc93e6fceb4a4ead7a6e47f61cdd358b14854444 Mon Sep 17 00:00:00 2001 From: Simon Coste Date: Wed, 4 Jan 2023 14:43:47 +0100 Subject: [PATCH 21/28] uniform tree generator --- src/Graphs.jl | 1 + src/SimpleGraphs/SimpleGraphs.jl | 4 +++- src/SimpleGraphs/generators/randgraphs.jl | 27 +++++++++++++++++++++++ 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/src/Graphs.jl b/src/Graphs.jl index 91ac4703e..7e42715c1 100644 --- a/src/Graphs.jl +++ b/src/Graphs.jl @@ -282,6 +282,7 @@ export watts_strogatz, newman_watts_strogatz, random_regular_graph, + uniform_tree, random_regular_digraph, random_configuration_model, random_tournament_digraph, diff --git a/src/SimpleGraphs/SimpleGraphs.jl b/src/SimpleGraphs/SimpleGraphs.jl index d60d56436..bcb7c0c97 100644 --- a/src/SimpleGraphs/SimpleGraphs.jl +++ b/src/SimpleGraphs/SimpleGraphs.jl @@ -36,7 +36,8 @@ import Graphs: num_self_loops, insorted, squash, - rng_from_rng_or_seed + rng_from_rng_or_seed, + prufer_decode export AbstractSimpleGraph, AbstractSimpleEdge, @@ -61,6 +62,7 @@ export AbstractSimpleGraph, random_regular_graph, random_regular_digraph, random_configuration_model, + uniform_tree, random_tournament_digraph, StochasticBlockModel, make_edgestream, diff --git a/src/SimpleGraphs/generators/randgraphs.jl b/src/SimpleGraphs/generators/randgraphs.jl index efc122a87..4bef42ed2 100644 --- a/src/SimpleGraphs/generators/randgraphs.jl +++ b/src/SimpleGraphs/generators/randgraphs.jl @@ -967,6 +967,33 @@ function random_configuration_model( end return g end +""" + uniform_tree(n) + +Generates a random labelled tree, drawn uniformly at random over the n^{n-2} such trees. A uniform word of length n-2 over the alphabet 1:n is generated (Prüfer sequence) then decoded. See also `prufer_decode` and [prufer sequence](https://en.wikipedia.org/wiki/Pr%C3%BCfer_sequence). + +### Optional Arguments +- `rng=nothing`: set the Random Number Generator. +- `seed=nothing`: set the RNG seed. + +# Examples +```jldoctest +julia> uniform_tree(10) +{10, 9} undirected simple Int64 graph +``` +""" +function uniform_tree( + n::Integer; + rng::Union{Nothing,AbstractRNG}=nothing, + seed::Union{Nothing,Integer}=nothing, +) + n == 1 && return Graph(1, 0) + n == 2 && return path_graph(2) + + rng = rng_from_rng_or_seed(rng, seed) + random_code = rand(rng, 1:n, n - 2) + return prufer_decode(random_code) +end """ random_regular_digraph(n, k) From 5d1a1fb9d06a20820ad5af037e0e7bff84fbbf34 Mon Sep 17 00:00:00 2001 From: Simon Coste Date: Wed, 4 Jan 2023 14:43:57 +0100 Subject: [PATCH 22/28] tests for uniform trees --- test/simplegraphs/generators/randgraphs.jl | 12 ++++++++++++ test/trees/prufer.jl | 16 ++++++++-------- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/test/simplegraphs/generators/randgraphs.jl b/test/simplegraphs/generators/randgraphs.jl index 0932a6775..8004b7cc8 100644 --- a/test/simplegraphs/generators/randgraphs.jl +++ b/test/simplegraphs/generators/randgraphs.jl @@ -245,6 +245,18 @@ @test is_directed(rd) end + @testset "uniform trees" begin + t = uniform_tree(50; rng=rng) + @test nv(t) == 50 + @test ne(t) == 49 + @test is_tree(t) + + t2 = uniform_tree(50; rng=StableRNG(4)) + @test nv(t2) == 50 + @test ne(t2) == 49 + @test is_tree(t2) + end + @testset "random configuration model" begin rr = random_configuration_model(10, repeat([2, 4], 5); rng=StableRNG(3)) @test nv(rr) == 10 diff --git a/test/trees/prufer.jl b/test/trees/prufer.jl index 05c3b63f6..b39d9de8e 100644 --- a/test/trees/prufer.jl +++ b/test/trees/prufer.jl @@ -12,18 +12,18 @@ end t2 = path_graph(2) t3 = star_graph(10) t4 = binary_tree(3) - - f1 = Graph(8,0) #forest with 4 tree components - for e in [(1,2),(2,3),(4,5),(6,7)] - add_edge!(f1,e...) + + f1 = Graph(8, 0) #forest with 4 tree components + for e in [(1, 2), (2, 3), (4, 5), (6, 7)] + add_edge!(f1, e...) end g1 = cycle_graph(8) g2 = complete_graph(4) - g3 = Graph(1,0) - g4 = Graph(2,0) - g5 = Graph(1,0) - add_edge!(g5,1,1) # loop + g3 = Graph(1, 0) + g4 = Graph(2, 0) + g5 = Graph(1, 0) + add_edge!(g5, 1, 1) # loop d1 = cycle_digraph(5) d2 = path_digraph(5) From 4a2d91679edbe12dee9ba89bda8a7053ccd49c48 Mon Sep 17 00:00:00 2001 From: Simon Coste Date: Wed, 4 Jan 2023 14:44:22 +0100 Subject: [PATCH 23/28] revert some silly code --- docs/src/algorithms/cycles.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/algorithms/cycles.md b/docs/src/algorithms/cycles.md index 05269a7ae..cdc0c43be 100644 --- a/docs/src/algorithms/cycles.md +++ b/docs/src/algorithms/cycles.md @@ -1,6 +1,6 @@ # Cycles -_Graphs.jl_ contains XXXX numerous algorithms related to [cycles](https://en.wikipedia.org/wiki/Cycle_(graph_theory)). +_Graphs.jl_ contains numerous algorithms related to [cycles](https://en.wikipedia.org/wiki/Cycle_(graph_theory)). ## Index From be8be2c759f6b52d211393d9db74d8aeced40bc2 Mon Sep 17 00:00:00 2001 From: Simon Coste Date: Wed, 4 Jan 2023 15:00:33 +0100 Subject: [PATCH 24/28] fixed typos in docs --- src/SimpleGraphs/generators/randgraphs.jl | 2 +- src/trees/prufer.jl | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/SimpleGraphs/generators/randgraphs.jl b/src/SimpleGraphs/generators/randgraphs.jl index 4bef42ed2..16a56f8e0 100644 --- a/src/SimpleGraphs/generators/randgraphs.jl +++ b/src/SimpleGraphs/generators/randgraphs.jl @@ -970,7 +970,7 @@ end """ uniform_tree(n) -Generates a random labelled tree, drawn uniformly at random over the n^{n-2} such trees. A uniform word of length n-2 over the alphabet 1:n is generated (Prüfer sequence) then decoded. See also `prufer_decode` and [prufer sequence](https://en.wikipedia.org/wiki/Pr%C3%BCfer_sequence). +Generates a random labelled tree, drawn uniformly at random over the ``n^{n-2}`` such trees. A uniform word of length `n-2` over the alphabet `1:n` is generated (Prüfer sequence) then decoded. See also the `prufer_decode` function and [this page on Prüfer codes](https://en.wikipedia.org/wiki/Pr%C3%BCfer_sequence). ### Optional Arguments - `rng=nothing`: set the Random Number Generator. diff --git a/src/trees/prufer.jl b/src/trees/prufer.jl index e453fb6f6..e84d7aea1 100644 --- a/src/trees/prufer.jl +++ b/src/trees/prufer.jl @@ -37,7 +37,6 @@ Each tree of size n is associated with a Prüfer sequence (a[1], ..., a[n-2]) wi Ref: [Prüfer sequence on Wikipedia](https://en.wikipedia.org/wiki/Pr%C3%BCfer_sequence) """ - function prufer_decode(code::AbstractVector{T})::SimpleGraph{T} where {T<:Integer} !_is_prufer(code) && throw( ArgumentError("The code must have one dimension and must be a Prüfer sequence. "), @@ -72,7 +71,6 @@ Each tree of size n ⩾ 2 is associated with a Prüfer sequence (a[1], ..., a[n- Ref: [Prüfer sequence on Wikipedia](https://en.wikipedia.org/wiki/Pr%C3%BCfer_sequence) """ - function prufer_encode(G::SimpleGraph{T})::Vector{T} where {T<:Integer} (nv(G) < 2 || !is_tree(G)) && throw(ArgumentError("The graph must be a tree with n ⩾ 2 vertices. ")) @@ -95,4 +93,4 @@ function prufer_encode(G::SimpleGraph{T})::Vector{T} where {T<:Integer} end return code -end +end \ No newline at end of file From d2e6f56544fc99d647d970e7ff738a75b8a57b39 Mon Sep 17 00:00:00 2001 From: Simon Coste Date: Wed, 4 Jan 2023 15:09:42 +0100 Subject: [PATCH 25/28] removed trailing space --- src/trees/prufer.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/trees/prufer.jl b/src/trees/prufer.jl index e84d7aea1..b3046ba2a 100644 --- a/src/trees/prufer.jl +++ b/src/trees/prufer.jl @@ -93,4 +93,4 @@ function prufer_encode(G::SimpleGraph{T})::Vector{T} where {T<:Integer} end return code -end \ No newline at end of file +end From e59a993202f4249eeca9fa177665bfcaf1ddc61e Mon Sep 17 00:00:00 2001 From: SimonCoste Date: Wed, 4 Jan 2023 16:00:33 +0100 Subject: [PATCH 26/28] determine return type from arg in uniform tree MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Simon Schölly --- src/SimpleGraphs/generators/randgraphs.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/SimpleGraphs/generators/randgraphs.jl b/src/SimpleGraphs/generators/randgraphs.jl index 16a56f8e0..1635be2df 100644 --- a/src/SimpleGraphs/generators/randgraphs.jl +++ b/src/SimpleGraphs/generators/randgraphs.jl @@ -987,11 +987,11 @@ function uniform_tree( rng::Union{Nothing,AbstractRNG}=nothing, seed::Union{Nothing,Integer}=nothing, ) - n == 1 && return Graph(1, 0) - n == 2 && return path_graph(2) + n <= 1 && return Graph(n) + n == 2 && return path_graph(n) rng = rng_from_rng_or_seed(rng, seed) - random_code = rand(rng, 1:n, n - 2) + random_code = rand(rng, Base.OneTo(n), n - 2) return prufer_decode(random_code) end From c635042a03c30226447bd3659b8b9647d365e6b3 Mon Sep 17 00:00:00 2001 From: Simon Coste Date: Wed, 4 Jan 2023 16:16:50 +0100 Subject: [PATCH 27/28] removed seed from arg --- src/SimpleGraphs/generators/randgraphs.jl | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/SimpleGraphs/generators/randgraphs.jl b/src/SimpleGraphs/generators/randgraphs.jl index 1635be2df..def940390 100644 --- a/src/SimpleGraphs/generators/randgraphs.jl +++ b/src/SimpleGraphs/generators/randgraphs.jl @@ -974,7 +974,6 @@ Generates a random labelled tree, drawn uniformly at random over the ``n^{n-2}`` ### Optional Arguments - `rng=nothing`: set the Random Number Generator. -- `seed=nothing`: set the RNG seed. # Examples ```jldoctest @@ -982,15 +981,10 @@ julia> uniform_tree(10) {10, 9} undirected simple Int64 graph ``` """ -function uniform_tree( - n::Integer; - rng::Union{Nothing,AbstractRNG}=nothing, - seed::Union{Nothing,Integer}=nothing, -) +function uniform_tree(n::Integer; rng::Union{Nothing,AbstractRNG}=nothing) n <= 1 && return Graph(n) n == 2 && return path_graph(n) - - rng = rng_from_rng_or_seed(rng, seed) + rng = rng_from_rng_or_seed(rng) random_code = rand(rng, Base.OneTo(n), n - 2) return prufer_decode(random_code) end From 426d438a25523cf46de0518105cee369d9b2015a Mon Sep 17 00:00:00 2001 From: Simon Coste Date: Thu, 5 Jan 2023 13:51:18 +0100 Subject: [PATCH 28/28] added missing arg in rng_from_rng_or_seed --- src/SimpleGraphs/generators/randgraphs.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SimpleGraphs/generators/randgraphs.jl b/src/SimpleGraphs/generators/randgraphs.jl index def940390..8fc22b768 100644 --- a/src/SimpleGraphs/generators/randgraphs.jl +++ b/src/SimpleGraphs/generators/randgraphs.jl @@ -984,7 +984,7 @@ julia> uniform_tree(10) function uniform_tree(n::Integer; rng::Union{Nothing,AbstractRNG}=nothing) n <= 1 && return Graph(n) n == 2 && return path_graph(n) - rng = rng_from_rng_or_seed(rng) + rng = rng_from_rng_or_seed(rng, nothing) random_code = rand(rng, Base.OneTo(n), n - 2) return prufer_decode(random_code) end