All minimum spanning trees implementation
Asked Answered
C

4

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!

Cot answered 29/5, 2010 at 16:17 Comment(8)
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?Bra
I'm implementing an algorithm from this paper (linkinghub.elsevier.com/retrieve/pii/S1571065309002066), one of the required steps is to iterate through all mst.Cot
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?Unbalanced
I'm looking for the same thing. Did you?Velarde
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.Grolier
the method suggested above by @Bra i.e. generating all subsets isn't correct. you will find some subsets that represent graphs with cycles and therefore aren't trees.Adali
@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.Bra
I have found this 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.Bra
F
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

Freeborn answered 29/5, 2010 at 23:38 Comment(6)
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.Cot
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.Freeborn
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.Cot
@Cot have you implemented any of these algorithms?Dominate
@Freeborn How do you go about generating all Spanning Trees? And how would you compute their total weight? I couldn't find anything for this.Mcdonough
So do we have implementations after a decade? 😅Adali
H
0

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

Histrionic answered 8/12, 2011 at 13:43 Comment(1)
This is an implementation that finds minimum spanning trees, as far as I can tell. Not all spanning trees.Unbalanced
G
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.

Greensward answered 22/1, 2016 at 9:11 Comment(0)
B
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")
Benford answered 10/10, 2022 at 20:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.