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  }