Selecting the right data structure to represent graphs can have an enormous impact on the performance of an algorithm. Your two basic choices are adjacency matrices and adjacency lists, illustrated in Figure .
An adjacency matrix is an matrix M where (typically) M[i,j] = 1 if there is an edge from vertex i to vertex j and M[i,j]=0 if there is not. Adjacency matrices are the simplest way to represent graphs. However, they doom you to using space no matter how many edges are in the graph. For large graphs, this will kill you. Remember that 1,000,000, and work up from there. Although there is some potential for saving space by packing multiple bits per word or simulating a triangular matrix for undirected graphs, these cost some of the simplicity that makes adjacency matrices so appealing.
Figure: The adjacency matrix and adjacency list of a given graph
Beyond simplicity, there are certain algorithmic operations that prove faster on adjacency matrices than adjacency lists. In particular, it takes time to test whether edge (i,j) is in a graph represented by an adjacency matrix. All we must do is read the appropriate bit.
An adjacency list consists of an n-element array of pointers, where the ith element points to a linked list of the edges incident on vertex i. To test whether edge (i,j) is in the graph, we search the ith list for j. This takes , where is the degree of the ith vertex. For a complete or almost complete graph, , so testing the existence of an edge can be very expensive relative to adjacency matrices. However, can be much less than n when the graph is sparse. Most of the graphs that one encounters in real life tend to be sparse. Recall the friendship graph as an example. Further, a surprising number of the most efficient graph algorithms can be and have been designed to avoid such edge-existence queries. The key is processing the edges in a systematic order like breadth-first or depth-first search.
For most applications, adjacency lists are the right way to go. The main drawback is the complexity of dealing with linked list structures. Things can be made arbitrarily hairy by adding extra pointers for special purposes. For example, the two versions of each edge in an undirected graph, (i,j) and (j,i), can be linked together by a pointer to facilitate deletions. Also, depending upon the operations you will perform on each list, you may or may not want it to be doubly linked, so that you can move backwards as easily as you move forwards.
It is a good idea to use a well-designed graph data type as a model for building your own, or even better as the foundation for your application. We recommend LEDA (see Section ) as the best-designed general-purpose graph data structure currently available. It may be more powerful (and hence somewhat slower/larger) than what you need, but it does so many things right that you are likely to lose most of the potential do-it-yourself benefits through clumsiness.
In summary, we have the following tradeoffs between adjacency lists and matrices:
|Faster to test if (x, y) is in graph?||adjacency matrices|
|Faster to find the degree of a vertex?||adjacency lists|
|Less memory on small graphs?||adjacency lists (m+n) vs.|
|Less memory on big graphs?||adjacency matrices (a small win)|
|Edge insertion or deletion?||adjacency matrices O(1) vs. O(d)|
|Faster to traverse the graph?||adjacency lists vs.|
|Better for most problems?||adjacency lists|