diff --git a/doc/weights.xml b/doc/weights.xml index 6816f17c7..1f98ee26d 100644 --- a/doc/weights.xml +++ b/doc/weights.xml @@ -69,4 +69,56 @@ gap> EdgeWeights(g); ]]> -<#/GAPDoc> \ No newline at end of file +<#/GAPDoc> + +<#GAPDoc Label="EdgeWeightedDigraphTotalWeight"> + + + An integer, float or rational. + + If digraph is a digraph with edge weights, then this attribute + returns the sum of the weights of its edges.

+ + &MUTABLE_RECOMPUTED_ATTR; + + See . + D := EdgeWeightedDigraph([[2], [1], [1, 2]], +> [[12], [5], [6, 9]]); + +gap> EdgeWeightedDigraphTotalWeight(D); +32]]> + + +<#/GAPDoc> + +<#GAPDoc Label="EdgeWeightedDigraphMinimumSpanningTree"> + + + A digraph. + + If digraph is a connected digraph with edge weights, then this + attribute returns a digraph which is a minimum spanning tree of + digraph.

+ + A spanning tree of a digraph is a subdigraph with the same + vertices but a subset of its edges that form an undirected tree. It is + minimum if it has the smallest possible total weight for a + spanning tree of that digraph.

+ + &MUTABLE_RECOMPUTED_ATTR; + + See , + and + . + D := EdgeWeightedDigraph([[2], [1], [1, 2]], +> [[12], [5], [6, 9]]); + +gap> T := EdgeWeightedDigraphMinimumSpanningTree(D); + +gap> EdgeWeights(T); +[ [ ], [ 5 ], [ 6 ] ]]]> + + +<#/GAPDoc> diff --git a/doc/z-chap5.xml b/doc/z-chap5.xml index 8adb9eb49..0baeaf83c 100644 --- a/doc/z-chap5.xml +++ b/doc/z-chap5.xml @@ -27,6 +27,8 @@

