Graph Plotting and Spanning Trees¶

In [95]:
# this is how you can plot graphs:

%matplotlib inline
import matplotlib.pyplot as plt
import math
from collections import deque

plt.figure()
plt.plot((1,2),(1,1),'b-', hold=True)
plt.plot((1,2),(1,1),'ro', hold=True) #x1,x2,y1,y2
plt.xlim([0,3])
plt.ylim([0,3])
plt.show()
/usr/local/share/jupyterhub/env/jupyterhub/lib/python3.6/site-packages/matplotlib/pyplot.py:3316: MatplotlibDeprecationWarning: The 'hold' keyword argument is deprecated since 2.0.
  mplDeprecation)
No description has been provided for this image
In [1]:
class Graph(object):
    """Represents an undirected graph"""

    def __init__(self, vertices, edges):
        '''A graph is defined by its set of vertices
           and its set of edges.'''
        self.V = set(vertices) # The set of vertices
        self.E = set([])       # The set of edges
        self.adjacency_map = {}          # A dictionary that will hold the list
                               # of adjacent vertices for each vertex.
        self.vertex_coordinates = {}       # An optional dictionary that can hold coordinates
                               # for the vertices.
        self.edge_labels = {}  # a dictionary of labels for edges

        self.add_edges(edges)  # Note the call to add_edges will also
                               # update the adjacency_map dictionary
        print ('(Initializing a graph with %d vertices and %d edges)' % (len(self.V),len(self.E)))


    def add_vertices(self,vertex_list):
        """ This method will add the vertices in the vertex_list
            to the set of vertices for this graph. Since V is a
            set, duplicate vertices will not be added to V. """
        for v in vertex_list:
            self.V.add(v)
        self.build_adjacency_map()


    def add_edges(self,edge_list):
        """ This method will add a list of edges to the graph
            It will insure that the vertices of each edge are
            included in the set of vertices (and not duplicated).
            It will also insure that edges are added to the
            list of edges and not duplicated. """
        for s,t in edge_list:
            if (s,t) not in self.E and (t,s) not in self.E:
                self.V.add(s)
                self.V.add(t)
                self.E.add((s,t))
        self.build_adjacency_map()


    def build_adjacency_map(self):
        self.adjacency_map = {}
        for v in self.V:
            self.adjacency_map[v] = []
        for (s,t) in self.E:
            self.adjacency_map[s].append(t)
            self.adjacency_map[t].append(s)


    def degree_of(self, vertex):
        """ return the degree of the given vertex """
        if vertex in self.V:
            return len(self.adjacency_map[vertex])
        else:
            return None


    def get_a_vertex(self):
        """ returns some vertex of the graph """
        if 0 < len(self.V):
            v = self.V.pop()
            self.V.add(v)
            return v
        else:
            return None


    def plot(self):
        nV = len(self.V)
        if len(self.vertex_coordinates) != nV:
            # Coordinates have not been specified for every vertex, put them on a circle
            dTheta = 2*math.pi/nV
            k = 0
            for v in self.V:
                self.vertex_coordinates[v] = (10*math.cos(math.pi/2-k*dTheta),10*math.sin(math.pi/2-k*dTheta))
                k += 1
        px = []
        py = []
        for v in self.V:
            px.append(self.vertex_coordinates[v][0])
            py.append(self.vertex_coordinates[v][1])
        plt.plot(px,py,'bo',hold=True)
        for vertex in self.V:
            p = self.vertex_coordinates[vertex]
            pq = max(0.1,math.sqrt(p[0]**2 + p[1]**2))
            rx = p[0]/pq
            ry = p[1]/pq
            plt.text(p[0]+0.2*rx, p[1]+0.2*ry, str(vertex))
        for s,t in self.E:
            plt.plot([self.vertex_coordinates[s][0], self.vertex_coordinates[t][0]],
                     [self.vertex_coordinates[s][1], self.vertex_coordinates[t][1]],
                     'b',hold=True)
            if (s,t) in self.edge_labels:
                label = self.edge_labels[(s,t)]
                plt.text((self.vertex_coordinates[s][0]+self.vertex_coordinates[t][0])/2-0.1,
                         (self.vertex_coordinates[s][1]+self.vertex_coordinates[t][1])/2-0.1, label)
        plt.xlim(min(px)-1.0, max(px)+1.1)
        plt.ylim(min(py)-1.0, max(py)+1.1)

    def get_a_component_spanning_tree(self, root):
        """ This routine uses a breadth-first search
        to obtain a tree that spans the component
        containing root """
        spanning_tree = []
        visited = {}
        for v in self.V:
            visited[v] = False
        Q = deque()
        visited[root] = True
        Q.append(root)
        while 0 < len(Q):
            v = Q.popleft()
            for u in self.adjacency_map[v]:
                if not visited[u]:
                    visited[u] = True
                    Q.append(u)
                    spanning_tree.append((v,u))
        return spanning_tree


    def is_connected(self):
        """ If the graph is connected then if the tree
        returned by get_a_component_spanning_tree has
        nV-1 edges - that is, it spans the graph."""
        root = self.get_a_vertex()
        tree = self.get_a_component_spanning_tree(root)
        if len(tree) == len(self.V)-1:
            return True
        else:
            return False
