If a graph is not connected, then it has at least two connected components.
An array works well when there are a static number of vertices and this number is known in advance. In this case the array can be pre-allocated. If the vertices are numbered consecutively, then the array can support direct access; otherwise a binary search may be required. An advantage of an array is that all the vertices can be visited by simply walking through the array entries.
A list is typically the least preferable of the data structures. Its advantage is that all the vertices can be visited by simply walking through the list. However, to find a vertex will require O(V) time and to insert a vertex may require O(V) time if the list is sorted.
The red-black tree has the advantage of finding a vertex in O(lg V) time.
Values of the entries
Implementation: If the language supports a bit data type, then matrices are typically stored as bit arrays. However, if either the language does not support a bit data type or the edges are represented as instances, then an integer or edge array is typically used.
As already noted, a graph consists of two elements, vertices and edges. To create a graph data structure we need to create classes for vertices and edges. We also need to create a container class for a graph. The graph class will provide the various methods that an application can use such as depth-first search, breadth-first search, and shortest path methods.
There are two types of graphs, directed and undirected graphs. Since both types of graphs support many of the same operations, we will define an abstract superclass called Graph and make DirectedGraph and UndirectedGraph inherit from Graph:
Graph | -------------- | | DirectedGraph UndirectedGraph
The methods in Graph will be declared as pure virtual. For example:
class Graph { void dfs( Vertex *v ) = 0; ... };
Ordinarily implementations of these methods would be provided in DirectedGraph and UndirectedGraph. However, these classes should also be abstract base classes because they will have different implementations depending on whether they are dense or sparse graphs. Consequently DirectedGraph might have subclasses labeled DenseDirectedGraph and SparseDirectedGraph and UndirectedGraph might have subclasses labeled DenseUndirectedGraph and SparseUndirectedGraph. These subclasses would then provide concrete implementations of the methods declared by Graph.
The design of the vertex and edge classes also must be handled with care. Different real world problems require different types of information to be stored with a vertex or an edge. We want a design that will accommodate the different needs of these problems without having to rewrite our graph classes for each different problem. One way to accomplish this task is to define abstract base classes for Vertex and Edge that contain information needed by many of the algorithms. For example, many of the algorithms require a visited field for a vertex so we will declare a visited field in the Vertex class. Similarly edges typically have a weight so we will declare a weight field in the Edge class.
The vertex class can be used with either directed or undirected graphs. However, depending on the design we choose for the edge class, it may or may not be usable by both types of graphs. For example, an undirected edge might maintain pointers to both of its vertices whereas a directed edge only needs to maintain a pointer to the vertex to which it points. Our solution will be to define a single vertex pointer field in the edge class. This field will suffice for a directed edge. An undirected edge can then be handled in one of two ways:
The vertex and edge data structures can now be roughed out using templates as follows:
template <class vtx_id_type, class weight_type> Edge; template <class id_type, class weight_type> class Vertex { protected: id_type id; bool visited; } // A vertex for a sparse graph uses an adjacency list template <class id_type, class weight_type> class SparseVertex : public Vertex<id_type, weight_type> { // We want graphs to have access to a vertice's variables. Unfortunately // we have to declare each type of graph to be a friend. friend class SparseDirectedGraph friend class SparseUndirectedGraph ... protected: Dlist<Edge<id_type, weight_type> *> adj_list; }; template <class vtx_id_type, class weight_type> class Edge { // We want graphs to have access to a vertice's variables. Unfortunately // we have to declare each type of graph to be a friend. friend class SparseDirectedGraph friend class DenseDirectedGraph ... protected: Vertex<vtx_id_type> *adj_vtx; weight_type weight; };
A graph must take the vertex id type and the weight type as parameters so it will need to be a template class as well.
Note that while we have provided these definitions using templates, it is not necessary to use templates in your own code. See examples in these notes for definitions of vertex and edge classes that do not use templates.