24

I've been looking for an implementation (I'm using networkx library.) that will find all the minimum spanning trees (MST) of an undirected weighted graph.

I can only find implementations for Kruskal's Algorithm and Prim's Algorithm both of which will only return a single MST.

I've seen papers that address this problem (such as Representing all minimum spanning trees with applications to counting and generation) but my head tends to explode someway through trying to think how to translate it to code.

In fact i've not been able to find an implementation in any language!

jfs
  • 399,953
  • 195
  • 994
  • 1,670
russtbarnacle
  • 915
  • 1
  • 10
  • 14
  • 1
    What do you need this for? I imagine doing it efficiently won't be trivial, so is generating all the subsets of `n - 1` edges too slow? – IVlad May 29 '10 at 16:21
  • I'm implementing an algorithm from this paper (http://linkinghub.elsevier.com/retrieve/pii/S1571065309002066), one of the required steps is to iterate through all mst. – russtbarnacle May 29 '10 at 16:40
  • 1
    Hi! Not to revive an old thread, but I'm looking for an implementation (in any language) of an algorithm that will give me all spanning trees of a graph. This is very similar to what you were looking for. Did you have any success? – rjkaplan Dec 04 '11 at 00:14
  • I'm looking for the same thing. Did you? – Nether Apr 13 '17 at 12:34
  • One simple idea that comes to mind is a slight modification of Kruskal's algorithm that would at each selection step iterate over all candidate edges (i.e. all min-weight, yet unselected edges that do not create a cycle in the growing forest) instead of picking an arbitrary one. – Anthony Labarre May 24 '18 at 14:53
  • the method suggested above by @IVlad i.e. generating all subsets isn't correct. you will find some subsets that represent graphs with cycles and therefore aren't trees. – Danyal Apr 28 '20 at 12:46
  • @DSM I don't see why that means it won't work - you can simply discard it if it's not a valid tree. It's a brute force method that lists all trees exhaustively, so while grossly inefficient, there's no reason it shouldn't work. – IVlad Apr 28 '20 at 13:03
  • I have found this http://www.mate.unlp.edu.ar/~liliana/lawclique_2016/07.pdf - which seems to describe some relatively simple algorithms, but not simple enough for me to be able to turn this into an answer after a quick read. – IVlad Apr 28 '20 at 13:11

4 Answers4

10

