The typical notation is G = (V, E), describing graph G as being composed of V (a set of vertices) and E (a set of edges connecting pairs of vertices in V).
Graphs are used to represent a wide variety of systems, e.g. cities connected by roads, airports connected by flight paths, functions connected by calls between them, people connected by relationships, courses connected by the rooms they meet in, etc etc.
There are two common implementation styles for graphs: adjacency lists and adjacency matrices.
To represent a graph of V vertices using an adjacency matrix, we store a VxV matrix, and in position [i][j] we store a value of true if there is an edge from vertex i to vertex j, or a value of false otherwise.
To represent a graph of V vertices using adjacency lists, for each vertex we maintain a linked list containing the vertices it is adjacent to (i.e. the vertices connected to it by an edge).
We'll spend a considerable amount of time studying the two implementations and their implications, along with the some of the most common approaches used to explore and manipulate graphs.
To start off with, we'll consider a very simple adjacency-matrix implementation, initially just as a set of global variables and functions. (In the lab you'll create a class-based version of the implementation.)
/**********************************************************/ /******** ADJACENCY MATRIX IMPLEMENTATION OF A GRAPH ******/ /***************** WITHOUT USING CLASSES ******************/ /**********************************************************/ #include <iostream> #include <string> using namespace std; /**********************************************************/ /******** GLOBAL TYPES, CONSTANTS, AND VARIABLES **********/ /**********************************************************/ // define the maximum number of nodes in the graph const int VERTICES = 32; // store the number of nodes currently in the graph // (initially 0, and cannot exceed VERTICES) int numnodes = 0; // define the contents of an individual node in the graph struct node { int nodeid; // the node's position in Graph[] string nodename; // text data for the application float nodedata; // numeric data for the application }; // store pointers to the graph nodes in an array, // Graph[i] contains a pointer to the node whose id is i node *Graph[VERTICES]; // create the adjacency matrix for the graph, // edges[i][j] is true iff there is an edge // going from node i to node j bool Edges[VERTICES][VERTICES]; /**********************************************************/ /************** GRAPH FUNCTIONS ***************************/ /**********************************************************/ // List of graph functions void empty_graph(); bool remove_node(int id); int insert_node(string name, float data); bool lookup_node(int id, string &name, float &data); bool add_edge(int src_id, int dest_id); bool remove_edge(int src_id, int dest_id); bool check_edge(int src_id, int dest_id); bool print_node(int id); void print_graph(); int get_graphsize(); int get_maxsize(); // call print_node on each node in the graph void print_graph() { for (int i = 0; i < VERTICES; i++) if (Graph[i]) print_node(i); } // return the number of nodes currently in the graph int get_graphsize() { return numnodes; } // return the number of nodes the graph is capable of holding // (i.e. vertices) int get_maxsize() { return VERTICES; } // initialize an empty graph, // wiping out the old graph (if any) void empty_graph() { for (int i = 0; i < VERTICES; i++) { delete Graph[i]; Graph[i] = NULL; for (int j = 0; j < VERTICES; j++) Edges[i][j] = false; } numnodes = 0; } // if a node with the specified id exists, // it is removed from the graph // along with all edges to and from it // returns true iff a node is removed bool remove_node(int id) { if ((id < 0) || (id >= VERTICES) || (Graph[id] == NULL)) return false; delete Graph[id]; Graph[id] = NULL; for (int i = 0; i < VERTICES; i++) { Edges[i][id] = false; Edges[id][i] = false; } numnodes--; return true; } // create a node and insert it's pointer in the first // free graph position, returning it's id value // returns -1 if unsuccessful int insert_node(string name, float data) { int id = 0; while (id < VERTICES) { if (Graph[id] == NULL) { Graph[id] = new node; if (!Graph[id]) return -1; Graph[id]->nodeid = id; Graph[id]->nodename = name; Graph[id]->nodedata = data; numnodes++; return id; } id++; } return -1; } // copy the name and data from the node in position id // return true iff successful bool lookup_node(int id, string &name, float &data) { if ((id < 0) || (id >= VERTICES) || (Graph[id] == NULL)) return false; name = Graph[id]->nodename; data = Graph[id]->nodedata; return true; } // create an edge from the node in position src_id // to the node in position dest_id // return true iff a new edge is successfully added bool add_edge(int src_id, int dest_id) { if ((src_id < 0) || (src_id >= VERTICES) || (Graph[src_id] == NULL)) return false; if ((dest_id < 0) || (dest_id >= VERTICES) || (Graph[dest_id] == NULL)) return false; if (Edges[src_id][dest_id]) return false; Edges[src_id][dest_id] = true; return true; } // remove the edge from the node in position src_id // to the node in position dest_id // return true iff an edge is successfully removed bool remove_edge(int src_id, int dest_id) { if ((src_id < 0) || (src_id >= VERTICES) || (Graph[src_id] == NULL)) return false; if ((dest_id < 0) || (dest_id >= VERTICES) || (Graph[dest_id] == NULL)) return false; if (!Edges[src_id][dest_id]) return false; Edges[src_id][dest_id] = false; return true; } // return true iff there is an edge from the node in // position src_id to the node in position dest_id bool check_edge(int src_id, int dest_id) { if ((src_id < 0) || (src_id >= VERTICES) || (Graph[src_id] == NULL)) return false; if ((dest_id < 0) || (dest_id >= VERTICES) || (Graph[dest_id] == NULL)) return false; return Edges[src_id][dest_id]; } // display the name, data and data for the node in // position id, then list the node ids for every // node which can be reached via one edge from // this node bool print_node(int id) { if ((id < 0) || (id >= VERTICES) || (Graph[id] == NULL)) { cout << "Invalid node: " << id << endl; return false; } cout << "Vertex " << id << ": ("; cout << Graph[id]->nodename << ", " << Graph[id]->nodedata << ") outgoing edges to =>"; for (int i = 0; i < VERTICES; i++) { if (Edges[id][i]) cout << i << ","; } cout << endl; return true; } /**********************************************************/ /******** APPLICATION FUNCTIONS ***************************/ /**********************************************************/ // List of application functions char getcommand(); void printmenu(); bool processcommand(char cmd); int getint(); float getfloat(); int main() { printmenu(); char cmd = getcommand(); while (processcommand(cmd)) { cout << endl; cmd = getcommand(); } return 0; } char getcommand() { cout << "Enter your command choice (G, V, E, I, A, R, C, H, Q, S, M, N)" << endl; char cmd; cin >> cmd; cmd = toupper(cmd); switch (cmd) { case 'G': case 'V': case 'E': case 'I': case 'A': case 'R': case 'C': case 'S': case 'M': case 'N': case 'H': case 'Q': return cmd; default: cout << "You have entered an invalid command" << endl; return getcommand(); } } void printmenu() { cout << "Enter I to insert a new vertex," << endl; cout << " or A to add an edge," << endl; cout << " or N to start a new (empty) graph," << endl; cout << " or V to display a single vertex," << endl; cout << " or E to display a single edge," << endl; cout << " or G to print the entire graph," << endl; cout << " or R to remove a vertex," << endl; cout << " or C to eliminate an edge," << endl; cout << " or M to lookup the number of elements the graph can hold," << endl; cout << " or S to lookup the number of elements currently in the graph," << endl; cout << " or H for help," << endl; cout << " or Q to quit." << endl; } bool processcommand(char cmd) { string key; float data; int id, src, dest; bool result1, result2; switch (cmd) { case 'H': printmenu(); break; case 'I': cout << "Enter the text data for the new vertex" << endl; cin >> key; data = getfloat(); cout << "(" << key << "," << data << ") "; id = insert_node(key, data); if (id >= 0) cout << "was inserted with id " << id << endl; else cout << "was not inserted correctly" << endl; break; case 'S': cout << "There are " << get_graphsize() << " vertices in the graph" << endl; break; case 'M': cout << "The graph can hold " << get_maxsize() << " vertices" << endl; break; case 'N': empty_graph(); cout << "The graph has been emptied" << endl; break; case 'R': id = getint(); result1 = lookup_node(id, key, data); result2 = remove_node(id); if (result1 && result2) { cout << "Removed vertex " << id << " ("; cout << key << "," << data << ")" << endl; } else { cout << "Unable to remove vertex " << id << endl; } break; case 'G': cout << "The graph contents are: " << endl; print_graph(); break; case 'V': id = getint(); print_node(id); break; case 'E': cout << "For the source, "; src = getint(); cout << "For the destination, "; dest = getint(); if (check_edge(src, dest)) cout << "There is an edge from " << src << " to " << dest << endl; else cout << "There is no edge from " << src << " to " << dest << endl; break; case 'A': cout << "For the source, "; src = getint(); cout << "For the destination, "; dest = getint(); if (add_edge(src, dest)) cout << "Added an edge from " << src << " to " << dest << endl; else cout << "Unable to add an edge from " << src << " to " << dest << endl; break; case 'C': cout << "For the source, "; src = getint(); cout << "For the destination, "; dest = getint(); if (remove_edge(src, dest)) cout << "Removed an edge from " << src << " to " << dest << endl; else cout << "Unable to remove an edge from " << src << " to " << dest << endl; break; case 'Q': return false; default: return true; } return true; } int getint() { string tmp; cout << "Enter a vertex id" << endl; cin >> tmp; return atoi(tmp.c_str()); } float getfloat() { string tmp; cout << "Enter a numeric value" << endl; cin >> tmp; return atof(tmp.c_str()); } |