In [3]:
V = ['A', 'B', 'C', 'D', 'E', 'F']
E = [('A','E'),('C','F'),('B','D'),('A','F'),('C','E'),('C','D')]
G1 = Graph(V,E)
print ('Vertices:',G1.V)
print ('Edges:',G1.E)
print ('adjacency:',G1.adjacency_map)
G1.edge_labels[('C','F')] = "hello"

G1.vertex_coordinates = {'A':(1,10),'B':(5,5),'C':(1,-10),'D':(5,-5),'E':(2,1),'F':(-5,1)}
G1.plot()
tree = G1.get_a_component_spanning_tree('A')
print ("spanning tree of F",tree)

print ("Is G1 connected?", G1.is_connected())
#
for edge in tree:
    s,t = edge
    xs,ys = G1.vertex_coordinates[s]
    xt,yt = G1.vertex_coordinates[t]
    plt.plot([xs,xt],[ys,yt],
             'r--',hold=True)
(Initializing a graph with 6 vertices and 6 edges)
Vertices: {'E', 'F', 'C', 'D', 'A', 'B'}
Edges: {('A', 'E'), ('C', 'F'), ('A', 'F'), ('B', 'D'), ('C', 'E'), ('C', 'D')}
adjacency: {'E': ['A', 'C'], 'F': ['C', 'A'], 'C': ['F', 'E', 'D'], 'D': ['B', 'C'], 'A': ['E', 'F'], 'B': ['D']}
spanning tree of F [('A', 'E'), ('A', 'F'), ('E', 'C'), ('C', 'D'), ('D', 'B')]
Is G1 connected? True
/usr/local/share/jupyterhub/env/jupyterhub/lib/python3.6/site-packages/matplotlib/pyplot.py:3316: MatplotlibDeprecationWarning: The 'hold' keyword argument is deprecated since 2.0.
  mplDeprecation)
No description has been provided for this image
In [ ]:
 
