gonum.org/v1/gonum@v0.15.1-0.20240517103525-f853624cb1bb/graph/topo/bron_kerbosch.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 topo
     6  
     7  import (
     8  	"slices"
     9  
    10  	"gonum.org/v1/gonum/graph"
    11  	"gonum.org/v1/gonum/graph/internal/set"
    12  )
    13  
    14  // DegeneracyOrdering returns the degeneracy ordering and the k-cores of
    15  // the undirected graph g.
    16  func DegeneracyOrdering(g graph.Undirected) (order []graph.Node, cores [][]graph.Node) {
    17  	order, offsets := degeneracyOrdering(g)
    18  
    19  	slices.Reverse(order)
    20  	cores = make([][]graph.Node, len(offsets))
    21  	offset := len(order)
    22  	for i, n := range offsets {
    23  		cores[i] = order[offset-n : offset]
    24  		offset -= n
    25  	}
    26  	return order, cores
    27  }
    28  
    29  // KCore returns the k-core of the undirected graph g with nodes in an
    30  // optimal ordering for the coloring number.
    31  func KCore(k int, g graph.Undirected) []graph.Node {
    32  	order, offsets := degeneracyOrdering(g)
    33  
    34  	var offset int
    35  	for _, n := range offsets[:k] {
    36  		offset += n
    37  	}
    38  	core := make([]graph.Node, len(order)-offset)
    39  	copy(core, order[offset:])
    40  	return core
    41  }
    42  
    43  // degeneracyOrdering is the common code for DegeneracyOrdering and KCore. It
    44  // returns l, the nodes of g in optimal ordering for coloring number and
    45  // s, a set of relative offsets into l for each k-core, where k is an index
    46  // into s.
    47  func degeneracyOrdering(g graph.Undirected) (l []graph.Node, s []int) {
    48  	nodes := graph.NodesOf(g.Nodes())
    49  
    50  	// The algorithm used here is essentially as described at
    51  	// http://en.wikipedia.org/w/index.php?title=Degeneracy_%28graph_theory%29&oldid=640308710
    52  
    53  	// Initialize an output list L in return parameters.
    54  
    55  	// Compute a number d_v for each vertex v in G,
    56  	// the number of neighbors of v that are not already in L.
    57  	// Initially, these numbers are just the degrees of the vertices.
    58  	dv := make(map[int64]int, len(nodes))
    59  	var (
    60  		maxDegree  int
    61  		neighbours = make(map[int64][]graph.Node)
    62  	)
    63  	for _, n := range nodes {
    64  		id := n.ID()
    65  		adj := graph.NodesOf(g.From(id))
    66  		neighbours[id] = adj
    67  		dv[id] = len(adj)
    68  		if len(adj) > maxDegree {
    69  			maxDegree = len(adj)
    70  		}
    71  	}
    72  
    73  	// Initialize an array D such that D[i] contains a list of the
    74  	// vertices v that are not already in L for which d_v = i.
    75  	d := make([][]graph.Node, maxDegree+1)
    76  	for _, n := range nodes {
    77  		deg := dv[n.ID()]
    78  		d[deg] = append(d[deg], n)
    79  	}
    80  
    81  	// Initialize k to 0.
    82  	k := 0
    83  	// Repeat n times:
    84  	s = []int{0}
    85  	for range nodes {
    86  		// Scan the array cells D[0], D[1], ... until
    87  		// finding an i for which D[i] is nonempty.
    88  		var (
    89  			i  int
    90  			di []graph.Node
    91  		)
    92  		for i, di = range d {
    93  			if len(di) != 0 {
    94  				break
    95  			}
    96  		}
    97  
    98  		// Set k to max(k,i).
    99  		if i > k {
   100  			k = i
   101  			s = append(s, make([]int, k-len(s)+1)...)
   102  		}
   103  
   104  		// Select a vertex v from D[i]. Add v to the
   105  		// beginning of L and remove it from D[i].
   106  		var v graph.Node
   107  		v, d[i] = di[len(di)-1], di[:len(di)-1]
   108  		l = append(l, v)
   109  		s[k]++
   110  		delete(dv, v.ID())
   111  
   112  		// For each neighbor w of v not already in L,
   113  		// subtract one from d_w and move w to the
   114  		// cell of D corresponding to the new value of d_w.
   115  		for _, w := range neighbours[v.ID()] {
   116  			dw, ok := dv[w.ID()]
   117  			if !ok {
   118  				continue
   119  			}
   120  			for i, n := range d[dw] {
   121  				if n.ID() == w.ID() {
   122  					d[dw][i], d[dw] = d[dw][len(d[dw])-1], d[dw][:len(d[dw])-1]
   123  					dw--
   124  					d[dw] = append(d[dw], w)
   125  					break
   126  				}
   127  			}
   128  			dv[w.ID()] = dw
   129  		}
   130  	}
   131  
   132  	return l, s
   133  }
   134  
   135  // BronKerbosch returns the set of maximal cliques of the undirected graph g.
   136  func BronKerbosch(g graph.Undirected) [][]graph.Node {
   137  	nodes := graph.NodesOf(g.Nodes())
   138  
   139  	// The algorithm used here is essentially BronKerbosch3 as described at
   140  	// http://en.wikipedia.org/w/index.php?title=Bron%E2%80%93Kerbosch_algorithm&oldid=656805858
   141  
   142  	p := set.NewNodesSize(len(nodes))
   143  	for _, n := range nodes {
   144  		p.Add(n)
   145  	}
   146  	x := set.NewNodes()
   147  	var bk bronKerbosch
   148  	order, _ := degeneracyOrdering(g)
   149  	slices.Reverse(order)
   150  	for _, v := range order {
   151  		neighbours := graph.NodesOf(g.From(v.ID()))
   152  		nv := set.NewNodesSize(len(neighbours))
   153  		for _, n := range neighbours {
   154  			nv.Add(n)
   155  		}
   156  		bk.maximalCliquePivot(g, []graph.Node{v}, set.IntersectionOfNodes(p, nv), set.IntersectionOfNodes(x, nv))
   157  		p.Remove(v)
   158  		x.Add(v)
   159  	}
   160  	return bk
   161  }
   162  
   163  type bronKerbosch [][]graph.Node
   164  
   165  func (bk *bronKerbosch) maximalCliquePivot(g graph.Undirected, r []graph.Node, p, x set.Nodes) {
   166  	if len(p) == 0 && len(x) == 0 {
   167  		*bk = append(*bk, r)
   168  		return
   169  	}
   170  
   171  	neighbours := bk.choosePivotFrom(g, p, x)
   172  	nu := set.NewNodesSize(len(neighbours))
   173  	for _, n := range neighbours {
   174  		nu.Add(n)
   175  	}
   176  	for _, v := range p {
   177  		if nu.Has(v) {
   178  			continue
   179  		}
   180  		vid := v.ID()
   181  		neighbours := graph.NodesOf(g.From(vid))
   182  		nv := set.NewNodesSize(len(neighbours))
   183  		for _, n := range neighbours {
   184  			nv.Add(n)
   185  		}
   186  
   187  		var found bool
   188  		for _, n := range r {
   189  			if n.ID() == vid {
   190  				found = true
   191  				break
   192  			}
   193  		}
   194  		var sr []graph.Node
   195  		if !found {
   196  			sr = append(r[:len(r):len(r)], v)
   197  		}
   198  
   199  		bk.maximalCliquePivot(g, sr, set.IntersectionOfNodes(p, nv), set.IntersectionOfNodes(x, nv))
   200  		p.Remove(v)
   201  		x.Add(v)
   202  	}
   203  }
   204  
   205  func (*bronKerbosch) choosePivotFrom(g graph.Undirected, p, x set.Nodes) (neighbors []graph.Node) {
   206  	// TODO(kortschak): Investigate the impact of pivot choice that maximises
   207  	// |p ⋂ neighbours(u)| as a function of input size. Until then, leave as
   208  	// compile time option.
   209  	if !tomitaTanakaTakahashi {
   210  		for _, n := range p {
   211  			return graph.NodesOf(g.From(n.ID()))
   212  		}
   213  		for _, n := range x {
   214  			return graph.NodesOf(g.From(n.ID()))
   215  		}
   216  		panic("bronKerbosch: empty set")
   217  	}
   218  
   219  	var (
   220  		max   = -1
   221  		pivot graph.Node
   222  	)
   223  	maxNeighbors := func(s set.Nodes) {
   224  	outer:
   225  		for _, u := range s {
   226  			nb := graph.NodesOf(g.From(u.ID()))
   227  			c := len(nb)
   228  			if c <= max {
   229  				continue
   230  			}
   231  			for n := range nb {
   232  				if _, ok := p[int64(n)]; ok {
   233  					continue
   234  				}
   235  				c--
   236  				if c <= max {
   237  					continue outer
   238  				}
   239  			}
   240  			max = c
   241  			pivot = u
   242  			neighbors = nb
   243  		}
   244  	}
   245  	maxNeighbors(p)
   246  	maxNeighbors(x)
   247  	if pivot == nil {
   248  		panic("bronKerbosch: empty set")
   249  	}
   250  	return neighbors
   251  }