github.com/gopherd/gonum@v0.0.4/graph/path/spanning_tree_test.go (about) 1 // Copyright ©2015 The Gonum Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package path 6 7 import ( 8 "math" 9 "testing" 10 11 "github.com/gopherd/gonum/graph" 12 "github.com/gopherd/gonum/graph/simple" 13 ) 14 15 func TestVerifySpanningTreeTests(t *testing.T) { 16 t.Parallel() 17 for _, test := range spanningTreeTests { 18 var w float64 19 for _, e := range test.treeEdges { 20 w += e.W 21 } 22 if w != test.want { 23 t.Fatalf("bad test: %s weight mismatch: %v != %v", test.name, w, test.want) 24 } 25 } 26 } 27 28 type spanningGraph interface { 29 graph.WeightedBuilder 30 graph.WeightedUndirected 31 WeightedEdges() graph.WeightedEdges 32 } 33 34 var spanningTreeTests = []struct { 35 name string 36 graph func() spanningGraph 37 edges []simple.WeightedEdge 38 want float64 39 treeEdges []simple.WeightedEdge 40 }{ 41 { 42 name: "Empty", 43 graph: func() spanningGraph { return simple.NewWeightedUndirectedGraph(0, math.Inf(1)) }, 44 want: 0, 45 }, 46 { 47 // https://upload.wikimedia.org/wikipedia/commons/f/f7/Prim%27s_algorithm.svg 48 // Modified to make edge weights unique; A--B is increased to 2.5 otherwise 49 // to prevent the alternative solution being found. 50 name: "Prim WP figure 1", 51 graph: func() spanningGraph { return simple.NewWeightedUndirectedGraph(0, math.Inf(1)) }, 52 edges: []simple.WeightedEdge{ 53 {F: simple.Node('A'), T: simple.Node('B'), W: 2.5}, 54 {F: simple.Node('A'), T: simple.Node('D'), W: 1}, 55 {F: simple.Node('B'), T: simple.Node('D'), W: 2}, 56 {F: simple.Node('C'), T: simple.Node('D'), W: 3}, 57 }, 58 59 want: 6, 60 treeEdges: []simple.WeightedEdge{ 61 {F: simple.Node('A'), T: simple.Node('D'), W: 1}, 62 {F: simple.Node('B'), T: simple.Node('D'), W: 2}, 63 {F: simple.Node('C'), T: simple.Node('D'), W: 3}, 64 }, 65 }, 66 { 67 // https://upload.wikimedia.org/wikipedia/commons/5/5c/MST_kruskal_en.gif 68 name: "Kruskal WP figure 1", 69 graph: func() spanningGraph { return simple.NewWeightedUndirectedGraph(0, math.Inf(1)) }, 70 edges: []simple.WeightedEdge{ 71 {F: simple.Node('a'), T: simple.Node('b'), W: 3}, 72 {F: simple.Node('a'), T: simple.Node('e'), W: 1}, 73 {F: simple.Node('b'), T: simple.Node('c'), W: 5}, 74 {F: simple.Node('b'), T: simple.Node('e'), W: 4}, 75 {F: simple.Node('c'), T: simple.Node('d'), W: 2}, 76 {F: simple.Node('c'), T: simple.Node('e'), W: 6}, 77 {F: simple.Node('d'), T: simple.Node('e'), W: 7}, 78 }, 79 80 want: 11, 81 treeEdges: []simple.WeightedEdge{ 82 {F: simple.Node('a'), T: simple.Node('b'), W: 3}, 83 {F: simple.Node('a'), T: simple.Node('e'), W: 1}, 84 {F: simple.Node('b'), T: simple.Node('c'), W: 5}, 85 {F: simple.Node('c'), T: simple.Node('d'), W: 2}, 86 }, 87 }, 88 { 89 // https://upload.wikimedia.org/wikipedia/commons/8/87/Kruskal_Algorithm_6.svg 90 name: "Kruskal WP example", 91 graph: func() spanningGraph { return simple.NewWeightedUndirectedGraph(0, math.Inf(1)) }, 92 edges: []simple.WeightedEdge{ 93 {F: simple.Node('A'), T: simple.Node('B'), W: 7}, 94 {F: simple.Node('A'), T: simple.Node('D'), W: 5}, 95 {F: simple.Node('B'), T: simple.Node('C'), W: 8}, 96 {F: simple.Node('B'), T: simple.Node('D'), W: 9}, 97 {F: simple.Node('B'), T: simple.Node('E'), W: 7}, 98 {F: simple.Node('C'), T: simple.Node('E'), W: 5}, 99 {F: simple.Node('D'), T: simple.Node('E'), W: 15}, 100 {F: simple.Node('D'), T: simple.Node('F'), W: 6}, 101 {F: simple.Node('E'), T: simple.Node('F'), W: 8}, 102 {F: simple.Node('E'), T: simple.Node('G'), W: 9}, 103 {F: simple.Node('F'), T: simple.Node('G'), W: 11}, 104 }, 105 106 want: 39, 107 treeEdges: []simple.WeightedEdge{ 108 {F: simple.Node('A'), T: simple.Node('B'), W: 7}, 109 {F: simple.Node('A'), T: simple.Node('D'), W: 5}, 110 {F: simple.Node('B'), T: simple.Node('E'), W: 7}, 111 {F: simple.Node('C'), T: simple.Node('E'), W: 5}, 112 {F: simple.Node('D'), T: simple.Node('F'), W: 6}, 113 {F: simple.Node('E'), T: simple.Node('G'), W: 9}, 114 }, 115 }, 116 { 117 // https://upload.wikimedia.org/wikipedia/commons/2/2e/Boruvka%27s_algorithm_%28Sollin%27s_algorithm%29_Anim.gif 118 name: "Borůvka WP example", 119 graph: func() spanningGraph { return simple.NewWeightedUndirectedGraph(0, math.Inf(1)) }, 120 edges: []simple.WeightedEdge{ 121 {F: simple.Node('A'), T: simple.Node('B'), W: 13}, 122 {F: simple.Node('A'), T: simple.Node('C'), W: 6}, 123 {F: simple.Node('B'), T: simple.Node('C'), W: 7}, 124 {F: simple.Node('B'), T: simple.Node('D'), W: 1}, 125 {F: simple.Node('C'), T: simple.Node('D'), W: 14}, 126 {F: simple.Node('C'), T: simple.Node('E'), W: 8}, 127 {F: simple.Node('C'), T: simple.Node('H'), W: 20}, 128 {F: simple.Node('D'), T: simple.Node('E'), W: 9}, 129 {F: simple.Node('D'), T: simple.Node('F'), W: 3}, 130 {F: simple.Node('E'), T: simple.Node('F'), W: 2}, 131 {F: simple.Node('E'), T: simple.Node('J'), W: 18}, 132 {F: simple.Node('G'), T: simple.Node('H'), W: 15}, 133 {F: simple.Node('G'), T: simple.Node('I'), W: 5}, 134 {F: simple.Node('G'), T: simple.Node('J'), W: 19}, 135 {F: simple.Node('G'), T: simple.Node('K'), W: 10}, 136 {F: simple.Node('H'), T: simple.Node('J'), W: 17}, 137 {F: simple.Node('I'), T: simple.Node('K'), W: 11}, 138 {F: simple.Node('J'), T: simple.Node('K'), W: 16}, 139 {F: simple.Node('J'), T: simple.Node('L'), W: 4}, 140 {F: simple.Node('K'), T: simple.Node('L'), W: 12}, 141 }, 142 143 want: 83, 144 treeEdges: []simple.WeightedEdge{ 145 {F: simple.Node('A'), T: simple.Node('C'), W: 6}, 146 {F: simple.Node('B'), T: simple.Node('C'), W: 7}, 147 {F: simple.Node('B'), T: simple.Node('D'), W: 1}, 148 {F: simple.Node('D'), T: simple.Node('F'), W: 3}, 149 {F: simple.Node('E'), T: simple.Node('F'), W: 2}, 150 {F: simple.Node('E'), T: simple.Node('J'), W: 18}, 151 {F: simple.Node('G'), T: simple.Node('H'), W: 15}, 152 {F: simple.Node('G'), T: simple.Node('I'), W: 5}, 153 {F: simple.Node('G'), T: simple.Node('K'), W: 10}, 154 {F: simple.Node('J'), T: simple.Node('L'), W: 4}, 155 {F: simple.Node('K'), T: simple.Node('L'), W: 12}, 156 }, 157 }, 158 { 159 // https://upload.wikimedia.org/wikipedia/commons/d/d2/Minimum_spanning_tree.svg 160 // Nodes labelled row major. 161 name: "Minimum Spanning Tree WP figure 1", 162 graph: func() spanningGraph { return simple.NewWeightedUndirectedGraph(0, math.Inf(1)) }, 163 edges: []simple.WeightedEdge{ 164 {F: simple.Node(1), T: simple.Node(2), W: 4}, 165 {F: simple.Node(1), T: simple.Node(3), W: 1}, 166 {F: simple.Node(1), T: simple.Node(4), W: 4}, 167 {F: simple.Node(2), T: simple.Node(3), W: 5}, 168 {F: simple.Node(2), T: simple.Node(5), W: 9}, 169 {F: simple.Node(2), T: simple.Node(6), W: 9}, 170 {F: simple.Node(2), T: simple.Node(8), W: 7}, 171 {F: simple.Node(3), T: simple.Node(4), W: 3}, 172 {F: simple.Node(3), T: simple.Node(8), W: 9}, 173 {F: simple.Node(4), T: simple.Node(8), W: 10}, 174 {F: simple.Node(4), T: simple.Node(10), W: 18}, 175 {F: simple.Node(5), T: simple.Node(6), W: 2}, 176 {F: simple.Node(5), T: simple.Node(7), W: 4}, 177 {F: simple.Node(5), T: simple.Node(9), W: 6}, 178 {F: simple.Node(6), T: simple.Node(7), W: 2}, 179 {F: simple.Node(6), T: simple.Node(8), W: 8}, 180 {F: simple.Node(7), T: simple.Node(8), W: 9}, 181 {F: simple.Node(7), T: simple.Node(9), W: 3}, 182 {F: simple.Node(7), T: simple.Node(10), W: 9}, 183 {F: simple.Node(8), T: simple.Node(10), W: 8}, 184 {F: simple.Node(9), T: simple.Node(10), W: 9}, 185 }, 186 187 want: 38, 188 treeEdges: []simple.WeightedEdge{ 189 {F: simple.Node(1), T: simple.Node(2), W: 4}, 190 {F: simple.Node(1), T: simple.Node(3), W: 1}, 191 {F: simple.Node(2), T: simple.Node(8), W: 7}, 192 {F: simple.Node(3), T: simple.Node(4), W: 3}, 193 {F: simple.Node(5), T: simple.Node(6), W: 2}, 194 {F: simple.Node(6), T: simple.Node(7), W: 2}, 195 {F: simple.Node(6), T: simple.Node(8), W: 8}, 196 {F: simple.Node(7), T: simple.Node(9), W: 3}, 197 {F: simple.Node(8), T: simple.Node(10), W: 8}, 198 }, 199 }, 200 201 { 202 // https://upload.wikimedia.org/wikipedia/commons/2/2e/Boruvka%27s_algorithm_%28Sollin%27s_algorithm%29_Anim.gif 203 // but with C--H and E--J cut. 204 name: "Borůvka WP example cut", 205 graph: func() spanningGraph { return simple.NewWeightedUndirectedGraph(0, math.Inf(1)) }, 206 edges: []simple.WeightedEdge{ 207 {F: simple.Node('A'), T: simple.Node('B'), W: 13}, 208 {F: simple.Node('A'), T: simple.Node('C'), W: 6}, 209 {F: simple.Node('B'), T: simple.Node('C'), W: 7}, 210 {F: simple.Node('B'), T: simple.Node('D'), W: 1}, 211 {F: simple.Node('C'), T: simple.Node('D'), W: 14}, 212 {F: simple.Node('C'), T: simple.Node('E'), W: 8}, 213 {F: simple.Node('D'), T: simple.Node('E'), W: 9}, 214 {F: simple.Node('D'), T: simple.Node('F'), W: 3}, 215 {F: simple.Node('E'), T: simple.Node('F'), W: 2}, 216 {F: simple.Node('G'), T: simple.Node('H'), W: 15}, 217 {F: simple.Node('G'), T: simple.Node('I'), W: 5}, 218 {F: simple.Node('G'), T: simple.Node('J'), W: 19}, 219 {F: simple.Node('G'), T: simple.Node('K'), W: 10}, 220 {F: simple.Node('H'), T: simple.Node('J'), W: 17}, 221 {F: simple.Node('I'), T: simple.Node('K'), W: 11}, 222 {F: simple.Node('J'), T: simple.Node('K'), W: 16}, 223 {F: simple.Node('J'), T: simple.Node('L'), W: 4}, 224 {F: simple.Node('K'), T: simple.Node('L'), W: 12}, 225 }, 226 227 want: 65, 228 treeEdges: []simple.WeightedEdge{ 229 {F: simple.Node('A'), T: simple.Node('C'), W: 6}, 230 {F: simple.Node('B'), T: simple.Node('C'), W: 7}, 231 {F: simple.Node('B'), T: simple.Node('D'), W: 1}, 232 {F: simple.Node('D'), T: simple.Node('F'), W: 3}, 233 {F: simple.Node('E'), T: simple.Node('F'), W: 2}, 234 {F: simple.Node('G'), T: simple.Node('H'), W: 15}, 235 {F: simple.Node('G'), T: simple.Node('I'), W: 5}, 236 {F: simple.Node('G'), T: simple.Node('K'), W: 10}, 237 {F: simple.Node('J'), T: simple.Node('L'), W: 4}, 238 {F: simple.Node('K'), T: simple.Node('L'), W: 12}, 239 }, 240 }, 241 } 242 243 func testMinumumSpanning(mst func(dst WeightedBuilder, g spanningGraph) float64, t *testing.T) { 244 for _, test := range spanningTreeTests { 245 g := test.graph() 246 for _, e := range test.edges { 247 g.SetWeightedEdge(e) 248 } 249 250 dst := simple.NewWeightedUndirectedGraph(0, math.Inf(1)) 251 w := mst(dst, g) 252 if w != test.want { 253 t.Errorf("unexpected minimum spanning tree weight for %q: got: %f want: %f", 254 test.name, w, test.want) 255 } 256 var got float64 257 for _, e := range graph.WeightedEdgesOf(dst.WeightedEdges()) { 258 got += e.Weight() 259 } 260 if got != test.want { 261 t.Errorf("unexpected minimum spanning tree edge weight sum for %q: got: %f want: %f", 262 test.name, got, test.want) 263 } 264 265 gotEdges := graph.EdgesOf(dst.Edges()) 266 if len(gotEdges) != len(test.treeEdges) { 267 t.Errorf("unexpected number of spanning tree edges for %q: got: %d want: %d", 268 test.name, len(gotEdges), len(test.treeEdges)) 269 } 270 for _, e := range test.treeEdges { 271 w, ok := dst.Weight(e.From().ID(), e.To().ID()) 272 if !ok { 273 t.Errorf("spanning tree edge not found in graph for %q: %+v", 274 test.name, e) 275 } 276 if w != e.Weight() { 277 t.Errorf("unexpected spanning tree edge weight for %q: got: %f want: %f", 278 test.name, w, e.Weight()) 279 } 280 } 281 } 282 } 283 284 func TestKruskal(t *testing.T) { 285 t.Parallel() 286 testMinumumSpanning(func(dst WeightedBuilder, g spanningGraph) float64 { 287 return Kruskal(dst, g) 288 }, t) 289 } 290 291 func TestPrim(t *testing.T) { 292 t.Parallel() 293 testMinumumSpanning(func(dst WeightedBuilder, g spanningGraph) float64 { 294 return Prim(dst, g) 295 }, t) 296 }