In [97]:
class PriorityQueue():
    '''
    The arguments passed to a PriorityQueue must consist of
    a priority and an object.  It must be possible to compare
    priorities using the < operator.
    
    push(x) adds x to the priority queue
    pop() removes and return the item with the smallest value
    '''

    def __init__(self):
        self._pq = []

    def _parent(self,n):
        return (n-1)//2

    def _leftchild(self,n):
        return 2*n + 1

    def _rightchild(self,n):
        return 2*n + 2

    def push(self, priority, object):
        self._pq.append((priority, object))
        n = len(self._pq) 
        self._bubble_up(n-1)

    def _bubble_up(self,c):
        while 0<c:
            c_item = self._pq[c]
            p = self._parent(c)
            p_item = self._pq[p]
            # since the parent of 0 is 0, so use "<"
            if c_item < p_item:
                self._pq[p] = c_item
                self._pq[c] = p_item
                c = p
            else:
                break

    def pop(self):
        n = len(self._pq)
        if n==0:
            obj = None
        elif n==1:
            pri,obj = self._pq.pop()
        else:
            pri,obj = self._pq[0]
            self._pq[0] = self._pq.pop()
            self._sift_down(0)
        return obj

    def _sift_down(self,p):
        n = len(self._pq)
        while p<n:
            p_item = self._pq[p]
            lc = self._leftchild(p)
            if n <= lc:
                break
            c_item = self._pq[lc]
            c = lc
            rc = self._rightchild(p)
            if rc < n:
                r_item = self._pq[rc]
                if r_item < c_item:
                    c_item = r_item
                    c = rc
            if p_item <= c_item:
                break
            self._pq[p] = c_item
            self._pq[c] = p_item
            p = c

    def is_empty(self):
        return 0 == len(self._pq)
In [14]:
def Kruskal_version1(G, weights):
    ''' Uses the Kruskal algorithm to find the minimum
        weighted tree of a connected graph.
        If the graph is not connected, the algorithm will
        return the minimum weighted forest.

        G is the graph, and weights is a dictionary of edges and their weights.
        The edges are the keys of the dictionary and the weights are the values.

        Version 1 (not efficient)
    '''

    PQ = PriorityQueue()
    for edge in weights:
        w = weights[edge]
        PQ.push(w,edge)
    color = {}
    for vertex in G.V:
        color[vertex] = 0
    nV = len(G.V)
    next_color = 1
    acyclic_subgraph = []
    weight = 0
    while not PQ.is_empty() and len(acyclic_subgraph)<(nV-1):
        edge = PQ.pop()
        s,t = edge
        if color[s] == 0 and color[t] == 0:
            #1. Add a new tree to the forest
            color[s] = next_color
            color[t] = next_color
            next_color += 1
            acyclic_subgraph.append(edge)
            weight += weights[edge]

        elif color[s] == 0 or color[t] == 0:
            #2. Add this edge to the existing tree
            if color[s] == 0:
                color[s] = color[t]
            else:
                color[t] = color[s]
            acyclic_subgraph.append(edge)
            weight += weights[edge]

        elif color[s] == color[t]:
            #3. Skip this edge since it would form a cycle
            pass

        else: # color[s] != 0, color[t] != 0, and color[s] != color[t]
            #4. Graft one tree onto the other
            if color[s] < color[t]:
                new_color = color[s]
                old_color = color[t]
            else:
                new_color = color[t]
                old_color = color[s]
            for v in G.V:
                #** Note that this step examines every vertex  ***
                #** each time one tree is grafted onto another ***
                if color[v] == old_color:
                    color[v] = new_color
            acyclic_subgraph.append(edge)
            weight += weights[edge]

    # We have examined all the edges and found the
    # largest minimum weighted acyclic subgraph.
    # If the graph is connected then it is the MST.
    return acyclic_subgraph, weight
In [98]:
V = ['A', 'B', 'C', 'D', 'E', 'F']
E = [('A','E'),('C','F'),('B','D'),('A','F'),('C','E'),('C','D')]
W1 = {('A','E'):18, ('C','F'):21, ('B','D'):14, ('A','F'):19, ('C','E'):16, ('C','D'):1}
G1 = Graph(V,E)
G1.vertex_coordinates = {'A':(1,10),'B':(5,5),'C':(1,-10),'D':(5,-5),'E':(2,1),'F':(-5,1)}
G1.edge_labels = W1
G1.plot()

tree,weight = Kruskal_version1(G1,W1)
if len(tree)==(len(G1.V)-1):
    print ('Minimum Spanning Tree:',tree)
else:
    print ('Disconnected Acyclic Subgraph:',tree)
    print ('Weight:',weight)
