In the undirected graph, there is an edge between node 1 and node 3; therefore:
Now consider the following (directed) graph:
Note that the layout of the graph is arbitrary -- the important thing is which nodes are connected to which other nodes. So, for example, the following graph is the same as the one given above, it's just been drawn differently:
Also note that an edge can connect a node to itself; for example:
For each of the following graphs, say whether it is:
In general, the nodes of a graph represent objects and the edges represent
relationships.
Here are some examples:
In a tree, all nodes can be reached from the root node, so an entire tree
can be represented by storing a single pointer to the root node.
Some graphs have a similar property; i.e., there is a special "root" node
from which all other nodes are reachable (control-flow graphs often have
this property).
In that case, a graph can also be represented using a single pointer to the
root node.
However, if there is no root node, then we must use an array or a linked list
of (pointers to) graph nodes to ensure access to every node
(i.e., the nodes are numbered from 0 to N-1, where N is the number of
nodes in the graph, and the kth value in the array or in the list
is a pointer to node k).
An advantage of using an array of pointers to nodes is that, given a node's
number, you can access the node in constant time (because subscripting in
an array is a constant-time operation).
If you use a linked list, accessing node n takes time O(n).
On the other hand, an advantage of using a linked list is that it makes it
easy to add a new node (just add an item to the front of the list).
If an array is used, then it will have to be expanded (doubled in size)
when it becomes full.
The nodes will contain whatever data is stored in a node (e.g., the name of a
city, the name of a CS class, the statement represented by a control-flow
graph node).
The nodes can also contain pointers to their successors, or that information
can be stored in a separate array.
These two approaches to storing edge information are discussed below.
The usual way to represent edges is to let each node contain pointers to
its successors (for directed graphs) or neighbors (for undirected graphs).
These pointers are sometimes called adjacency lists.
The pointers could be stored in an array, or in a linked list, or in
a Sequence (which itself could be implemented using either an array or a
linked list).
Assuming that a Sequence is used,
the following class definitions can be used to implement directed graphs:
Here's a picture of a graph, and of the Graph object that represents it:
Some special kinds of graphs
A directed graph can also be a complete graph; in that case, there must
be an edge from every node to every other node.
Here's an example of a weighted, directed graph:
If the graph is a directed graph, also say whether it is cyclic or acyclic.
Uses for Graphs
The reason graphs are good representations in cases like those described
above is that there are many standard graph algorithms (operations on
graphs) that can be used to answer useful questions like:
Representing Graphs
Adjacency lists
class GraphNode {
// fields
Object data;
Sequence successors;
// methods
...
}
class Graph {
// fields
GraphNode[] nodes; // array of ptrs to nodes
int numNodes; // number of nodes in the graph
// methods
...
}
Operation | Adjacency Matrix | Adjacency List |
add edge | O(1) | O(1) |
remove edge from node j to node k | O(1) | worst case: O(# successors of j), which can be as bad as O(N) |
is there an edge from node j to node k? | O(1) | worst case: O(# successors of j), which can be as bad as O(N) |
list all successors of a node j | O(N) | O(# successors of j), which can be as bad as O(N) |
initialize | O(N2) | O(N) |