The order of growth for a graph algorithm is usually expressed as a function of |V|, the number of vertices, and |E|, the number of edges.
The order of growth for BFS is O(|V| + |E|), which is a convenient way to say that the run time grows in proportion to either |V| or |E|, whichever is “bigger.”
To see why, think about these four operations:
Adding them up, we get O(|V| + |E|). If we know the relationship between |V| and |E|, we can simplify this expression. For example, in a regular graph, the number of edges is in O(|V|) so BFS is linear in |V|. In a complete graph, the number of edges is in O(|V|2) so BFS is quadratic in |V|.
Of course, this analysis is based on the assumption that all four operations—adding and removing vertices, marking and checking marks—are constant time.
Marking vertices is easy. You can add an attribute to the Vertex objects or use a dictionary1 and the in operator to keep track of marked vertices.
But making a first-in-first-out (FIFO) queue that can add and remove vertices in constant time turns out to be non-trivial.
A FIFO is a data structure that provides the following operations:
There are several good implementations of this data structure. One is the doubly-linked list, which you can read about at wikipedia.org/wiki/Doubly-linked_list. Another is a circular buffer, which you can read about at wikipedia.org/wiki/Circular_buffer.
Write an implementation of a FIFO using either a doubly-linked list or a circular buffer.
Yet another possibility is to use a Python dictionary augmented with two indices: nextin keeps track of the back of the queue; nextout keeps track of the front. Here is an implementation2:
class DictQueue(dict):
def __init__(self):
self.nextin = 0
self.nextout = 0
def append(self, item):
self.nextin += 1
self[self.nextin] = item
def pop(self):
self.nextout += 1
return dict.pop(self, self.nextout)
append increments nextin and then stores the new item in the dictionary; both operations are constant time3.
pop increments nextout and then uses dict.pop to remove and return the item at the end of the queue. Again, both operations are constant time.
As an alternative, the Python collections module provides an object called a deque, which stands for “double-ended queue”. It is supposed to be pronounced “deck,” but many people say “deek.” A Python deque can be adapted very easily to implement a FIFO.
You can read about deques at wikipedia.org/wiki/Deque and get the details of the Python implementation at docs.python.org/lib/deque-objects.html.
The following implementation of a BFS4 contains two performance errors. What are they? What is the actual order of growth for this algorithm?
def bfs(top_node, visit):
"""Breadth-first search on a graph, starting at top_node."""
visited = set()
queue = [top_node]
while len(queue):
curr_node = queue.pop(0) # Dequeue
visit(curr_node) # Visit the node
visited.add(curr_node)
# Enqueue non-visited and non-enqueued children
queue.extend(c for c in curr_node.children
if c not in visited and c not in queue)
Stanley Milgram was an American social psychologist who conducted two of the most famous experiments in social science, the Milgram experiment, which studied people’s obedience to authority (wikipedia.org/wiki/Milgram_experiment) and the Small World Experiment wikipedia.org/wiki/Small_world_phenomenon, which studied the structure of social networks.
In the Small World Experiment, Milgram sent a package to a number of randomly-chosen people in Wichita, Kansas, with instructions asking them to forward an enclosed letter to a target person, identified by name and occupation, in Sharon, Massachusetts (which is the town near Boston where I grew up). The subjects were told that they could mail the letter directly to the target person only if they knew him personally; otherwise they were instructed to send it, and the same instructions, to a relative or friend they thought would be more likely to know the target person.
Many of the letters were never delivered, but of the ones that were it turned out that the average path length—the number of times the letters were forwarded—was about six. This result was taken to confirm previous observations (and speculations) that the typical distance between any two people in a social network is about “six degrees of separation.”
This conclusion is surprising because most people expect social networks to be localized—people tend to live near their friends—and in a graph with local connections, path lengths tend to increase in proportion to geographical distance. For example, most of my friends live nearby, so I would guess that the average distance between nodes in a social network is about 50 miles. Wichita is about 1600 miles from Boston, so if Milgram’s letters traversed typical links in the social network, they should have taken 32 hops, not six.
In 1998 Duncan Watts and Steven Strogatz published a paper in Nature, “Collective dynamics of ’small-world’ networks,” that proposed an explanation for the small world phenomenon.
Watts and Strogatz started with two kinds of graph that were well understood: random graphs and regular graphs. They looked at two properties of these graphs, clustering and path length.
Their initial result was what you might expect: regular graphs have high clustering and high path lengths; random graphs with the same size tend to have low clustering and low path lengths. So neither of these is a good model of social networks, which seem to combine high clustering with short path lengths.
Their goal was to create a generative model of a social network. A generative model tries to explain a phenomenon by modeling a process that builds or leads to the phenomenon. In this case Watts and Strogatz proposed a process for building small-world graphs:
The proportion of edges that are rewired is a parameter, p, that controls how random the graph is. With p=0, the graph is regular; with p=1 it is random.
Watts and Strogatz found that small values of p yield graphs with high clustering, like a regular graph, and low path lengths, like a random graph.
Read the Watts and Strogatz paper and answer the following questions:
Create a file named SmallWorldGraph.py and define a class named SmallWorldGraph that inherits from RandomGraph.
clustering_coefficient that
computes and returns the clustering coefficient as defined in the
paper.Before we can replicate the other line, we have to learn about shortest path algorithms.
Edsger W. Dijkstra was a Dutch computer scientist who invented an efficient shortest-path algorithm (see wikipedia.org/wiki/Dijkstra's_algorithm), He also invented the semaphore, which is a data structure used to coordinate programs that communicate with each other (see see wikipedia.org/wiki/Semaphore_(programming) and Downey, The Little Book of Semaphores).
Dijkstra is also famous (and notorious) as the authors of a series of essays on computer science Some, like “A Case against the GO TO Statement,” have had a profound effect on programming practice. Others, like “The Cruelty of Really Teaching Computing Science,” are entertaining in their cantankerousness, but less effective.
Dijkstra’s algorithm solves the “single source shortest path problem,” which means that it finds the minimum distance from a given “source” node to every other node in the graph (or at least every connected node).
We will start with a simplified version of the algorithm that considers all edges the same length. The more general version works with any non-negative edge lengths.
The simplified version is similar to the breadth-first search in Section 1.4 except that instead of marking visited nodes, we label them with their distance from the source. Initially all nodes are labeled with an infinite distance. Like a breadth-first search, Dijkstra’s algorithm uses a queue of discovered unvisited nodes.
The first time you execute Step 2, the only node in the queue has distance 0. The second time, the queue contains all nodes with distance 1. Once those nodes are processed, the queue contains all nodes with distance 2, and so on.
So when a node is discovered for the first time, it is labelled with the distance d+1, which is the shortest path to that node. It is not possible that you will discover a shorter path later, because if there were a shorter path, you would have discovered it sooner. Of course, that is not a proof of the correctness of the algorithm, but it is a sketch of the structure of the proof, which is by contradiction.
In the more general case, where the edges have different lengths, it is possible to discover a shorter path after you have discovered a longer path, so a little more work is required.
Write an implementation of Dijkstra’s algorithm and use it to compute the average path length of a SmallWorldGraph.
Make a graph that replicates the line marked L(p)/L(0) in Figure 2 of the Watts and Strogatz paper. Confirm that the average path length drops off quickly for small values of p. What is the range of values for p that yield graphs with high clustering and low path lengths?
A natural question about the Watts and Strogatz paper is whether the small world phenomenon is specific to their generative model or whether other similar models yield the same qualitative result (high clustering and low path lengths).
To answer this question, choose a variation of the Watts and Strogatz model and replicate their Figure 2. There are two kinds of variation you might consider:
If a range of similar models yield similar behavior, we would say that the results of the paper are robust.
To compute the average path length in a SmallWorldGraph, you probably ran Dijkstra’s single-source shortest path algorithm for each node in the graph. In effect, you solved the “all-pairs shortest path” problem, which finds the shortest path between all pairs of nodes.
If you asked me why planetary orbits are approximately elliptical, I might start by modeling a planet and a star as point masses; I would look up the law of universal gravitation at wikipedia.org/wiki/Newton's_law_of_universal_gravitation and use it to write a differential equation for the motion of the planet. Then I would either derive the orbit equation or, more likely, look it up at wikipedia.org/wiki/Orbit_equation. With a little algebra, I could derive the conditions that yield an elliptical orbit. Then I would argue that the objects we consider planets satisfy these conditions.
People, or at least scientists, are generally satisfied with this kind of explanation. One of the reasons for its appeal is that the assumptions and approximations in the model seem reasonable. Planets and stars are not really point masses, but the distances between them are so big that their actual sizes are negligible. Planets in the same solar system can affect each others’ orbits, but the effect is usually small. And we ignore relativistic effects, again on the assumption that they are small.
This explanation is also appealing because it is equation-based. We can express the orbit equation in a closed form, which means that we can compute orbits very efficiently. It also means that we can derive general expressions for the orbital velocity, orbital period, and other quantities.
Finally, I think this kind of explanation is appealing because it has the form of a mathematical proof. It starts from a set of axioms and derives the result by logic and analysis. But it is important to remember that the proof pertains to the model and not the real world. That is, we can prove that an idealized model of a planet yields an elliptic orbit, but we can’t prove that the model pertains to actual planets (in fact, it does not).
By comparison, Watts and Strogatz’s explanation of the small world phenomenon may seem less satisfying. First, the model is more abstract, which is to say less realistic. Second, the results are generated by simulation, not by mathematical analysis. Finally, the results seem less like a proof and more like an example.
Many of the models in this book are like the Watts and Strogatz model: abstract, simulation-based and (at least superficially) less formal than conventional mathematical models. One of the goals of this book it to consider the questions these models raise:
Over the course of the book I will offer my answers to these questions, but they are tentative and sometimes speculative. I encourage you to consider them skeptically and reach your own conclusions.