Previous Up Next

Chapter 4  Small world graphs

4.1  Analysis of graph algorithms

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 a vertex to the queue: this happens once for each vertex, so the total cost is in O(|V|).
Removing a vertex from the queue: this happens once for each vertex, so the total cost is in O(|V|).
Marking a vertex “visited”: this happens once for each vertex, so the total cost is in O(|V|).
Checking whether a vertex is marked: this happens once each edge, so the total cost is in O(|E|).

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 put the marked ones in a set and use the in operator.

But making a first-in-first-out (FIFO) queue that can add and remove vertices in constant time turns out to be non-trivial.

4.2  FIFO implementation

A FIFO is a data structure that provides the following operations:

Add a new item to the end of the queue.
Remove and return the item at the front of the queue.

There are several good implementations of this data structure. One is the doubly-linked list, which you can read about at Another is a circular buffer, which you can read about at

Exercise 1  

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 and two indices: nextin keeps track of the back of the queue; nextout keeps track of the front. The dictionary maps from integer indices to values.

Here is an implementation based on Raymond Hettinger’s recipe at

class DictFifo(object):

    def __init__(self):
        self.nextin = 0
        self.nextout = 0 = {}

    def append(self, value):[self.nextin] = value
        self.nextin += 1

    def pop(self, n=-1):
        value =
        self.nextout += 1
        return value

append stores the new item and increments nextin; both operations are constant time.

pop removes the last item and increments nextout. Again, both operations are constant time.

As yet another 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 to implement a FIFO.

You can read about deques at and get the details of the Python implementation at

Exercise 2  

The following implementation of a BFS 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

        # 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)

Test this code with a range of graph sizes and check your analysis. Then use a FIFO implementation to fix the errors and confirm that your algorithm is linear.

4.3  Stanley Milgram

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 ( and the Small World Experiment, which studied the structure of social networks (




4.4  Watts and Strogatz

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. You can download it from





  2. Choose a subset of the edges in the graph and “rewire” them by replacing them with random edges. Again, you could replicate the procedure described in the paper or experiment with alternatives.



Exercise 3  

Read the Watts and Strogatz paper and answer the following questions:

  1. What process do Watts and Strogatz use to rewire their graphs?
  2. What is the definition of the clustering coefficient C(p)?
  3. What is the definition of the average path length L(p)?
  4. What real-world graphs did Watts and Strogatz look at? What evidence do they present that these graphs have the same structure as the graphs generated by their model?
Exercise 4  

Create a file named and define a class named SmallWorldGraph that inherits from RandomGraph.

If you did Exercise 2.4 you can use your own; otherwise you can download mine from

  1. Write a method called rewire that takes a probability p as a parameter and, starting with a regular graph, rewires the graph using Watts and Strogatz’s algorithm.
  2. Write a method called clustering_coefficient that computes and returns the clustering coefficient as defined in the paper.
  3. Make a graph that replicates the line marked C(p)/C(0) in Figure 2 of the paper. In other words, confirm that the clustering coefficient drops off slowly for small values of p.

Before we can replicate the other line, we have to learn about shortest path algorithms.

4.5  Dijkstra

Edsger W. Dijkstra was a Dutch computer scientist who invented an efficient shortest-path algorithm (see's_algorithm). He also invented the semaphore, which is a data structure used to coordinate programs that communicate with each other (see and Downey, The Little Book of Semaphores).

Dijkstra is famous (and notorious) as the author 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 “On 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 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 2.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.

  1. Give the source node distance 0 and add it to the queue. Give the other nodes infinite distance.
  2. Remove a vertex from the queue and assign its distance to d. Find the vertices it is connected to. For each connected vertex with infinite distance, replace the distance with d+1 and add it to the queue.
  3. If the queue is not empty, go back to Step 2.

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 labeled 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. That is not a proof of the correctness of the algorithm, but it sketches the structure of the proof 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 needed.

Exercise 5  

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?

Exercise 6  

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:

  • Instead of starting with a regular graph, start with another graph with high clustering. One option is a locally-connected graph where vertices are placed at random locations in the plane and each vertex is connected to its nearest k neighbors.
  • Experiment with different kinds of rewiring.

If a range of similar models yield similar behavior, we say that the results of the paper are robust.

Exercise 7  

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.

  1. Find an algorithm for the all-pairs shortest path problem and implement it. Compare the run time with your “all-source Dijkstra” algorithm.
  2. Which algorithm gives better order-of-growth run time as a function of the number of vertices and edges? Why do you think Dijkstra’s algorithm does better than the order-of-growth analysis suggests?

4.6  What kind of explanation is that?

If you ask me why planetary orbits are elliptical, I might start by modeling a planet and a star as point masses; I would look up the law of universal gravitation at'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 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 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:

  • What kind of work can these models do: are they predictive, or explanatory, or both?
  • Are the explanations these models offer less satisfying than explanations based on more traditional models? Why?
  • How should we characterize the differences between these and more conventional models? Are they different in kind or only in degree?

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.

Like this book?

Are you using one of our books in a class?


Previous Up Next