Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
c012231
first draft of depth first traversal
henrykh Feb 19, 2015
a0ccc29
adds test for unconnected graph and cyclic graph
henrykh Feb 19, 2015
67a1b26
adds first draft of breadth first traversal
henrykh Feb 19, 2015
fa2602a
importing Queue
joelstanner Feb 20, 2015
397dd51
added exception handling to breadth_first, and tests
joelstanner Feb 20, 2015
15c45ad
Docstrings added, README.md updated
joelstanner Feb 20, 2015
a9b6ea6
changed add_node to dictionary. add_edge now has weight
joelstanner Mar 2, 2015
b1f0edd
changed the del_node method to delete a dict, same with del_edge
joelstanner Mar 2, 2015
5e0604c
linting
joelstanner Mar 2, 2015
bc25ce7
changing tests to use dicts
joelstanner Mar 2, 2015
241ad51
converts edge dict to ordered dict to maintain predictable order in t…
henrykh Mar 2, 2015
214c882
adds travis and updates readme with weighted graph
henrykh Mar 2, 2015
58ae7b9
edit travis yml
henrykh Mar 2, 2015
9cf442e
removes pip install requirements from travis, edits readme
henrykh Mar 2, 2015
5a65d5b
adds tests for adding edges
henrykh Mar 2, 2015
c10bfe4
edges method gives edge weights
henrykh Mar 2, 2015
0bd6e10
updates test for edges method
henrykh Mar 2, 2015
47e5f87
updates add edge, neighbors now returns a list
henrykh Mar 3, 2015
5208e5d
initial implementation of Dijkstra's graph traversal
joelstanner Mar 3, 2015
cad836f
adds pathing to dijkstra method and tests
henrykh Mar 3, 2015
15bce23
modifies docstring for dijkstra's, removes unnecessary line
henrykh Mar 3, 2015
48d4dc3
added Bellman-Ford graph traversal implementation
joelstanner Mar 4, 2015
a7d2731
added tests, fixed errors, linting
joelstanner Mar 4, 2015
e9c05fe
updates readme with shortest path info
henrykh Mar 4, 2015
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
language: python
python:
- "2.7"
script: py.test
19 changes: 17 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
[![Build Status](https://travis-ci.org/henrykh/data-structures.svg)](https://travis-ci.org/henrykh/data-structures)

# data-structures

This repository holds sample code for a number of classic data structures implemented in Python.
Expand All @@ -18,8 +20,17 @@ This repository holds sample code for a number of classic data structures implem
* Priority Queue with Heap
- This implementation of the priority queue imports our binary heap module and passes tuples consisting of priority, seniority, and value into a heap. This implementation is adapted from the Python Cookbook example cited in the Resources section.

* Graph (unweighted, directed)
* Graph (weighted, directed)
- As described from [Python Patterns - Implementing Graphs](https://www.python.org/doc/essays/graphs/) "Graphs are networks consisting of nodes connected by edges or arcs. In directed graphs, the connections between nodes have a direction, and are called arcs."
- Graph Traversal
- Graph traversal is explored in a [depth first](http://en.wikipedia.org/wiki/Depth-first_search) and [breadth first](http://en.wikipedia.org/wiki/Breadth-first_search) style.
- Graph weighting
- Edges accept value weights or default to one if none is provided
- Shortest Path Algorithms
- Dijkstra's Algorithm
- Bellman-Ford Algorithm
- Both algorithms use iterative relaxation to find the shortest path between a starting node and target node. Through its use of a priority queue, Dijkstra's algorithm picks the minimum weighted node from among those it has not yet explored. By comparison, the Bellman-Ford algorithm processes all of the edges n-1 times, where n is the total number of nodes in the graph. This thoroughness increases the runtime of Bellman-Ford but allows it to handle negative weights, which Dijkstra's algorithm cannot.


## Resources
[Linked Lists Wiki](http://en.wikipedia.org/wiki/Linked_list)
Expand All @@ -29,10 +40,14 @@ This repository holds sample code for a number of classic data structures implem
[Binary Heap Visualization](http://www.comp.nus.edu.sg/~stevenha/visualization/heap.html)
[Priority Queue Wiki](http://en.wikipedia.org/wiki/Priority_queue)
[Implementing a Priority Queue from a Binary Heap](https://www.safaribooksonline.com/library/view/python-cookbook-3rd/9781449357337/ch01s05.html)
[Python Patterns - Implementing Graphs](https://www.python.org/doc/essays/graphs/)
[Python Patterns - Implementing Graphs](https://www.python.org/doc/essays/graphs/)
[Dijkstra's Algorithm](http://en.wikipedia.org/wiki/Dijkstra%27s_algorithm)
[Bellman-Ford Algorithm](http://en.wikipedia.org/wiki/Bellman%E2%80%93Ford_algorithm)


Pytest - for testing structure functions
Travis CI


##Collaborators
Joel Stanner
Expand Down
Empty file added __init__.py
Empty file.
Empty file added graph/__init__.py
Empty file.
152 changes: 136 additions & 16 deletions graph/simple_graph.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
from collections import OrderedDict
from queue import Queue
from Queue import PriorityQueue


W_DEFAULT = 1


class Graph(object):
"""Implements a graph data structure"""

Expand All @@ -11,43 +19,40 @@ def nodes(self):
def edges(self):
"""return a list of all edges in the graph"""

return [(key, node) for key, value in
self.graph_dict.iteritems() for node in value]
return [(key, node, weight) for key, edge_dict in
self.graph_dict.iteritems() for node, weight in edge_dict.items()]

def add_node(self, node):
"""add a new node 'n' to the graph"""
self.graph_dict.setdefault(node,[])
self.graph_dict.setdefault(node, OrderedDict())

def add_edge(self, node_1, node_2):
def add_edge(self, node_1, node_2, weight=W_DEFAULT):
"""add a new edge to the graph connecting 'n1' and 'n2', if either n1
or n2 are not already present in the graph, they are added.
"""
try:
self.graph_dict[node_1].append(node_2)
self.add_node(node_2)
self.graph_dict[node_1][node_2] = weight
except KeyError:
self.add_node(node_1)
self.graph_dict[node_1].append(node_2)
if node_2 not in self.nodes():
self.add_node(node_2)
self.graph_dict[node_1][node_2] = weight

def del_node(self, node):
"""delete the node 'n' from the graph"""
try:
del self.graph_dict[node]
for val_list in self.graph_dict.values():
if node in val_list:
val_list.remove(node)
for val_dict in self.graph_dict.values():
if node in val_dict:
del val_dict[node]
except KeyError:
raise KeyError("Node not found")

def del_edge(self, node_1, node_2):
"""delete the edge connecting 'n1' and 'n2' from the graph"""
try:
self.graph_dict[node_1].remove(node_2)
del self.graph_dict[node_1][node_2]
except KeyError:
raise KeyError("First node not found")
except ValueError:
raise ValueError("Edge not found")
raise KeyError("Edge not found")

def has_node(self, node):
"""True if node 'n' is contained in the graph, False if not"""
Expand All @@ -56,7 +61,7 @@ def has_node(self, node):
def neighbors(self, node):
"""return the list of all nodes connected to 'n' by edges"""
try:
return self.graph_dict[node]
return self.graph_dict[node].keys()
except KeyError:
raise KeyError("Node not found")

Expand All @@ -69,3 +74,118 @@ def adjacent(self, node_1, node_2):
return node_2 in self.graph_dict[node_1]
except KeyError:
raise KeyError("First node not found")

def depth_first_traversal(self, start):
"""Perform a full depth-first traversal of the graph beginning at start.
Return the full visited path when traversal is complete.
"""
try:
explored = OrderedDict()
return self._depth_first_traversal(start, explored)
except KeyError:
raise KeyError("Node does not exist")

def _depth_first_traversal(self, start, explored):
"""Helper function for depth_first_traversal for recursion"""
explored.setdefault(start, 1)

for child in self.graph_dict[start]:
if child not in explored:
self._depth_first_traversal(child, explored)

return explored.keys()

def breadth_first_traversal(self, start):
"""Perform a full breadth-first traversal of the graph, beginning at
start. Return the full visited path when traversal is complete.
"""
try:
explored = OrderedDict()
queue = Queue()
explored.setdefault(start, 1)

queue.enqueue(start)

while queue.size():
node = queue.dequeue()

for child in self.graph_dict[node]:
if child not in explored:
explored.setdefault(child, 1)
queue.enqueue(child)

return explored.keys()
except KeyError:
raise KeyError("Node does not exist")

def dijkstra_shortest(self, start, end):
"""Implementation of Dijkstra's shortest path algorithm, returns a list
of shortest path from start to end"""
if start is end:
return [start]
distance = {start: 0}
previous_node = {}
pqueue = PriorityQueue()
for node in self.nodes():
if node is not start:
distance[node] = float('inf')
previous_node['node'] = None
pqueue.put((distance[node], node)) # (priority, data)

while pqueue:
current = pqueue.get()[1]
if current == end:
break
for neighbor in self.neighbors(current):
alt = distance[current] + self.graph_dict[current][neighbor]
if alt < distance[neighbor]:
distance[neighbor] = alt
previous_node[neighbor] = current
pqueue.put((distance[neighbor], neighbor))

path = []
while end is not start:
path.append(end)
try:
end = previous_node[end]
except KeyError:
return "No Path Found"
path.append(start)
path.reverse()
return path

def bellman_ford_shortest(self, start, end):
"""Implementation of Bellman-Ford's shortest path algorithm, returns a
list of shortest path from start to end"""
if start is end:
return [start]
distance = {start: 0}
previous_node = {}
# Step 1: initialize graph
for node in self.nodes():
if node is not start:
distance[node] = float('inf')
previous_node[node] = None

# Step 2: relax edges repeatedly
for i in range(1, len(self.nodes()) - 1):
for edge in self.edges():
if distance[edge[0]] + edge[2] < distance[edge[1]]:
distance[edge[1]] = distance[edge[0]] + edge[2]
previous_node[edge[1]] = edge[0]

# Step 3: check for negative-weight cycles
for edge in self.edges():
if distance[edge[0]] + edge[2] < distance[edge[1]]:
raise Exception("Graph contains a negative-weight cycle")

path = []
while end is not start:
path.append(end)
try:
end = previous_node[end]
except KeyError:
return "No Path Found"
path.append(start)
path.reverse()
return path
Loading