17.4 Adjacency -Lists RepresentationThe standard representation that is preferred for graphs that are not dense is called the adjacency-lists representation, where we keep track of all the vertices connected to each vertex on a linked list that is associated with that vertex. We maintain an array of lists so that, given a vertex, we can immediately access its list; we use linked lists so that we can add new edges in constant time. Program 17.9 is an implementation of the ADT interface in Program 17.1 that is based on this approach, and Figure 17.10 depicts an example. To add an edge connecting v and w to this representation of the graph, we add w to v 's adjacency list and v to w 's adjacency list. In this way, we still can add new edges in constant time, but the total amount of space that we use is proportional to the number of vertices plus the number of edges (as opposed to the number of vertices squared, for the adjacency-matrix representation). For undirected graphs, we again represent each edge in two different places: an edge connecting v and w is represented as nodes on both adjacency lists. It is important to include both; otherwise , we could not answer efficiently simple questions such as, "Which vertices are adjacent to vertex v ?" Program 17.10 implements the iterator that answers this question for clients , in time proportional to the number of such vertices. Figure 17.10. Adjacency-lists data structureThis figure depicts a representation of the graph in Figure 17.1 as an array of linked lists. The space used is proportional to the number of nodes plus the number of edges. To find the indices of the vertices connected to a given vertex v, we look at the vth position in an array, which contains a pointer to a linked list containing one node for each vertex connected to v. The order in which the nodes appear on the lists depends on the method that we use to construct the lists . The implementation in Programs 17.9 and 17.10 is a low-level one. An alternative is to use a Java collection class to implement each edge list (see Exercise 17.30). The disadvantage of doing so is that such implementations typically support many more operations than we need and therefore typically carry extra overhead that might affect the performance of all of our algorithms (see Exercise 17.31). Indeed, all of our graph algorithms use the Graph ADT interface, so this implementation is an appropriate place to encapuslate all the low-level operations and concentrate on efficiency without affecting our other code. Another advantage of using the linked-list representation is that it provides a concrete basis for understanding the performance characteristics of our implementations.
As for our array-based implementation, the linked-list “based implementation in Programs 17.9 and 17.10 lacks a clone implementation (see Section 4.9). Such a method is easy to develop by extending the one for the clonable queue implementation of Program 4.24 (see Exercise 17.29). By contrast to Program 17.7, Program 17.9 builds multigraphs, because it does not remove parallel edges. Checking for duplicate edges in the adjacency-lists structure would necessitate searching through the lists and could take time proportional to V . Similarly, Program 17.9 does not include an implementation of the remove edge operation or the edge existence test. Adding implementations for these methods is an easy exercise (see Exercise 17.28), but each operation might take time proportional to V to search through the lists for the nodes that represent the edges. These costs make the basic adjacency-lists representation unsuitable for applications involving either huge graphs where parallel edges cannot be tolerated or heavy use of remove edge or of edge existence tests. In Section 17.5, we discuss adjacency-list implementations that support constant-time remove edge and edge existence operations. When a graph's vertex names are not integers, then (as with adjacency matrices) two different programs might associate vertex names with the integers from 0 to V “ 1 in two different ways, leading to two different adjacency-list structures (see, for example, Program 17.15). We cannot expect to be able to tell whether two different structures represent the same graph because of the difficulty of the graph isomorphism problem. Moreover, with adjacency lists, there are numerous representations of a given graph even for a given vertex numbering. No matter in what order the edges appear on the adjacency lists, the adjacency-list structure represents the same graph (see Exercise 17.33). This characteristic of adjacency lists is important to know because the order in which edges appear on the lists affects, in turn , the order in which edges are processed by algorithms. That is, the adjacency-list structure determines how our various algorithms see the graph. An algorithm should produce a correct answer no matter how the edges are ordered on the adjacency lists, but it might get to that answer by different sequences of computations for different orderings. If an algorithm does not need to examine all the graph's edges, this effect might affect the time that it takes. And, if there is more than one correct answer, different input orderings might lead to different output results. The primary advantage of the adjacency-lists representation over the adjacency-matrix representation is that it always uses space proportional to E + V , as opposed to V 2 in the adjacency matrix. The primary disadvantage is that testing for the existence of specific edges can take time proportional to V , as opposed to constant time in the adjacency matrix. These differences trace, essentially , to the difference between using a linked list and using an array to represent the set of vertices incident on each vertex. Thus, we see again that an understanding of the basic properties of linked data structures and array is critical if we are to develop efficient graph ADT implementations. Our interest in these performance differences is that we want to avoid implementations that are inappropriately inefficient under unexpected circumstances when a wide range of operations is to be demanded of the ADT. In Section 17.5, we discuss the application of basic data structures to realize many of the theoretical benefits of both structures. Nonetheless, Program 17.9 is a simple implementation with the essential characteristics that we need to learn efficient algorithms for processing sparse graphs. Exercises
|