Depth First Search and Breadth First Search
I am right in front of a ton of exams and I need to learn about
algorithms and data structures. When I read about pseudocode of Graph
traversal algorithms, I thought:
Why not actually implement them in a real programming language? So I
did so and now you can study my code now here. I guess this problem was
solved a thousand times before, but I learnt something and I hope my
approach has some uniqueness to it.
Additionlay, you can also generate a topological order after you traversed the whole Graph, which is a nice little extra.
If you want the most recent version of the code, you can visit its own Github repo here.
Well, here's the code. Just download and run it like this: python graph_traversal.py
# -*- coding: utf-8 -*-
__author__ = 'Nikolai Tschacher'
__version__ = '0.1'
__contact__ = 'admin@incolumitas.com'
import time
from collections import deque
"""
This is just a little representation of two basic graph traversal methods.
- Depth-First-Search
- Breadth-First-Search
It's by no means meant to be fast or performant. Rather it is for educational
purposes and to understand it better for myself.
"""
class Node(object):
"""Represents a node."""
def __init__(self, name):
self.name = name
self._visited = 0
self.discovery_time = None
self.finishing_time = None
def neighbors(self, adjacency_list):
return adjacency_list[self]
@property
def visited(self):
return self._visited
@visited.setter
def visited(self, value):
if value == 1:
self.discovery_time = time.clock()
elif value == 2:
self.finishing_time = time.clock()
self._visited = value
def __str__(self):
return str(self.name)
def __repr__(self):
return str(self.name)
# Let's define our sample graph and represent it in an adjacency list.
# This means that for every node, we store the outgoing edges in a list.
Nodes = [Node(i) for i in range(10)]
Graph = {
Nodes[0]: [Nodes[5], Nodes[3]],
Nodes[1]: [Nodes[8], Nodes[3]],
Nodes[2]: [Nodes[5]],
Nodes[3]: [Nodes[9], Nodes[8]],
Nodes[4]: [Nodes[5], Nodes[2]],
Nodes[5]: [Nodes[9]],
Nodes[6]: [Nodes[9]],
Nodes[7]: [Nodes[5], Nodes[2], Nodes[6]],
Nodes[8]: [Nodes[9], Nodes[4]],
Nodes[9]: [Nodes[0], Nodes[1]],
}
"""
Depth-First-Search
Running time: O(|V| + |E|)
"""
def depth_first_search(Graph, Nodes):
for node in Nodes:
node.visited = 0
for node in Nodes:
if node.visited == 0:
depth_first_search_visit(Graph, node)
def depth_first_search_visit(Graph, node):
node.visited = 1
for neighbor in node.neighbors(Graph):
if neighbor.visited == 0:
depth_first_search_visit(Graph, neighbor)
node.visited = 2
"""
Breadth-First-Search
"""
def breadth_first_search(Graph, Nodes):
for node in Nodes:
node.visited = 0
for node in Nodes:
if node.visited == 0:
breadth_first_search_visit(Graph, node)
def breadth_first_search_visit(Graph, node):
node.visited = 1
queue = deque([node])
while True:
try:
u = queue.popleft()
except IndexError:
break
for neighbor in u.neighbors(Graph):
if neighbor.visited == 0:
neighbor.visited = 1
queue.append(neighbor)
node.visited = 2
def print_topological(Nodes):
print('Toplogical sort of the Graph:')
# prints a topological sort
for node in sorted(Nodes, key=lambda obj: obj.finishing_time):
print('\t {}: {}'.format(node, node.finishing_time))
if __name__ == '__main__':
print('Using Depth-First-Search')
# should print each node exactly once
depth_first_search(Graph, Nodes)
print_topological(Nodes)
print('\n')
# the same buth with Breadth-First-Search
print('Using Breadth-First-Search')
breadth_first_search(Graph, Nodes)
print_topological(Nodes)