Skip to content

Commit

Permalink
chap14: prim-janik, kruskal and disjoint sets
Browse files Browse the repository at this point in the history
  • Loading branch information
AliNisarAhmed committed Sep 10, 2023
1 parent 950f5d0 commit 8700a4e
Show file tree
Hide file tree
Showing 7 changed files with 170 additions and 6 deletions.
2 changes: 1 addition & 1 deletion python/python-algo-ds/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__package__ = ''
__package__ = 'python_algo_ds'
121 changes: 119 additions & 2 deletions python/python-algo-ds/chap14/graph.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
from ..chap9.adaptable_pq import AdaptableHeapPriorityQueue
from chap9.adaptable_pq import AdaptableHeapPriorityQueue, HeapPriorityQueue
from .partition import Partition
import pprint


class Vertex:
Expand All @@ -14,6 +16,9 @@ def element(self):
def __hash__(self):
return hash(id(self))

def __repr__(self):
return repr(self._element)


class Edge:
__slots__ = '_origin', '_destination', '_element'
Expand All @@ -37,6 +42,9 @@ def element(self):
def __hash__(self):
return hash((self._origin, self._destination))

def __repr__(self):
return repr(self._origin) + '-' + repr(self._destination)


class Graph:
"""Representation of a simple graph using an adjacency map"""
Expand Down Expand Up @@ -99,9 +107,10 @@ def insert_vertex(self, x=None):
return v

def insert_edge(self, u, v, x=None):
"""insert_edge(origin, destination, weight)"""
e = Edge(u, v, x)
self._outgoing[u][v] = e
self._incoming[u][v] = e
self._incoming[v][u] = e


def topological_sort(g: Graph):
Expand Down Expand Up @@ -195,3 +204,111 @@ def shortest_path_tree(g: Graph, s: Vertex, d):
if d[v] == d[u] + weight:
tree[v] = e # edge e is used to reach v
return tree


def MST_PrimJarnik(g: Graph):
"""Compute a min spanning tree of weighted graph g
Return a list of edges that comprise the MST (in arbitrary order)
"""

d = {} # d[v] is bound on distance to tree
tree = [] # list of edges in spanning tree
pq = AdaptableHeapPriorityQueue() # d[v] maps to value (v, e=(u,v))
pqlocator = {} # map from vertex to its pq locator

for v in g.vertices():
if len(d) == 0:
d[v] = 0
else:
d[v] = float('inf')
pqlocator[v] = pq.add(d[v], (v, None))

while not pq.is_empty():
key, value = pq.remove_min()
u, edge = value
del pqlocator[u] # u is no longer in pq

if edge is not None:
tree.append(edge)

for link in g.incident_edges(u):
v = link.opposite(u)
if v in pqlocator: # thus v not yet in tree
# see if edge (u,v) better connects v to the growing tree
weight = link.element()
if weight < d[v]:
d[v] = weight
pq.update(pqlocator[v], d[v], (v, link))
return tree


def MST_Kruskal(g: Graph):
"""Compute a min spanning tree of a graph
Return a list of edges that comprise the MST
The elements of the grah's edges are assumed to be weights
"""
tree = [] # list of edges in the MST
pq = HeapPriorityQueue() # entries are edges in G, with weights as keys
forest = Partition() # keeps track of forest of clusters
position = {} # map each node to its partition key

for v in g.vertices():
position[v] = forest.make_group(v)

for e in g.edges():
pq.add(e.element(), e) # edge's element is assumed to be its weight

size = g.vertex_count()

while len(tree) != size - 1 and not pq.is_empty():
# tree not spanning and unprocessed edges remain
weight, edge = pq.remove_min()
u, v = edge.endpoints()
a = forest.find(position[u])
b = forest.find(position[v])
if a != b:
tree.append(edge)
forest.union(a, b)
return tree


if __name__ == "__main__":
g = Graph()
jfk = g.insert_vertex('JFK')
pvd = g.insert_vertex("PVD")
bos = g.insert_vertex("BOS")
bwi = g.insert_vertex("BWI")
mia = g.insert_vertex("MIA")
ord = g.insert_vertex("ORD")
dfw = g.insert_vertex("DFW")
sfo = g.insert_vertex("SFO")
lax = g.insert_vertex("LAX")

g.insert_edge(bwi, jfk, 184)
g.insert_edge(bwi, ord, 621)
g.insert_edge(bwi, mia, 946)
g.insert_edge(jfk, dfw, 1391)
g.insert_edge(jfk, pvd, 144)
g.insert_edge(jfk, ord, 740)
g.insert_edge(jfk, mia, 1090)
g.insert_edge(jfk, bos, 187)
g.insert_edge(pvd, ord, 849)
g.insert_edge(bos, mia, 1258)
g.insert_edge(bos, sfo, 2704)
g.insert_edge(bos, ord, 867)
g.insert_edge(mia, dfw, 1121)
g.insert_edge(mia, lax, 2342)
g.insert_edge(dfw, ord, 802)
g.insert_edge(dfw, sfo, 1464)
g.insert_edge(dfw, lax, 1235)
g.insert_edge(lax, sfo, 337)
g.insert_edge(sfo, ord, 1846)

d = shortest_path_lengths(g, bwi)
pprint.pprint(d)
pprint.pprint(shortest_path_tree(g, bwi, d))

pprint.pp(MST_PrimJarnik(g))
42 changes: 42 additions & 0 deletions python/python-algo-ds/chap14/partition.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
class Partition:
"""Union-find structure for maintaining disjoint sets"""

# ---- Nested Position class ----

class Position:
__slots__ = '_container', '_element', '_size', '_parent'

def __init__(self, container, e):
"""Create a new position that is the leader of its own group
"""
self._container = container
self._element = e
self._size = 1
self._parent = self # convention for group leader

def element(self):
return self._element

def make_group(self, e):
"""Makes a new group containing element e, and returns its Position"""
return self.Position(self, e)

def find(self, p):
"""Finds the group containing p and return the leader's position"""
if p._parent != p:
# overwrite p._parent after recursion
p._parent = self.find(p._parent)
return p._parent

def union(self, p, q):
"""Merges the groups containing element p & q (if distinct)"""
a = self.find(p)
b = self.find(q)

if a is not b:
if a._size > b._size:
b._parent = a
a._size += b._size
else:
a._parent = b
b._size += a._size
4 changes: 4 additions & 0 deletions python/python-algo-ds/chap14/pathmagic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
1 change: 1 addition & 0 deletions python/python-algo-ds/chap9/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__package__ = 'chap9'
4 changes: 2 additions & 2 deletions python/python-algo-ds/chap9/adaptable_pq.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from heap_based_pq import HeapPriorityQueue
from .heap_based_pq import HeapPriorityQueue


class AdaptableHeapPriorityQueue(HeapPriorityQueue):
Expand All @@ -23,7 +23,7 @@ def __init__(self, k, v, j):
self._index = j

def _swap(self, i, j):
super._swap(i, j)
super()._swap(i, j)
self._data[i]._index = i # update locator index post-swap
self._data[j]._index = j # update locator index (post-swap)

Expand Down
2 changes: 1 addition & 1 deletion python/python-algo-ds/chap9/heap_based_pq.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
The height of a heap storing n entries is given by
- h = floor(log n)
"""
from priority_queue_base import PriorityQueueBase
from .priority_queue_base import PriorityQueueBase


class HeapPriorityQueue(PriorityQueueBase):
Expand Down

0 comments on commit 8700a4e

Please sign in to comment.