# -*- coding: utf-8 -*-
"""
Created on Tue Jun 5 15:44:57 2018
@author: Administrator
"""
import numpy as np
import copy
# In[1]:
# graph adt
class graph:
"""Graph ADT"""
def __init__(self):
self.graph = {}
self.visited = {}
def append(self, vertexid, edge, weight):
"""add/update new vertex,edge,weight"""
if vertexid not in self.graph.keys():
self.graph[vertexid] = {}
self.visited[vertexid] = 0
if edge not in self.graph.keys():
self.graph[edge] = {}
self.visited[edge] = 0
self.graph[vertexid][edge] = weight
def reveal(self):
"""return adjacent list"""
return self.graph
def vertex(self):
"""return all vertices in the graph"""
return list(self.graph.keys())
def edge(self, vertexid):
"""return edge of a particular vertex"""
return list(self.graph[vertexid].keys())
def edge_reverse(self, vertexid):
"""return vertices directing to a particular vertex"""
return [i for i in self.graph if vertexid in self.graph[i]]
def weight(self, vertexid, edge):
"""return weight of a particular vertex"""
return (self.graph[vertexid][edge])
def order(self):
"""return number of vertices"""
return len(self.graph)
def visit(self, vertexid):
"""visit a particular vertex"""
self.visited[vertexid] = 1
def go(self, vertexid):
"""return the status of a particular vertex"""
return self.visited[vertexid]
def route(self):
"""return which vertices have been visited"""
return self.visited
def degree(self, vertexid):
"""return degree of a particular vertex"""
return len(self.graph[vertexid])
def mat(self):
"""return adjacent matrix"""
self.matrix = [[0 for _ in range(max(self.graph.keys()) + 1)] for i in range(max(self.graph.keys()) + 1)]
for i in self.graph:
for j in self.graph[i].keys():
self.matrix[i][j] = 1
return self.matrix
def remove(self, vertexid):
"""remove a particular vertex and its underlying edges"""
for i in self.graph[vertexid].keys():
self.graph[i].pop(vertexid)
self.graph.pop(vertexid)
def disconnect(self, vertexid, edge):
"""remove a particular edge"""
del self.graph[vertexid][edge]
def clear(self, vertexid=None, whole=False):
"""unvisit a particular vertex"""
if whole:
self.visited = dict(zip(self.graph.keys(), [0 for i in range(len(self.graph))]))
elif vertexid:
self.visited[vertexid] = 0
else:
assert False, "arguments must satisfy whole=True or vertexid=int number"
# In[2]:
# algorithms
#
def bfs(ADT, current):
"""Breadth First Search"""
# create a queue with rule of first-in-first-out
queue = []
queue.append(current)
while queue:
# keep track of the visited vertices
current = queue.pop(0)
ADT.visit(current)
for newpos in ADT.edge(current):
# visit each vertex once
if ADT.go(newpos) == 0 and newpos not in queue:
queue.append(newpos)
#
def bidir_bfs(ADT, start, end):
"""Bidirectional Breadth First Search"""
# create queues with rule of first-in-first-out
# queue1 for bfs from start
# queue2 for bfs from end
queue1 = []
queue1.append(start)
queue2 = []
queue2.append(end)
visited1 = []
visited2 = []
while queue1 or queue2:
# keep track of the visited vertices
if queue1:
current1 = queue1.pop(0)
visited1.append(current1)
ADT.visit(current1)
if queue2:
current2 = queue2.pop(0)
visited2.append(current2)
ADT.visit(current2)
# intersection of two trees
stop = False
for i in ADT.vertex():
if i in visited1 and i in visited2:
stop = True
break
if stop:
break
# do not revisit a vertex
for newpos in ADT.edge(current1):
if newpos not in visited1 and newpos not in queue1:
queue1.append(newpos)
for newpos in ADT.edge(current2):
if newpos not in visited2 and newpos not in queue2:
queue2.append(newpos)
#
def dfs_itr(ADT, current):
"""Depth First Search without Recursion"""
queue = []
queue.append(current)
# the loop is the backtracking part when it reaches cul-de-sac
while queue:
# keep track of the visited vertices
current = queue.pop(0)
ADT.visit(current)
# priority queue
smallq = []
# find children and add to the priority
for newpos in ADT.edge(current):
if ADT.go(newpos) == 0:
# if the child vertex has already been in queue
# move it to the frontline of queue
if newpos in queue:
queue.remove(newpos)
smallq.append(newpos)
queue = smallq + queue
#
def dfs(ADT, current):
"""Depth First Search"""
# keep track of the visited vertices
ADT.visit(current)
# the loop is the backtracking part when it reaches cul-de-sac
for newpos in ADT.edge(current):
# if the vertex hasnt been visited
# we call dfs recursively
if ADT.go(newpos) == 0:
dfs(ADT, newpos)
#
def dfs_topo_sort(ADT, current):
"""Topological sort powered by recursive DFS to get linear ordering"""
# keep track of the visited vertices
ADT.visit(current)
yield current
# the loop is the backtracking part when it reaches cul-de-sac
for newpos in ADT.edge(current):
# if the vertex hasnt been visited
# we call dfs recursively
if ADT.go(newpos) == 0:
yield from dfs_topo_sort(ADT, newpos)
#
def kahn(ADT):
"""Topological sort powered by Kahn's Algorithm"""
# find all edges
edges = []
for i in ADT.vertex():
for j in ADT.edge(i):
edges.append((i, j))
# find vertices with zero in-degree
start = [i[0] for i in edges]
end = [i[1] for i in edges]
queue = set(start).difference(set(end))
# in-degree for every vertex
in_degree = {}
for i in set(end):
in_degree[i] = end.count(i)
result = []
while queue:
# pop a random vertex with zero in-degree
current = queue.pop()
result.append(current)
# check its neighbors
for i in ADT.edge(current):
# update their in-degree
in_degree[i] -= 1
# add vertices with zero in-degree into the queue
if in_degree[i] == 0:
queue.add(i)
return result
#
def bfs_path(ADT, start, end):
"""Breadth First Search to find the path from start to end"""
# create a queue with rule of first-in-first-out
queue = []
queue.append(start)
# pred keeps track of how we get to the current vertex
pred = {}
while queue:
# keep track of the visited vertices
current = queue.pop(0)
ADT.visit(current)
for newpos in ADT.edge(current):
# visit each vertex once
if ADT.go(newpos) == 0 and newpos not in queue:
queue.append(newpos)
pred[newpos] = current
# traversal ends when the target is met
if current == end:
break
# create the path by backtracking
# trace the predecessor vertex from end to start
previous = end
path = []
while pred:
path.insert(0, previous)
if previous == start:
break
previous = pred[previous]
# note that if we cant go from start to end
# we may get inf for distance
# additionally,