Edge Weights <#Include Label="EdgeWeights"> <#Include Label="EdgeWeightedDigraph"> + <#Include Label="EdgeWeightedDigraphTotalWeight"> + <#Include Label="EdgeWeightedDigraphMinimumSpanningTree">
Orders diff --git a/gap/weights.gd b/gap/weights.gd index 33fed4801..b15f5dd71 100644 --- a/gap/weights.gd +++ b/gap/weights.gd @@ -12,6 +12,11 @@ DeclareAttribute("EdgeWeights", IsDigraph); DeclareGlobalFunction("EdgeWeightedDigraph"); DeclareProperty("IsNegativeEdgeWeightedDigraph", IsDigraph and HasEdgeWeights); +DeclareAttribute("EdgeWeightedDigraphTotalWeight", IsDigraph and HasEdgeWeights); # 2. Edge Weight Copies -DeclareOperation("EdgeWeightsMutableCopy", [IsDigraph and HasEdgeWeights]); \ No newline at end of file +DeclareOperation("EdgeWeightsMutableCopy", [IsDigraph and HasEdgeWeights]); + +# 3. Minimum Spanning Trees +DeclareAttribute("EdgeWeightedDigraphMinimumSpanningTree", + IsDigraph and HasEdgeWeights); diff --git a/gap/weights.gi b/gap/weights.gi index 9ddd888c2..777c55d10 100644 --- a/gap/weights.gi +++ b/gap/weights.gi @@ -67,7 +67,7 @@ function(digraph, weights) return digraph; end); -InstallMethod(IsNegativeEdgeWeightedDigraph, "for an edge weighted digraph", +InstallMethod(IsNegativeEdgeWeightedDigraph, "for a digraph with edge weights", [IsDigraph and HasEdgeWeights], function(digraph) local weights, u, w; @@ -84,10 +84,91 @@ function(digraph) return false; end); +InstallMethod(EdgeWeightedDigraphTotalWeight, +"for a digraph with edge weights", +[IsDigraph and HasEdgeWeights], +D -> Sum(EdgeWeights(D), Sum)); + ############################################################################# # 2. Copies of edge weights ############################################################################# InstallMethod(EdgeWeightsMutableCopy, "for a digraph with edge weights", [IsDigraph and HasEdgeWeights], -D -> List(EdgeWeights(D), ShallowCopy)); \ No newline at end of file +D -> List(EdgeWeights(D), ShallowCopy)); + +############################################################################# +# 3. Minimum Spanning Trees +############################################################################# + +InstallMethod(EdgeWeightedDigraphMinimumSpanningTree, +"for a digraph with edge weights", +[IsDigraph and HasEdgeWeights], +function(digraph) + local weights, numberOfVertices, edgeList, u, outNeighbours, idx, v, w, mst, + mstWeights, partition, i, nrEdges, total, node, x, y, out; + + # check graph is connected + if not IsConnectedDigraph(digraph) then + ErrorNoReturn("the argument must be a connected digraph,"); + fi; + + weights := EdgeWeights(digraph); + + # create a list of edges containing u-v + # w: the weight of the edge + # u: the start vertex + # v: the finishing vertex of that edge + numberOfVertices := DigraphNrVertices(digraph); + edgeList := []; + for u in DigraphVertices(digraph) do + outNeighbours := OutNeighboursOfVertex(digraph, u); + for idx in [1 .. Size(outNeighbours)] do + v := outNeighbours[idx]; # the out neighbour + w := weights[u][idx]; # the weight to the out neighbour + Add(edgeList, [w, u, v]); + od; + od; + + # sort edge weights by their weight + StableSortBy(edgeList, x -> x[1]); + + mst := EmptyPlist(numberOfVertices); + mstWeights := EmptyPlist(numberOfVertices); + + partition := PartitionDS(IsPartitionDS, numberOfVertices); + + for v in [1 .. numberOfVertices] do + Add(mst, []); + Add(mstWeights, []); + od; + + i := 1; + nrEdges := 0; + total := 0; + while nrEdges < numberOfVertices - 1 do + node := edgeList[i]; + + w := node[1]; + u := node[2]; + v := node[3]; + + i := i + 1; + + x := Representative(partition, u); + y := Representative(partition, v); + + # if cycle doesn't exist + if x <> y then + Add(mst[u], v); + Add(mstWeights[u], w); + nrEdges := nrEdges + 1; + total := total + w; + Unite(partition, x, y); + fi; + od; + + out := EdgeWeightedDigraph(mst, mstWeights); + SetEdgeWeightedDigraphTotalWeight(out, total); + return out; +end); diff --git a/tst/standard/weights.tst b/tst/standard/weights.tst index dd3c94828..6acc69b38 100644 --- a/tst/standard/weights.tst +++ b/tst/standard/weights.tst @@ -18,9 +18,11 @@ gap> DIGRAPHS_StartTest(); gap> d := EdgeWeightedDigraph([[2], []], [[5], []]); -# create with Digraph +# create edge weighted digraph gap> d := EdgeWeightedDigraph(Digraph([[2], []]), [[5], []]); +gap> EdgeWeightedDigraphTotalWeight(d); +5 # weight not valid gap> d := EdgeWeightedDigraph([[2], []], [["a"], []]); @@ -85,6 +87,56 @@ gap> d := EdgeWeightedDigraph([[2], [1]], [[-5], [10]]); gap> IsNegativeEdgeWeightedDigraph(d); true +# not connnected digraph +gap> d := EdgeWeightedDigraph([[1], [2]], [[5], [10]]); + +gap> EdgeWeightedDigraphMinimumSpanningTree(d); +Error, the argument must be a connected digraph, + +# digraph with one node +gap> d := EdgeWeightedDigraph([[]], [[]]); + +gap> tree := EdgeWeightedDigraphMinimumSpanningTree(d); + +gap> EdgeWeightedDigraphTotalWeight(tree); +0 + +# digraph with loop +gap> d := EdgeWeightedDigraph([[1]], [[5]]); + +gap> EdgeWeightedDigraphMinimumSpanningTree(d); + + +# digraph with cycle +gap> d := EdgeWeightedDigraph([[2], [3], [1]], [[5], [10], [15]]); + +gap> tree := EdgeWeightedDigraphMinimumSpanningTree(d); + +gap> EdgeWeightedDigraphTotalWeight(tree); +15 + +# digraph with negative edge +gap> d := EdgeWeightedDigraph([[2], []], [[-5], []]); + +gap> EdgeWeightedDigraphMinimumSpanningTree(d); + + +# digraph with negative cycle +gap> d := EdgeWeightedDigraph([[2], [3], [1]], [[-5], [-10], [-15]]); + +gap> EdgeWeightedDigraphMinimumSpanningTree(d); + + +# digraph with parallel edges +gap> d := EdgeWeightedDigraph([[2, 2, 2], [1]], [[10, 5, 15], [7]]); + +gap> EdgeWeightedDigraphMinimumSpanningTree(d); + + +# DIGRAPHS_UnbindVariables +gap> Unbind(d); +gap> Unbind(tree); + # gap> DIGRAPHS_StopTest(); gap> STOP_TEST("Digraphs package: standard/weights.tst", 0); \ No newline at end of file diff --git a/tst/testinstall.tst b/tst/testinstall.tst index 6cdc58701..e165c78c2 100644 --- a/tst/testinstall.tst +++ b/tst/testinstall.tst @@ -411,10 +411,16 @@ gap> String(D); "DigraphFromDigraph6String(\"&CECG\")" gap> String(CycleDigraph(4)); "CycleDigraph(4)" + +# Edge-weighted digraphs gap> d := EdgeWeightedDigraph([[2], [1]], [[5], [10]]); gap> EdgeWeights(d); [ [ 5 ], [ 10 ] ] +gap> EdgeWeightedDigraphTotalWeight(d); +15 +gap> EdgeWeightedDigraphMinimumSpanningTree(d); + # Issue 617: bug in DigraphRemoveEdge, wasn't removing edge labels gap> D := DigraphByEdges(IsMutableDigraph, [[1, 2], [2, 3], [3, 4], [4, 1], [1, 1]]);;