Network Routing
Brandon Blatter
CS 312
View this report in your browser
Let's be honest. PDFs were not meant for displaying code, and code was not meant to be displayed in PDFs. Please view this report in your browser so it's easy to read.
Code
NetworkPoint.cs
using System;
using System.Collections.Generic;
using System.Drawing;
namespace NetworkRouting
{
// custom class that manages data associated with each node
public class NetworkPoint
{
public NetworkPoint(PointF p, int index)
{
point = p;
listIndex = index;
prevPoint = null;
distance = 1000000000;
}
public PointF point;
public int listIndex;
public NetworkPoint prevPoint;
public double distance;
}
}
NetworkSolver.cs
using System;
using System.Collections.Generic;
using System.Drawing;
namespace NetworkRouting
{
public class NetworkSolver
{
public NetworkSolver(int start, int stop, List<PointF> points, List<HashSet<int>> adjacencies)
{
startNodeIndex = start;
stopNodeIndex = stop;
pointList = new List<NetworkPoint>();
// initialize point list
for(int i = 0; i < points.Count; i++) {
pointList.Add(new NetworkPoint(points[i], i));
}
// set start node to have a distance of 0
pointList[startNodeIndex].distance = 0;
adjacencyList = adjacencies;
}
private int startNodeIndex;
private int stopNodeIndex;
private List<NetworkPoint> pointList;
private List<HashSet<int>> adjacencyList;
private BinaryHeapPQ binaryHeapH;
private ArrayPQ arrayH;
private List<PointF> getStopToStartList(NetworkPoint stopPoint) {
NetworkPoint currentPoint = stopPoint;
List<PointF> points = new List<PointF>();
while(currentPoint.listIndex != startNodeIndex) {
points.Add(currentPoint.point);
currentPoint = currentPoint.prevPoint;
}
points.Add(currentPoint.point);
return points;
}
private double DistanceBetween(NetworkPoint point1, NetworkPoint point2) {
double xDistance = point1.point.X - point2.point.X;
double yDistance = point1.point.Y - point2.point.Y;
return Math.Sqrt(xDistance * xDistance + yDistance * yDistance);
}
public List<PointF> SolveWithBinaryHeap() {
// constructor will run setup and MakeQueue
binaryHeapH = new BinaryHeapPQ(pointList);
NetworkPoint u = null;
HashSet<int> adjacentToU = null;
NetworkPoint v = null;
// Dijkstra's algorithm
while(!binaryHeapH.IsEmpty()) {
u = binaryHeapH.DeleteMin();
// points not connected - report error
if(u == null) return null;
if(u.listIndex == stopNodeIndex) return getStopToStartList(u);
// get adjacent point indices
adjacentToU = adjacencyList[u.listIndex];
foreach(int adjacentIndex in adjacentToU) {
// process point
v = pointList[adjacentIndex];
if(v.distance > u.distance + DistanceBetween(u, v)) {
v.distance = u.distance + DistanceBetween(u, v);
v.prevPoint = u;
binaryHeapH.DecreaseKey(v.listIndex);
}
}
}
// stop point not found (should never execute)
return null;
}
public List<PointF> SolveWithArray() {
// constructor will run setup and MakeQueue
arrayH = new ArrayPQ(pointList);
NetworkPoint u = null;
HashSet<int> adjacentToU = null;
NetworkPoint v = null;
// Dijkstra's algorithm
while(!arrayH.IsEmpty()) {
u = arrayH.DeleteMin();
// points not connected - report error
if(u == null) return null;
if(u.listIndex == stopNodeIndex) return getStopToStartList(u);
// get adjacent point indices
adjacentToU = adjacencyList[u.listIndex];
foreach(int adjacentIndex in adjacentToU) {
// process point
v = pointList[adjacentIndex];
if(v.distance > u.distance + DistanceBetween(u, v)) {
v.distance = u.distance + DistanceBetween(u, v);
v.prevPoint = u;
arrayH.DecreaseKey(v.listIndex);
}
}
}
// stop point not found (should never execute)
return null;
}
}
}
ArrayPQ.cs
using System;
using System.Collections.Generic;
using System.Drawing;
namespace NetworkRouting
{
public class ArrayPQ
{
public ArrayPQ(List<NetworkPoint> points)
{
MakeQueue(points);
}
private List<NetworkPoint> pointList;
public bool IsEmpty() {
return pointList.Count == 0;
}
// MakeQueue: O(n)
public void MakeQueue(List<NetworkPoint> points) {
// reset list of points
pointList = new List<NetworkPoint>();
// add each point given; order doesn't matter
for (int i = 0; i < points.Count; i++) {
Insert(points[i]);
}
}
// Insert: O(1)
public void Insert(NetworkPoint point) {
// initialize point with default values
pointList.Add(point);
}
// DeleteMin: O(n)
public NetworkPoint DeleteMin() {
double minDistance = 1000000000;
NetworkPoint min = null;
int mindex = -1;
// find point with smallest distance
for (int i = 0; i < pointList.Count; i++) {
if(pointList[i].distance != -1 && pointList[i].distance < minDistance) {
minDistance = pointList[i].distance;
min = pointList[i];
mindex = i;
}
}
// if all points are unreachable
if(min == null) return null;
// remove min from list
pointList.RemoveAt(mindex);
// return that point so it can be used
return min;
}
// Insert: O(1)
public void DecreaseKey(int index) {
// do nothing - no change in array implementation
}
}
}
BinaryHeapPQ.cs
using System;
using System.Collections.Generic;
using System.Drawing;
namespace NetworkRouting
{
public class BinaryHeapPQ
{
public BinaryHeapPQ(List<NetworkPoint> points)
{
MakeQueue(points);
}
// locations of nodes on the heap (access from DeleteMin)
private List<int> heapLocations;
private List<NetworkPoint> heapList;
public bool IsEmpty() {
return heapList.Count == 0;
}
// gets node's parent from binary heap by index
private int GetParentIndex(int nodeIndex) {
return nodeIndex / 2;
}
private int GetSmallerChildIndex(int nodeIndex) {
int childIndex1 = nodeIndex * 2;
// if left child index is greater than size of heap, use nodeIndex
if(childIndex1 >= heapList.Count) return nodeIndex;
int childIndex2 = childIndex1 + 1;
// if right child index is greater than size of heap, use left child index
if(childIndex2 >= heapList.Count) return childIndex1;
NetworkPoint child1 = heapList[childIndex1];
NetworkPoint child2 = heapList[childIndex2];
// if right child doesn't exist or has greater distance, use left child
if(child1.distance < child2.distance) return childIndex1;
// otherwise use right child
else return childIndex2;
}
// swap nodes by index in heap list
private void SwapNodes(int nodeIndex1, int nodeIndex2) {
// get nodes to switch
NetworkPoint node1 = heapList[nodeIndex1];
NetworkPoint node2 = heapList[nodeIndex2];
// swap nodes actual nodes in heapList
heapList[nodeIndex2] = node1;
heapList[nodeIndex1] = node2;
// swap indices in heapLocations
heapLocations[node1.listIndex] = nodeIndex2;
heapLocations[node2.listIndex] = nodeIndex1;
}
private void BubbleUp(int nodeIndex) {
int parentIndex = GetParentIndex(nodeIndex);
// while parent value is higher
while(heapList[parentIndex].distance > heapList[nodeIndex].distance) {
// swap nodes
SwapNodes(parentIndex, nodeIndex);
// update current node and parent indices
nodeIndex = parentIndex;
parentIndex = GetParentIndex(parentIndex);
}
}
private void BubbleDown(int nodeIndex) {
int smallerChildIndex = GetSmallerChildIndex(nodeIndex);
if(smallerChildIndex == 0) return;
// while child value is lower
while(heapList[smallerChildIndex].distance < heapList[nodeIndex].distance) {
// swap nodes
SwapNodes(smallerChildIndex, nodeIndex);
// update current node and parent indices
nodeIndex = smallerChildIndex;
smallerChildIndex = GetSmallerChildIndex(smallerChildIndex);
if(smallerChildIndex == 0) return;
}
}
// MakeQueue: O(n log n)
public void MakeQueue(List<NetworkPoint> points) {
// reset list of points
heapList = new List<NetworkPoint>();
heapLocations = new List<int>();
// add each point given; order doesn't matter
for (int i = 0; i < points.Count; i++) {
Insert(points[i]);
}
}
// Insert: O(log n)
public void Insert(NetworkPoint point) {
// add point to end of list (bottom right of tree)
heapList.Add(point);
// add location as the last element in the heap list
heapLocations.Add(heapList.Count - 1);
// bubble up
BubbleUp(heapList.Count - 1);
}
// DeleteMin: O(log n)
public NetworkPoint DeleteMin() {
// if top node is greater than our cutoff, assume unreachable
if(heapList[0].distance > 999999999) return null;
// get top node
NetworkPoint min = heapList[0];
heapLocations[min.listIndex] = -1;
// replace top node with bottommost rightmost
heapList[0] = heapList[heapList.Count - 1];
heapList.RemoveAt(heapList.Count - 1);
if(heapList.Count > 0) {
heapLocations[heapList[0].listIndex] = 0;
}
// bubble down
BubbleDown(0);
return min;
}
// DecreaseKey: O(log n)
public void DecreaseKey(int listIndex) {
// bubble up actual location in heap
BubbleUp(heapLocations[listIndex]);
}
}
}
Complexity
Time Complexity
There are two implementations for this project. Each implementation has a different time complexity.
In both implementations, even though I halt Dijkstra's algorithm on finding the destination node, it is possible that every vertex will be deleted once. This yields a big O of (|V| * DeleteMin
). Every vertex will need to be added and every edge may need to have its key value decreased, which yields a big O of ((|V| + |E|) * Insert
or DecreaseKey
) (Insert
and DecreaseKey
have the same big O).
My Array implementation has an overall big O of O(|V|2). The array priority queue has operations with the following complexities:
Insert()
: O(1) (adding to an array is constant time)DeleteMin()
: O(|V|) (the whole array must be searched to find the minimum key value)DecreaseKey()
: O(1) (the array is not reordered after a key value is modified)The big O for the binary heap is therefore O(|V| * |V| + (|V| + |E|) * 1). The dominant term and final big O is O(|V| * |V|).
My Binary Heap implementation has an overall big O of O((|V| + |E|) log |V|) The binary heap priority queue has operations with the following complexities:
Insert()
: O(log |V|) (bubbling up is at worst O(log n))DeleteMin()
: O(log |V|) (bubbling down is at worst O(log n))DecreaseKey()
: O(log |V|) (bubbling up is at worst O(log n))
The big O for the binary heap is therefore O(|V| * log |V| + (|V| + |E|) * log |V|). The dominant term and final big O is O((|V| + |E|) * log |V|).
Note: I kept an array of indices to nodes in the heap so I wouldn't have to traverse the heap in order to find the appropriate value when calling DecreaseKey
. Instead, I simply looked up where the node was using my array of indices. This allows finding the node to be O(1) and the final big O of the function to be O(log n) with bubbling.
Space Complexity
My space complexity for both implementations is O(|V|). I used one array to store the vertices in my array implementation and two arrays to store the vertices in my binary heap implementation (one for the binary heap and one to store the references to points in the binary heap). The total time complexity of running either algorithm is only a constant multiple of O(|V|).
Outcomes
All times are measured in seconds.
Raw
Binary Heap
100 | 1,000 | 10,000 | 100,000 | 1,000,000 | |
---|---|---|---|---|---|
1 | 0.0003 | 0.001 | 0.005 | 0.088 | 2.948 |
2 | 0.0001 | 0.001 | 0.014 | 0.376 | 5.410 |
3 | 0.0002 | 0.003 | 0.010 | 0.299 | 1.284 |
4 | 0.0001 | 0.001 | 0.007 | 0.282 | 6.517 |
5 | 0.0002 | 0.001 | 0.022 | 0.362 | 6.413 |
Array
100 | 1,000 | 10,000 | 100,000 | |
---|---|---|---|---|
1 | 0.0001 | 0.008 | 0.193 | 28.293 |
2 | 0.0001 | 0.005 | 0.628 | 102.446 |
3 | 0.0001 | 0.011 | 0.423 | 88.876 |
4 | 0.0001 | 0.005 | 0.282 | 91.088 |
5 | 0.0001 | 0.010 | 0.892 | 99.778 |
Means
100 | 1,000 | 10,000 | 100,000 | 1,000,000 | |
---|---|---|---|---|---|
Binary Heap | 0.0002 | 0.001 | 0.012 | 0.281 | 4.514 |
Array | 0.0001 | 0.008 | 0.484 | 82.096 | ~10000 |
Graphs
I graphed the results in two different programs. 1,000,000 is omitted for the array implementation because I collected no data for it and it is easier to see the differences between the two graphs without it.
Discussion
The graphs of my results appear to be very skewed. It's difficult to see the O(n2) complexity for the array implementation as it doesn't seem to grow very quickly at first. The O(n log n) complexity for the binary heap implementation looks okay.
The actual values of the averages make it a little easier to see the correlation between the number of points and the time taken to find the shortest path. The binary heap grows a bit more quickly than if it had a linear slope and the array is certainly exponential. I tried to collect data for the array implementation at 1,000,000 points but I ran my program for more than an hour and the calculation still had not finished.
In general the results correlate with the expected outcomes of the two implementations. For small sets of points, the times to find the shortest path from one to another appear to be the same. However, with larger sets of points, the binary heap implementation of Dijsktra's algorithm is shown to be much more efficient than the painful array implementation.
Screenshots
Note: as with many other students, my viewport was stretched in the Y direction. I tried to resize the viewport manually so as to match the size used in the example screenshots, but I may not have done so perfectly. Please keep that in mind if the Y values are off (or if distances appear more stretched the steeper a slope is).
Demo 1
Seed: 42, Size: 20, Source: 16, Destination: 17
Demo 2
Seed: 123, Size: 200, Source: 111, Destination: 38