I don't know if this is the solution, but it's a solution (it's the graph version of a brute force, I would say):

  1. Find the MST of the graph using kruskal's or prim's algorithm. This should be O(E log V).
  2. Generate all spanning trees. This can be done in O(Elog(V) + V + n) for n = number of spanning trees, as I understand from 2 minutes's worth of google, can possibly be improved.
  3. Filter the list generated in step #2 by the tree's weight being equal to the MST's weight. This should be O(n) for n as the number of trees generated in step #2.

Note: Do this lazily! Generating all possible trees and then filtering the results will take O(V^2) memory, and polynomial space requirements are evil - Generate a tree, examine it's weight, if it's an MST add it to a result list, if not - discard it.
Overall time complexity: O(Elog(V) + V + n) for G(V,E) with n spanning trees

Rubys
  • 3,167
  • 2
  • 25
  • 26
  • 1
    Interesting. The paper i refer to in the question has a similar requirement of generating all spanning trees. It refers to another paper "Algorithms for Enumerating All Spanning Trees of Undirected and Weighted Graphs" but i'm pretty much back again at being unable to find *any* implementations of that or generally enumerating all spanning trees. Seems i may have to implement this paper or both papers to get the solution. – russtbarnacle May 30 '10 at 00:10
  • Wait, you're hoping to find an implementation in your favorite language of this? I wouldn't count on it. You have the algorithms, implement them. Doubt it's gonna get any better than that. – Rubys May 30 '10 at 09:35
  • 1
    I was hoping to find an implementation but i have been unable to find one in any language for "all spanning trees" or "all minimum spanning trees". So i was more surprised that there are no implementations at all, in any language. – russtbarnacle May 30 '10 at 11:05
  • @russtbarnacle have you implemented any of these algorithms? – jguilhermeam Feb 02 '16 at 15:47
  • 1
    @Rubys How do you go about generating all Spanning Trees? And how would you compute their total weight? I couldn't find anything for this. – FaCoffee Oct 20 '16 at 13:05
  • So do we have implementations after a decade? – Danyal Apr 28 '20 at 12:48
0

You can find an idea in the work of Sorensen and Janssens (2005).

The idea is to generate the STs in the increasing order, and as soon as you get the bigger value of ST stop the enumeration.

0

Here's a short python implementation, basically a recursive variation of Kruskal's. Uses weight of the the first MST found to limit the size of the search space thereafter. Definitely still exponential complexity but better than generating every spanning tree. Some test code is also included.

[Note: this was just my own experimentation for fun and possible inspiration of further thoughts on the problem from others, it's not an attempt to specifically implement any of the solutions suggested in other supplied answers here]

# Disjoint set find (and collapse) 
def find(nd, djset):
    uv = nd
    while djset[uv] >= 0: uv = djset[uv]
    if djset[nd] >= 0: djset[nd] = uv
    return uv

# Disjoint set union (does not modify djset)
def union(nd1, nd2, djset):
    unionset = djset.copy()
    if unionset[nd2] < unionset[nd1]:
        nd1, nd2 = nd2, nd1

    unionset[nd1] += unionset[nd2]
    unionset[nd2] = nd1
    return unionset

# Bitmask convenience methods; uses bitmasks
# internally to represent MST edge combinations
def setbit(j, mask): return mask | (1 << j)
def isbitset(j, mask): return (mask >> j) & 1
def masktoedges(mask, sedges):
    return [sedges[i] for i in range(len(sedges)) 
            if isbitset(i, mask)]

# Upper-bound count of viable MST edge combination, i.e.
# count of edge subsequences of length: NEDGES, w/sum: WEIGHT
def count_subsequences(sedges, weight, nedges):
#{
    def count(i, target, length, cache):
        tkey = (i, target, length)
        if tkey in cache: return cache[tkey]
        if i == len(sedges) or target < sedges[i][2]: return 0
            
        cache[tkey] = (count(i+1, target, length, cache) +
            count(i+1, target - sedges[i][2], length - 1, cache) + 
            (1 if sedges[i][2] == target and length == 1 else 0))
        
        return cache[tkey]
    
    return count(0, weight, nedges, {})
#}

# Arg: n is number of nodes in graph [0, n-1]
# Arg: sedges is list of graph edges sorted by weight
# Return: list of MSTs, where each MST is a list of edges
def find_all_msts(n, sedges):
#{
    # Recursive variant of kruskal to find all MSTs
    def buildmsts(i, weight, mask, nedges, djset):
    #{
        nonlocal maxweight, msts
        if nedges == (n-1):
            msts.append(mask)
            if maxweight == float('inf'):
                print(f"MST weight: {weight}, MST edges: {n-1}, Total graph edges: {len(sedges)}")
                print(f"Upper bound numb viable MST edge combinations: {count_subsequences(sedges, weight, n-1)}\n")
                maxweight = weight
                
            return
        
        if i < len(sedges):
        #{
            u,v,wt = sedges[i]
            if weight + wt*((n-1) - nedges) <= maxweight:
            #{
                # Left recursive branch - include edge if valid
                nd1, nd2 = find(u, djset), find(v, djset)
                if nd1 != nd2: buildmsts(i+1, weight + wt,
                    setbit(i, mask), nedges+1, union(nd1, nd2, djset))
            
                # Right recursive branch - always skips edge
                buildmsts(i+1, weight, mask, nedges, djset)
            #}
        #}
    #}
        
    maxweight, msts = float('inf'), []
    djset = {i: -1 for i in range(n)}    
    buildmsts(0, 0, 0, 0, djset)    
    return [masktoedges(mask, sedges) for mask in msts]
#}

import time, numpy

def run_test_case(low=10, high=21):
    rng = numpy.random.default_rng()
    n = rng.integers(low, high)
    nedges = rng.integers(n-1, n*(n-1)//2)

    edges = set()
    while len(edges) < nedges: 
        u,v = sorted(rng.choice(range(n), size=2, replace=False))
        edges.add((u,v))

    weights = sorted(rng.integers(1, 2*n, size=nedges))
    sedges = [[u,v,wt] for (u,v), wt in zip(edges, weights)]
    print(f"Numb nodes: {n}\nSorted edges: {sedges}\n")
    
    for i, mst in enumerate(find_all_msts(n, sedges)):
        if i == 0: print("MSTs:")
        print((i+1), ":", mst)

if __name__ == "__main__":
    initial = time.time()
    run_test_case(20, 35)
    print(f"\nRun time: {time.time() - initial}s")
AJ Donich
  • 31
  • 5
0

Ronald Rivest has a nice implementation in Python, mst.py

synthesizerpatel
  • 27,321
  • 5
  • 74
  • 91
  • 5
    This is an implementation that finds minimum spanning trees, as far as I can tell. Not _all_ spanning trees. – rjkaplan Dec 11 '11 at 04:38