diff --git a/src/SimpleGraphs/SimpleGraphs.jl b/src/SimpleGraphs/SimpleGraphs.jl index bcb7c0c97..1ac93f7f7 100644 --- a/src/SimpleGraphs/SimpleGraphs.jl +++ b/src/SimpleGraphs/SimpleGraphs.jl @@ -4,6 +4,7 @@ using SparseArrays using LinearAlgebra using Graphs using SimpleTraits +using DataStructures: OrderedDict import Random: AbstractRNG @@ -62,6 +63,8 @@ export AbstractSimpleGraph, random_regular_graph, random_regular_digraph, random_configuration_model, + havel_hakimi_graph, + kleitman_wang_graph, uniform_tree, random_tournament_digraph, StochasticBlockModel, diff --git a/src/SimpleGraphs/generators/randgraphs.jl b/src/SimpleGraphs/generators/randgraphs.jl index 8fc22b768..43e84fde1 100644 --- a/src/SimpleGraphs/generators/randgraphs.jl +++ b/src/SimpleGraphs/generators/randgraphs.jl @@ -989,6 +989,7 @@ function uniform_tree(n::Integer; rng::Union{Nothing,AbstractRNG}=nothing) return prufer_decode(random_code) end + """ random_regular_digraph(n, k) diff --git a/src/SimpleGraphs/generators/staticgraphs.jl b/src/SimpleGraphs/generators/staticgraphs.jl index 64e004877..8c6d3fb6a 100644 --- a/src/SimpleGraphs/generators/staticgraphs.jl +++ b/src/SimpleGraphs/generators/staticgraphs.jl @@ -760,3 +760,161 @@ function lollipop_graph(n1::T, n2::T) where {T<:Integer} add_edge!(g, n1, n1 + 1) return g end + + +""" + havel_hakimi_graph(degree_sequence::AbstractVector{<:Integer}) + +Returns a simple graph with a given finite degree sequence of non-negative integers generated via the Havel-Hakimi algorithm which works as follows: +1. successively connect the node of highest degree to other nodes of highest degree; +2. sort the remaining nodes by degree in decreasing order; +3. repeat the procedure. + +The `eltype` of the returned graph will be `Int64`. + +## References +1. [Hakimi (1962)](https://doi.org/10.1137/0110037); +2. [Wikipedia](https://en.wikipedia.org/wiki/Havel%E2%80%93Hakimi_algorithm). +""" +function havel_hakimi_graph(degree_sequence::AbstractVector{<:Integer}) + return havel_hakimi_graph(Int, degree_sequence) +end + +""" + havel_hakimi_graph(T::Type{<:Integer}, degree_sequence::AbstractVector{<:Integer}) + +Returns a simple graph with a given finite degree sequence of non-negative integers generated via the Havel-Hakimi algorithm which works as follows: +1. successively connect the node of highest degree to other nodes of highest degree; +2. sort the remaining nodes by degree in decreasing order; +3. repeat the procedure. + +The `eltype` of the returned graph will be `T`. + +## References +1. [Hakimi (1962)](https://doi.org/10.1137/0110037); +2. [Wikipedia](https://en.wikipedia.org/wiki/Havel%E2%80%93Hakimi_algorithm). +""" +function havel_hakimi_graph(T::Type{<:Integer}, degree_sequence::AbstractVector{<:Integer}) + # Check whether the degree sequence has only non-negative values + all(degree_sequence .>= 0) || + throw(ArgumentError("The degree sequence must contain non-negative integers only.")) + # Instantiate an empty simple graph + graph = SimpleGraph{T}(length(degree_sequence)) + # Create a (vertex, degree) ordered dictionary + vertices_degrees_dict = OrderedDict( + vertex => degree for (vertex, degree) in enumerate(degree_sequence) + ) + # Havel-Hakimi algorithm + while (any(!=(0), values(vertices_degrees_dict))) + # Sort the new sequence in non-increasing order + vertices_degrees_dict = OrderedDict( + sort(collect(vertices_degrees_dict); by=last, rev=true) + ) + # Remove the first vertex and distribute its stabs + max_vertex, max_degree = popfirst!(vertices_degrees_dict) + # Connect the node of highest degree to other nodes of highest degree + for vertex in Iterators.take(keys(vertices_degrees_dict), max_degree) + add_edge!(graph, max_vertex, vertex) + vertices_degrees_dict[vertex] -= 1 + # Check whether the remaining degree is nonnegative + vertices_degrees_dict[vertex] >= 0 || throw(ErrorException("The degree sequence is not graphical.")) + end + end + # Return the simple graph + return graph +end + + +""" + kleitman_wang_graph(indegree_sequence::AbstractVector{<:Integer},outdegree_sequence::AbstractVector{<:Integer}) + +Returns a simple directed graph with given finite in-degree and out-degree sequences of non-negative integers generated via the Kleitman-Wang algorithm, that works like follows: +1. Sort the indegree-outdegree pairs in lexicographical order; +2. Select a pair that has strictly positive outdegree, say the i-th pairs that has outdegree = b_i; +3. Subtract 1 to the first b_i highest indegrees (the i-th being excluded), and set b_i to 0; +4. Repeat from 1. until all indegree-outdegree pairs are of the form (0.0). + +The `eltype` of the returned graph will be `Int64`. + +## References +- [Wikipedia](https://en.wikipedia.org/wiki/Kleitman%E2%80%93Wang_algorithms) +- [Kleitman and Wang (1973)](https://doi.org/10.1016/0012-365X(73)90037-X) +""" +function kleitman_wang_graph( indegree_sequence::AbstractVector{<:Integer}, outdegree_sequence::AbstractVector{<:Integer},) + return kleitman_wang_graph(Int, indegree_sequence, outdegree_sequence) +end + + + +""" + kleitman_wang_graph(T::Type{<:Integer}, indegree_sequence::AbstractVector{<:Integer},outdegree_sequence::AbstractVector{<:Integer}) + +Returns a simple directed graph with given finite in-degree and out-degree sequences of non-negative integers generated via the Kleitman-Wang algorithm, that works like follows: +1. Sort the indegree-outdegree pairs in lexicographical order; +2. Select a pair that has strictly positive outdegree, say the i-th pairs that has outdegree = b_i; +3. Subtract 1 to the first b_i highest indegrees (the i-th being excluded), and set b_i to 0; +4. Repeat from 1. until all indegree-outdegree pairs are of the form (0.0). + +The `eltype` of the returned graph will be `T`. + +## References +- [Wikipedia](https://en.wikipedia.org/wiki/Kleitman%E2%80%93Wang_algorithms) +- [Kleitman and Wang (1973)](https://doi.org/10.1016/0012-365X(73)90037-X) +""" +function kleitman_wang_graph( T::Type{<:Integer}, + indegree_sequence::AbstractVector{<:Integer}, + outdegree_sequence::AbstractVector{<:Integer}, +) + length(indegree_sequence) == length(outdegree_sequence) || throw( + ArgumentError( + "The provided `indegree_sequence` and `outdegree_sequence` must be of the dame length.", + ), + ) + # Check whether the indegree_sequence and outdegree_sequence have only non-negative values + all(indegree_sequence .>= 0) || throw( + ArgumentError( + "The `indegree_sequence` sequence must contain non-negative integers only." + ), + ) + all(outdegree_sequence .>= 0) || throw( + ArgumentError( + "The `outdegree_sequence` sequence must contain non-negative integers only." + ), + ) + + # Instantiate an empty simple graph + graph = SimpleDiGraph{T}(length(indegree_sequence)) + # Create a (vertex, degree) ordered dictionary + S = zip(indegree_sequence, outdegree_sequence) + vertices_degrees_dict = OrderedDict(i => tup for (i, tup) in enumerate(S)) + # Kleitman-Wang algorithm + while (any(Iterators.flatten(values(vertices_degrees_dict)) .!= 0)) + # Sort the new sequence in non-increasing lexicographical order + vertices_degrees_dict = OrderedDict( + sort( + collect(vertices_degrees_dict); + by=last, + rev=true, + ), + ) + # Find a vertex with positive outdegree,and temporarily remove it from `vertices_degrees_dict` + i, (a_i, b_i) = 0, (0, 0) + for (_i, (_a_i, _b_i)) in collect(deepcopy(vertices_degrees_dict)) + if _b_i != 0 + i, a_i, b_i = (_i, _a_i, _b_i) + delete!(vertices_degrees_dict, _i) + break + end + end + # Connect the vertex found above to other nodes of highest degree + for (v, degs) in collect(vertices_degrees_dict)[1:b_i] + add_edge!(graph, i, v) + vertices_degrees_dict[v] = (degs[1] - 1, degs[2]) + # Check whether the remaining indegree is nonnegative + vertices_degrees_dict[v][1] >= 0 || throw(ErrorException("The indegree and outdegree sequences are not graphical.")) + end + # Reinsert the vertex, with zero outdegree + vertices_degrees_dict[i] = (a_i, 0) + end + return graph +end diff --git a/test/simplegraphs/generators/staticgraphs.jl b/test/simplegraphs/generators/staticgraphs.jl index 17107adce..088f9c128 100644 --- a/test/simplegraphs/generators/staticgraphs.jl +++ b/test/simplegraphs/generators/staticgraphs.jl @@ -648,3 +648,53 @@ @test_throws DomainError lollipop_graph(-1, -1) end end + + +@testset "havel hakimi" begin + rr = havel_hakimi_graph(repeat([2, 4], 5)) + @test nv(rr) == 10 + @test ne(rr) == 15 + @test is_directed(rr) == false + + rr = havel_hakimi_graph(zeros(Int, 1000)) + @test nv(rr) == 1000 + @test ne(rr) == 0 + @test is_directed(rr) == false + + rr = havel_hakimi_graph([2, 2, 2]) + @test nv(rr) == 3 + @test ne(rr) == 3 + @test is_directed(rr) == false + + graph = SimpleGraph(10, 15) + degree_sequence = degree(graph) + rr = havel_hakimi_graph(degree_sequence) + @test nv(rr) == 10 + @test ne(rr) == 15 + @test is_directed(rr) == false +end + +@testset "kleitman wang" begin + rr = kleitman_wang_graph(repeat([2, 4], 5), repeat([2, 4], 5)) + @test nv(rr) == 10 + @test ne(rr) == 30 + @test is_directed(rr) == true + + rr = kleitman_wang_graph(zeros(Int, 1000), zeros(Int, 1000)) + @test nv(rr) == 1000 + @test ne(rr) == 0 + @test is_directed(rr) == true + + rr = kleitman_wang_graph([2, 2, 2], [2, 2, 2]) + @test nv(rr) == 3 + @test ne(rr) == 6 + @test is_directed(rr) == true + + graph = SimpleDiGraph(10, 15) + indegree_sequence = indegree(graph) + outdegree_sequence = outdegree(graph) + rr = kleitman_wang_graph(indegree_sequence, outdegree_sequence) + @test nv(rr) == 10 + @test ne(rr) == 15 + @test is_directed(rr) == true +end