for edge in tree:
    s,t = edge
    xs,ys = G1.vertex_coordinates[s]
    xt,yt = G1.vertex_coordinates[t]
    plt.plot([xs,xt],[ys,yt],
             'r--',hold=True)
(Initializing a graph with 6 vertices and 6 edges)
Minimum Spanning Tree: [('C', 'D'), ('B', 'D'), ('C', 'E'), ('A', 'E'), ('A', 'F')]
/usr/local/share/jupyterhub/env/jupyterhub/lib/python3.6/site-packages/matplotlib/pyplot.py:3316: MatplotlibDeprecationWarning: The 'hold' keyword argument is deprecated since 2.0.
  mplDeprecation)
No description has been provided for this image
In [31]:
V = ['A', 'B', 'C', 'D', 'E', 'F']
E = [('A','B'),('A','E'),('C','F'),('B','D'),('C','D'),('A','F'),('C','E')]
W1 = {('A','B'):7, ('A','E'):18, ('C','F'):21, ('C','D'):13, ('B','D'):14, ('A','F'):19, ('C','E'):16}
G1 = Graph(V,E)
G1.vertex_coordinates = {'A':(1,10),'B':(5,5),'C':(1,-10),'D':(5,-5),'E':(2,1),'F':(-5,1)}
G1.edge_labels = W1
G1.plot()

tree,weight = Kruskal_version1(G1,W1)
if len(tree)==(len(G1.V)-1):
    print ('Minimum Spanning Tree:',tree)
else:
    print ('Disconnected Acyclic Subgraph:',tree)
    print ('Weight:',weight)
for edge in tree:
    s,t = edge
    xs,ys = G1.vertex_coordinates[s]
    xt,yt = G1.vertex_coordinates[t]
    plt.plot([xs,xt],[ys,yt],
             'r--',hold=True)
(Initializing a graph with 6 vertices and 7 edges)
Minimum Spanning Tree: [('A', 'B'), ('C', 'D'), ('B', 'D'), ('C', 'E'), ('A', 'F')]
/usr/local/share/jupyterhub/env/jupyterhub/lib/python3.6/site-packages/matplotlib/pyplot.py:3316: MatplotlibDeprecationWarning: The 'hold' keyword argument is deprecated since 2.0.
  mplDeprecation)
No description has been provided for this image

Question 1¶

  1. implement print_tree() that prints a line of text for each vertex v with arrows how you reach the root if you use find_set(v). Example (from problem above):
A -> B -> D -> E -> F
C -> D -> E -> F
  1. Implement a function height() that returns the height of the tallest tree (number of edges in the longest chain in the function print_tree())
  2. Improve the function print_sets() to print each disjoint set grouped using parentheses like this:
(A D C) (B) (E F)
In [139]:
class DisjointSet(object):
    class Link(object):
        def __init__(self,x):
            self.item = x
            self.next = None
            
    def __init__(self):
        self.links = {} # maps each item to its Link
        
    def insert(self, vertices):
        """ insert the given vertices as isolated sets """
        for v in vertices:
            self.links[v] = self.Link(v)
    
    def find_set(self, s):
        """ return representative object (find and return the root item) """
        l = self.links[s]
        while l.next != None:
            l = l.next
        return l.item
    
    def union(self, s, t):
        """ take the union of the sets than contain s and t """
        root_s = self.find_set(s)
        root_t = self.find_set(t)
        if root_s != root_t:
            # attach root(s) to root(t):
            self.links[root_s].next = self.links[root_t]
            
    '''
    def print_sets(self):        
        for item in self.links:
            root = self.find_set(item)
            if root == item:
                print(item, end=" ")
            else:
                print("%s(root:%s)"%(item,root), end=" ")
        print("")
    '''
    
    def print_sets(self):  
        rootsMaps = {}
        for item in self.links:
            root = self.find_set(item)
            if root in rootsMaps:
                rootsMaps[root].append(item)
            else:
                rootsMaps[root] = [item]

        for root in rootsMaps:
            print("(" + rootsMaps[root][0], end = "")
            rootsMaps[root].pop(0)
            
            for disjointVertexSet in rootsMaps[root]:
                print(" " + disjointVertexSet, end = "")
            print(")", end = " ")
        print("")

    def print_tree(self):        
        for item in self.links:
            root = self.find_set(item)
            l = self.links[item]
            print(l.item,end=" ")
            while l.next != None:
                l = l.next
                print('-> ' + l.item,end=" ")  
            print("")

    def height(self):
        height = -1
        for item in self.links:
            root = self.find_set(item)
            l = self.links[item]
            n = 0
            while l.next != None:
                l = l.next
                n += 1
            if n > height:
                height = n
        return height
    
