log in
register

[Algorithm] Graph in python

Qingqi@2021-04-11 #algorithm and data structure

# references:
#   https://runestone.academy/runestone/books/published/pythonds/Graphs/toctree.html


from pythonds.graphs import Graph, Vertex, PriorityQueue
import sys


# directed acyclic graph (DAG)

# 1. Adjacency Matrix
# 2. Adjacency List


class Vertex(object):
    dist = None
    prev = None
    color = 'white'

    def __init__(self, key):
        self.id = key
        self.connected_to = {}

    # nbr: neighbor
    def add_nbr(self, nbr, weight=0):
        self.connected_to[nbr] = weight


class Graph(object):
    def __init__(self):
        self.vertexes = {}

    def add_vertex(self, key):
        self.vertexes[key] = Vertex(key)

    def add_edge(self, key_from, key_toward, weight=0):
        if key_from not in self.vertexes:
            self.add_vertex(key_from)
        if key_toward not in self.vertexes:
            self.add_vertex(key_toward)
        self.vertexes[key_from].add_nbr(self.vertexes[key_toward], weight)

    def __iter__(self):
        return iter(self.vertexes.values())


# g = Graph()
# for i in range(6):
#     g.add_vertex(i)
# g.vertexes
# g.add_edge(0, 1, 5)
# g.add_edge(0, 5, 2)
# g.add_edge(1, 2, 4)
# g.add_edge(2, 3, 9)
# g.add_edge(3, 4, 7)
# g.add_edge(3, 5, 3)
# g.add_edge(4, 0, 1)
# g.add_edge(5, 4, 8)
# g.add_edge(5, 2, 1)
# for v in g:
#     for w in v.connected_to.keys():
#         print("( %s , %s )" % (v.id, w.id))


# Word Ladder Problem
def build_graph(word_list):
    d = {}
    g = Graph()
    # create buckets of words that differ by one letter
    for word in word_list:
        for i in range(len(word)):
            bucket = word[:i] + '_' + word[i+1:]
            if bucket in d:
                d[bucket].append(word)
            else:
                d[bucket] = [word]
    # add vertices and edges for words in the same bucket
    for bucket in d.keys():
        for word1 in d[bucket]:
            for word2 in d[bucket]:
                if word1 != word2:
                    g.add_edge(word1, word2)
    return g


word_list = ['foil', 'fail', 'fall', 'pall', 'poll', 'pool',
             'cool', 'fool', 'foul', 'pole', 'pope', 'pale',
             'sale', 'page', 'sage']
g = build_graph(word_list)


# Breadth First Search (BFS)
# start is a Vertex
def bfs(start):
    start.dist = 0
    # queue, first-in-first-out
    vert_queue = []
    vert_queue.append(start)
    while (len(vert_queue) > 0):
        vert_curr = vert_queue.pop()
        for nbr in vert_curr.connected_to:
            if nbr.color == 'white':
                nbr.color = 'gray'
                nbr.dist = vert_curr.dist + 1
                nbr.pred = vert_curr
                vert_queue.append(nbr)
        vert_curr.color = 'black'


bfs(g.vertexes['fool'])
g.vertexes['sage'].dist


# Knight’s Tour

# Building the Graph
def knightGraph(bdSize):
    ktGraph = Graph()
    for row in range(bdSize):
        for col in range(bdSize):
            nodeId = posToNodeId(row, col, bdSize)
            newPositions = genLegalMoves(row, col, bdSize)
            for e in newPositions:
                nid = posToNodeId(e[0], e[1], bdSize)
                ktGraph.addEdge(nodeId, nid)
    return ktGraph


def posToNodeId(row, column, board_size):
    return (row * board_size) + column


def genLegalMoves(x, y, bdSize):
    newMoves = []
    moveOffsets = [(-1, -2), (-1, 2), (-2, -1), (-2, 1),
                   (1, -2), (1, 2), (2, -1), (2, 1)]
    for i in moveOffsets:
        newX = x + i[0]
        newY = y + i[1]
        if (legalCoord(newX, bdSize) and
                legalCoord(newY, bdSize)):
            newMoves.append((newX, newY))
    return newMoves


def legalCoord(x, bdSize):
    if x >= 0 and x < bdSize:
        return True
    else:
        return False


# Implementing the Tour
# depth first search (DFS)
# n: the current depth in the search tree
# path: a list of vertices visited up to this point
# u: the vertex in the graph we wish to explore
# limit: the number of nodes in the path
def knightTour(n, path, u, limit):
    # visited vertices are colored gray
    u.setColor('gray')
    path.append(u)
    if n < limit:
        nbrList = list(u.getConnections())
        i = 0
        done = False
        # keep going
        while i < len(nbrList) and not done:
            if nbrList[i].getColor() == 'white':
                # self call
                done = knightTour(n+1, path, nbrList[i], limit)
            i = i + 1
        if not done:  # prepare to backtrack
            path.pop()
            u.setColor('white')
    # exit
    else:
        done = True
    return done


# inplace the call to u.getConnections
def orderByAvail(n):
    resList = []
    for v in n.getConnections():
        if v.getColor() == 'white':
            c = 0
            for w in v.getConnections():
                if w.getColor() == 'white':
                    c = c + 1
            resList.append((c, v))
    resList.sort(key=lambda x: x[0])
    return [y[1] for y in resList]


# DFS: General Depth First Search
class DFSGraph(Graph):
    def __init__(self):
        super().__init__()
        self.time = 0

    def dfs(self):
        for aVertex in self:
            aVertex.setColor('white')
            aVertex.setPred(-1)
        for aVertex in self:
            if aVertex.getColor() == 'white':
                self.dfsvisit(aVertex)

    def dfsvisit(self, startVertex):
        startVertex.setColor('gray')
        self.time += 1
        startVertex.setDiscovery(self.time)
        for nextVertex in startVertex.getConnections():
            if nextVertex.getColor() == 'white':
                nextVertex.setPred(startVertex)
                self.dfsvisit(nextVertex)
        startVertex.setColor('black')
        self.time += 1
        startVertex.setFinish(self.time)


# TODO: Dijkstra's algorithm
def dijkstra(aGraph, start):
    pq = PriorityQueue()
    start.setDistance(0)
    pq.buildHeap([(v.getDistance(), v) for v in aGraph])
    while not pq.isEmpty():
        currentVert = pq.delMin()
        for nextVert in currentVert.getConnections():
            newDist = currentVert.getDistance() \
                    + currentVert.getWeight(nextVert)
            if newDist < nextVert.getDistance():
                nextVert.setDistance(newDist)
                nextVert.setPred(currentVert)
                pq.decreaseKey(nextVert, newDist)


# TODO: Prim's algorithm
def prim(G, start):
    pq = PriorityQueue()
    for v in G:
        v.setDistance(sys.maxsize)
        v.setPred(None)
    start.setDistance(0)
    pq.buildHeap([(v.getDistance(), v) for v in G])
    while not pq.isEmpty():
        currentVert = pq.delMin()
        for nextVert in currentVert.getConnections():
            newCost = currentVert.getWeight(nextVert)
            if nextVert in pq and newCost < nextVert.getDistance():
                nextVert.setPred(currentVert)
                nextVert.setDistance(newCost)
                pq.decreaseKey(nextVert, newCost)

Comments

Log in to add your comment

Don't have an account? register here