ds = DisjointSet()
ds.insert(["A","B","C","D","X"])
ds.print_sets()
ds.union("A","C")
ds.print_sets()
ds.union("D","C")
ds.print_sets()
ds.union("A","B")
ds.print_sets()
ds.print_tree()
print("root(A):", ds.find_set("A"))
print("A joined with C?", ds.find_set("A")==ds.find_set("C"))
print("B joined with X?", ds.find_set("B")==ds.find_set("X"))
print ("height:",ds.height())
        
(A) (B) (C) (D) (X) 
(A C) (B) (D) (X) 
(A C D) (B) (X) 
(A B C D) (X) 
A -> C -> B 
B 
C -> B 
D -> C -> B 
X 
root(A): B
A joined with C? True
B joined with X? False
height: 2
In [100]:
def KruskalDisjointSet(G, W, ds):
    ''' Uses the Kruskal algorithm to find the minimum
        weighted tree of a connected graph.
        If the graph is not connected, the algorithm will
        return the minimum weighted forest.

        G is the graph, and W is a dictionary of edges and their weights, 
        ds is an empty DisjointSet
    '''

    PQ = PriorityQueue()
    for edge in W:
        w = W[edge]
        PQ.push(w,edge)
    
    # initialize disjoint set:
    ds.insert(G.V)
    
    acyclic_subgraph = []
    weight = 0
    n_v = len(G.V)
    while not PQ.is_empty() and len(acyclic_subgraph)<(n_v-1):
        edge = PQ.pop()
        s,t = edge
        if ds.find_set(s) != ds.find_set(t):
            # s and t are in different sets
            ds.union(s,t)
            acyclic_subgraph.append(edge)
            weight += W[edge]

    return acyclic_subgraph, weight
In [85]:
V = ['A', 'B', 'C', 'D', 'E', 'F']
E = [('A','B'),('A','E'),('C','F'),('B','D'),('C','D'),('A','F'),('C','E')]
W1 = {('A','B'):7, ('A','E'):18, ('C','F'):21, ('C','D'):13, ('B','D'):14, ('A','F'):19, ('C','E'):16}
G1 = Graph(V,E)
G1.vertex_coordinates = {'A':(1,10),'B':(5,5),'C':(1,-10),'D':(5,-5),'E':(2,1),'F':(-5,1)}
G1.edge_labels = W1
G1.plot()

ds = DisjointSet()
tree,weight = KruskalDisjointSet(G1,W1,ds)
for edge in tree:
    s,t = edge
    xs,ys = G1.vertex_coordinates[s]
    xt,yt = G1.vertex_coordinates[t]
    plt.plot([xs,xt],[ys,yt],
             'r--',hold=True)

ds.print_tree()
print ("height:", ds.height())
(Initializing a graph with 6 vertices and 7 edges)
B -> D -> E -> F 
C -> D -> E -> F 
A -> B -> D -> E -> F 
E -> F 
F 
D -> E -> F 
height: 4
/usr/local/share/jupyterhub/env/jupyterhub/lib/python3.6/site-packages/matplotlib/pyplot.py:3316: MatplotlibDeprecationWarning: The 'hold' keyword argument is deprecated since 2.0.
  mplDeprecation)
No description has been provided for this image

Question 2¶

Implement a class DisjointSetWeightedQuickUnion (by copying DisjointSet) that keeps track of the weight (height of the tree at each root node) and attaches the smaller to the taller tree. Execute the block below and check that the height is much smaller.

In [164]:
class DisjointSetWeightedQuickUnion(object):
    class Link(object):
        def __init__(self,x):
            self.item = x
            self.next = None
            
    def __init__(self):
        self.heightsMap = {}
        self.links = {} # maps each item to its Link
        
    def insert(self, vertices):
        """ insert the given vertices as isolated sets """
        for v in vertices:
            self.links[v] = self.Link(v)
            self.heightsMap[v] = 0
    
    def find_set(self, s):
        """ return representative object (find and return the root item) """
        l = self.links[s]
        while l.next != None:
            l = l.next
        return l.item
    
    def union(self, s, t):
        """ take the union of the sets than contain s and t """
        root_s = self.find_set(s)
        root_t = self.find_set(t)

        if root_s != root_t:
            # attach root(s) to root(t):
            if self.heightsMap[root_s] < self.heightsMap[root_t]:
                self.links[root_s].next = self.links[root_t]
            elif self.heightsMap[root_s] > self.heightsMap[root_t]:
                self.links[root_t].next = self.links[root_s]
            else:
                self.links[root_s].next = self.links[root_t]
                self.heightsMap[root_s] += 1
                self.heightsMap[root_t] += 1

    '''
    def print_sets(self):        
        for item in self.links:
            root = self.find_set(item)
            if root == item:
                print(item, end=" ")
            else:
                print("%s(root:%s)"%(item,root), end=" ")
        print("")
    '''
    
    def print_sets(self):  
        rootsMaps = {}
        for item in self.links:
            root = self.find_set(item)
            if root in rootsMaps:
                rootsMaps[root].append(item)
            else:
                rootsMaps[root] = [item]

        for root in rootsMaps:
            print("(" + rootsMaps[root][0], end = "")
            rootsMaps[root].pop(0)
            
            for disjointVertexSet in rootsMaps[root]:
                print(" " + disjointVertexSet, end = "")
            print(")", end = " ")
        print("")

    def print_tree(self):        
        for item in self.links:
            root = self.find_set(item)
            l = self.links[item]
            print(l.item,end=" ")
            while l.next != None:
                l = l.next
                print('-> ' + l.item,end=" ")  
            print("")

    def height(self):
        height = -1
        for item in self.links:
            root = self.find_set(item)
            l = self.links[item]
            n = 0
            while l.next != None:
                l = l.next
                n += 1
            if n > height:
                height = n
        return height
In [165]:
V = ['A', 'B', 'C', 'D', 'E', 'F']
E = [('A','B'),('A','E'),('C','F'),('B','D'),('C','D'),('A','F'),('C','E')]
W1 = {('A','B'):7, ('A','E'):18, ('C','F'):21, ('C','D'):13, ('B','D'):14, ('A','F'):19, ('C','E'):16}
G1 = Graph(V,E)
G1.vertex_coordinates = {'A':(1,10),'B':(5,5),'C':(1,-10),'D':(5,-5),'E':(2,1),'F':(-5,1)}
G1.edge_labels = W1
G1.plot()

ds = DisjointSetWeightedQuickUnion()
tree,weight = KruskalDisjointSet(G1,W1,ds)
for edge in tree:
    s,t = edge
    xs,ys = G1.vertex_coordinates[s]
    xt,yt = G1.vertex_coordinates[t]
    plt.plot([xs,xt],[ys,yt],
             'r--',hold=True)

ds.print_tree()
print ("height:", ds.height())
(Initializing a graph with 6 vertices and 7 edges)
B -> D 
C -> D 
A -> B -> D 
E -> D 
F -> D 
D 
height: 2
/usr/local/share/jupyterhub/env/jupyterhub/lib/python3.6/site-packages/matplotlib/pyplot.py:3316: MatplotlibDeprecationWarning: The 'hold' keyword argument is deprecated since 2.0.
  mplDeprecation)
No description has been provided for this image

Question 3: A more complicated example¶

find a minimum spanning tree and draw it

In [173]:
WPM =  {('A', 'B'):  2, ('A', 'C'):  8, ('A', 'D'): 10, ('A', 'E'):  5,\
  ('B', 'D'): 14, ('B', 'J'): 17, ('C', 'D'): 12, ('C', 'G'):  3,\
  ('C', 'H'): 10, ('D', 'H'):  4, ('E', 'F'):  2, ('E', 'N'):  6,\
  ('F', 'G'):  6, ('F', 'O'):  6, ('G', 'O'):  4, ('H', 'T'):  8,\
  ('I', 'J'):  8, ('I', 'K'):  4, ('I', 'L'):  6, ('J', 'M'): 10,\
  ('J', 'S'):  6, ('K', 'P'): 12, ('K', 'Q'): 12, ('L', 'M'): 12,\
  ('L', 'Q'):  8, ('M', 'R'):  8, ('N', 'O'):  2, ('N', 'V'):  4,\
  ('O', 'V'): 12, ('P', 'T'):  6, ('P', 'U'):  4, ('Q', 'R'): 10,\
  ('R', 'S'): 12, ('S', 'W'): 13, ('T', 'U'): 14, ('U', 'W'): 10,\
  ('V', 'W'):  2 }
PMV = ['A','B','C','D','E','F','G','H','I','J','K','L','M','N','P','Q','R','S','T','U','V','W']
PMcoord = {'B':(-3,3),'A':(0,3),'E':(3,3)}
PMcoord['J']=(-3,2); PMcoord['D']=(-1,2); PMcoord['C']=(1,2); PMcoord['F']=(2,2)
PMcoord['I']=(-1,1); PMcoord['H']=(0,1); PMcoord['G']=(1,1)
PMcoord['M']=(-2,0); PMcoord['L']=(-1,0); PMcoord['K']=(0,0); PMcoord['T']=(1,0); PMcoord['O']=(2,0); PMcoord['N']=(3,0)
PMcoord['R']=(-2,-1); PMcoord['Q']=(-1,-1); PMcoord['P']=(0,-1); PMcoord['U']=(2,-1)
PMcoord['S']=(-3,-2); PMcoord['W']=(0,-2); PMcoord['V']=(3,-2)
PMG = Graph([],WPM.keys())
PMG.vertex_coordinates = PMcoord
PMG.edge_labels = WPM
PMG.plot()

ds = DisjointSetWeightedQuickUnion()
tree,weight = KruskalDisjointSet(PMG,WPM,ds)
for edge in tree:
    s,t = edge  
    xs,ys = PMG.vertex_coordinates[s]
    xt,yt = PMG.vertex_coordinates[t]
    plt.plot([xs,xt],[ys,yt],
             'r--',hold=True)

ds.print_tree()
print ("height:", ds.height())
(Initializing a graph with 23 vertices and 37 edges)
/usr/local/share/jupyterhub/env/jupyterhub/lib/python3.6/site-packages/matplotlib/pyplot.py:3316: MatplotlibDeprecationWarning: The 'hold' keyword argument is deprecated since 2.0.
  mplDeprecation)
G -> O 
M -> R -> S -> O 
B -> F -> O 
O 
N -> O 
V -> W -> O 
S -> O 
I -> K -> S -> O 
J -> S -> O 
Q -> S -> O 
C -> G -> O 
A -> B -> F -> O 
H -> U -> O 
P -> U -> O 
R -> S -> O 
W -> O 
E -> F -> O 
T -> U -> O 
F -> O 
D -> H -> U -> O 
L -> K -> S -> O 
K -> S -> O 
U -> O 
height: 3
No description has been provided for this image
In [ ]: