Graph ADT Inheritance
description
Transcript of Graph ADT Inheritance
Graph ADT Inheritance
Final Exam Not Thursday, May 20, 2-3:55am Check Spring course schedule
Tuesday, May 18, 2-3:55pm, Here in LSci 185
Use the study sheet Assortment of programming questions Like the last midterm, except, 200 points (half on Tables, Trees, Graphs) (half on material from first half of course)
Types of graphs
Graph ADT Adjacency lists
directed undirected
Adjacency matrices directed undirected
Abstract classes An abstract class encapsulates the most
fundamental operations of the class In C++ we call this an Abstract Base Class
We do not instantiate a Graph ADT Instead we preferring to instantiate one of
the four types of graph sub-ADT’s on the last slide.
Base and derived classes
The Graph ADT is a general template for all graphs. It is a base class
The four sub-ADT’s we have defined are derived classes
They share all the characteristics of the base class, plus they add their own implementations.
Inheritance In C++, it is common to establish
derived classes from base classes.
This is called ‘inheritance’.
Inheritance Hierarchy for Graphs
Graph
AdjList Graph
AdjMatrix Graph
Directed AdjList Graph
Undirected AdjList Graph
Directed AdjMatrix
Graph
Undirected AdjMatrix
Graph
The Graph ADT abstract base class
class graph { public: graph(int size) : n(size), m(0) { } virtual int vertexSize() { return n; } virtual int edgeSize() { return m; } virtual void addEdge(int fromV, int toV) = 0;
// specify abstract class protected: int n; // number of vertices int m; // number of edges };
C++ notes The constructor
graph(int size) : n(size), m(0) { } has no body to be included in another file
(like that of a derived type), but initializes n to the argument size, and assigns m to 0 recall: n is the number of vertices and m
is the number of edges
Deriving Adj. List graphs
The adjacency list graph is a sub-ADT of the Graph ADT.
We will establish it, so that a further classes (directed or undirected adjacency list ADT’s) can be derived from it.
Adj. List Graph ADT
Characteristics: A List L stores items of some type,
called ListElementType. The items in the List are ordered; the Lists (1,2) and (2,1) are distinct.
Operations void L.insert(ListElementType elem) Precondition: None. Postcondition: L = L with an instance of
elem added to the end of L.
first()
bool L.first(ListElementType &elem) Precondition: None. Postcondition: If the list is empty, none.
Otherwise, the variable elem contains the first item in L; the “next” item to be returned is the second in L.
Returns: true if and only if there is at least one element in L.
next() bool L.next(ListElementType &elem) Precondition: The “first” operation has been
called at least once. Postcondition: Variable elem contains the
next item in L, if there is one, and the next counter advances by one; if there is no next element, none.
Returns: true if and only if there was a next item.
Use of first and next For iteration
for (p=L.first(); p; p=L.next())
Using our ALGraph ADT
The adjacency list is essentially a list of linked lists.
The standard List ADT provides a way to iterate through a list, it does not provide a way for two iterative process to work simultaineously however.
One way to solve the dilemma is to create and ‘iterator class’
Iterator class An iterator class provides a movable
pointer. We can instantiate as many of them as
we need. In our case, one will iterate through
vertices while the other can be used to iterate through edges
friend functions Iterators only make sense however if they
have some other object to iterate on. Therefore, we will allow objects of our
iterator class to have access to the internal (private) portions of our List ADT.
We do this by declaring the class to be a ‘friend class’
friend class ListIter < ListElementType >;
List and ListIter Our List class will only construct the list
and insert new members
Our List iterator class (ListIter) will move pointers around the List as we need to perform various key List operations.
Class List: public section template < class ListElementType > class List { public: List() : head(0) { } virtual void insert(const ListElementType & elem); friend class ListIter < ListElementType >;
Class List: protected section
protected: struct Node; typedef Node * Link; struct Node { ListElementType elem; Link next; }; Link head; }; elem next
ListIter considerations
We will want to pass a list to the ListIter so that it constructs a pointer to that List and guarantees that the list will be not change through iteration (const)
We will also need to have some indication of when to stop iterating. Perhaps a sentinel value marking the end of the List under consideration.
Class ListIter: public section
template < class ListElementType > class ListIter { public: // constructor makes const List called myList // I is the list the iterator will work on ListIter(
const List < ListElementType > & l, ListElementType endFlag )
: myList(l), myEndFlag(endFlag), iterPtr(0) { } virtual ListElementType operator++();
Class ListIter: protected section
protected: const List < ListElementType > & myList; List < ListElementType >::Link iterPtr; ListElementType myEndFlag; }; Works because
of ‘friend’ status.
Overloaded operator++
template < class ListElementType > ListElementType ListIter < ListElementType > :: operator++() { if (iterPtr == 0) // if NULL point to head iterPtr = myList.head; else // else point to next Link iterPtr = iterPtr->next; // Now that the pointer is advanced... if (iterPtr) // if it now points to a Link return iterPtr->elem; // return the element else // else it points to NULL return myEndFlag; // return end-of-list }
insert() template < class ListElementType > void List < ListElementType > ::
insert(const ListElementType & elem) { // prepend to list Link addedNode = new Node; assert(addedNode); addedNode->elem = elem; addedNode->next = head; head = addedNode; }
AL Base Class
Finally, we can construct an adjacency list base class from the Graph abstract base class the ListIter class
Adjacency List Base Class header
#include "cx12-1.h" // graph base class #include "cx12-2.h" // list class
typedef List < int > IntList; typedef ListIter < int > IntListIter;
Class ALGraph()
class ALGraph : public graph { public: ALGraph(int size) : graph(size) { vertexList = new IntList[n]; assert(vertexList); } friend class NeighborIter; protected: IntList * vertexList; };
call the graphconstructor
Class NeighborIter()
class NeighborIter : public IntListIter { public: NeighborIter(const ALGraph & G, int startVertex) : IntListIter (G.vertexList[startVertex], G.n) { assert(startVertex < G.n); } };
UALists and DALists Now that we have defined
Graph abstract base class Iterator class Adjacency list class
We can derive the bottom-most ADTs Undirected adjacency lists Directed adjacency lists
Undirected ALGraph header
class UALGraph : public ALGraph { public: UALGraph(int size) : ALGraph(size) { } virtual void addEdge(int fromV, int toV); };
Implementation of UALGraph
void UALGraph::addEdge(int fromV, int toV) { assert(fromV < n && fromV >= 0 && toV < n && toV >= 0); vertexList[fromV].insert(toV); vertexList[toV].insert(fromV); m++; }
DALGraph header
#include "cx12-4.h" // ALGraph -- Adjacency List Base Class
class DALGraph : public ALGraph { public: DALGraph(int size) : ALGraph(size) { } virtual void addEdge(int fromV, int toV); };
Implementation for DALGraph
void DALGraph::addEdge(int fromV, int toV) { assert(fromV < n && fromV >= 0 && toV < n && toV >= 0); vertexList[fromV].insert(toV); m++; }
Topological Sorting A topological sort is a method of arranging the
nodes of a graph in some logical order. Example: the course prerequisites. The problem:
given a partial order of nodes in a graph find the total order that is consistent with the partial
order (in other words, rearrange the vertices without changing the edge relationships so we can see a linear progression).
Partial Order Example
0 1
3 4
22
1
3
5
4
Topsort solutions 0,1,2,3,4 0,2,3,1,4 There is often more than one total order
that displays the characteristics of the partial order.
A Topsort algorithm
If a vertex has no ‘in-edges’, then it can be placed in order right away (see 1 below).
0 1
3 4
22
1
3
5
4
Topsort algorithm after one step
0 1
3 4
21
Topsort algorithm (con’t)
0 1
3 4
21
Now look for another vertex with no in-edges and take it out of circulation.
Topsort after the second step
0 1
3 4
212
Topsort (con’t) Continue until all vertices have been considered.
Resulting total order: 1, 0, 3, 2, 4
Note, this retains the original partial order
Original partial order
0 1
3 4
22
1
3
5
4
Topsort problem
Topsort only works as long as the graph lends itself to ordering.
Graphs with cycles in them cannot be topologically sorted because there is a portion in which every node had an ‘in-edge’.
Graph with a cycle
0 1
2
Improvements To improve the Topsort algorithm we have a
number of things we can do. First, we must recognise cycles and abort. Second, when we search for nodes with no ‘in-
edges’ we should start with the likeliest candidates. These are nodes that were formerly adjacent to the last one we selected in our sort. After all, they just lost an in-edge and may not have any left.
Topsort algorithm nextLabel = 1 find all vertex with no in-edges and push them onto a stack while the stack is not empty do pop a vertex v from the stack label(v) = nextLabel add 1 to nextLabel remove all other edges from v if any neighbor of v now has no in-edge, push it onto the stack if all vertices labeled, report the labels else report that the digraph has a cycle
One Step in a Topological Sort
0 1
34
210
2
10
1
Step 1
0 0
1
Push 0 on the stack
Push 1 on the stack
Push all vertices without ‘in-edges’ onto the stack.
0 1
34
210
2
10
1
Step 2
0
1
Pop 1 off the stack
Topsort order
Pop off the top element and label it in topological order.
1
0 1
34
210
2
10
1
Step 3
0
3
Push 3 on the stack
Push all new vertices without ‘in-edges’ onto to stack.
1
Topsort order
1
0 1
34
210
2
10
1
Step 4
1
Topsort order
1
0
Pop 3 off the stack
32
0 1
34
210
2
10
1
Step 5
1
Topsort order
1
0
Pop 0 off the stack
32
3
Step 6
1
Topsort order
1
0
32
3
2
Push 2 on the stack
Step 7
Pop 2 off the stack
1
Topsort order
1
0
32
3
24
Step 8
4
Push 4 on the stack
1
Topsort order
1
0
32
3
24
Step 9
Pop 4 off the stack
1
Topsort order
1
0
32
3
24
45
Topological Sort #include "cx12-7.h" // directed adjacency list graphs #include "sx8-1.h" // stacks (supplemental version) #include "cx9-4.h" // queues #include <fstream.h>
int main() { // read graph from a file // first entry is size of graph const char * inFileName = "graph.dat"; ifstream ifs(inFileName); assert(ifs); // make sure graph exists
Topological sort (con’t)
int n; ifs >> n; DALGraph G(n); cout << "Created graph; n = " << G.vertexSize() << endl; // now read in the edges and add to the graph int u, v; while ( ifs >> u ) { ifs >> v; G.addEdge(u,v); } cout << "Edges in graph: m = " << G.edgeSize() << endl;
Topological sort (con’t)
// count the number of in-edges for each vertex int * vertices(new int[n]); assert(vertices); for (u = 0; u < n; u++) vertices[u] = 0; for (u = 0; u < n; u++) { NeighborIter ni(G,u); while ((v = ++ni) != n) vertices[v]++; }
Topological sort (con’t)
// put vertices with no in-edge onto a stack Stack < int > s; for (u = 0; u < n; u++) if (vertices[u] == 0) s.push(u); if (s.isEmpty()) { cout << "graph has a cycle!\n"; return 0; }
Topological sort (con’t) // begin topological sort // As each vertex is identified, put it into a queue and // decrement the number of in-edges for its neighbors int count = 0; // number of vertices found so far Queue < int > sortedEdges; while (!s.isEmpty()) { count++; u = s.pop(); sortedEdges.enqueue(u); // reduce in count for u's neighbors; // for each that goes to zero, put on stack NeighborIter ni(G,u);
Topological sort (con’t)
while ((v = ++ni) != n) { --vertices[v]; if (vertices[v] == 0) s.push(v); } }
Topological sort (con’t)
// check results if (count < n) cout << "Couldn't complete top sort -- cycle
present.\n"; cout << "Ordering for top sort: \n"; while (!sortedEdges.isEmpty()) cout << sortedEdges.dequeue() << '\t'; cout << endl; return n; }
The Adjacency Matrix Classes
We have just examined topological sorting with an adjacency list class
Now we turn to the adjacency matrix class to show how it can be used to demonstrate transitive closure.
Ad. Matrix Base Class #include "cx12-1.h" // graph base class
class amGraph : public graph { public: amGraph(int size); virtual bool edgeMember(int fromV, int toV); protected: int * * am; // am points to a pointer to int };
Implementation amGraph::amGraph(int size) : graph(size) { int i; am = new int * [n]; // make an array of pointers to int assert(am); for (i = 0; i < n; i++) { am[i] = new int[n]; // make an array of ints assert(am[i]); int j; for (j = 0; j < n; j++) // initialize the array to 0 am[i][j] = 0; } }
edgeMember()
bool amGraph::edgeMember(int fromV, int toV) { assert (fromV < n && toV < n && fromV >= 0 && toV >= 0); return bool(am[fromV][toV] != 0); }
Derived types of Adjacency Matrix
Now that we have defined the adjacency matrix, we are ready to proceed to our definitions of its two sub-ADTs
Directed Adjacency Matrix (DAM) Undirected Adjacency Matrix (UAM)
DAM Class header
#include "cx12-10.h" // adjacency matrix base class
class dAMGraph : public amGraph { public: dAMGraph(int size, int initialValue = 0) : amGraph(size) { } virtual void addEdge(int fromV, int toV); };
Implementation of DAM Class
void dAMGraph::addEdge(int fromV, int toV) { assert(fromV < n && toV < n && fromV >= 0 && toV >= 0); if (!edgeMember(fromV, toV)) { m++; am[fromV][toV] = 1; } }
UAM header file
class uAMGraph : public amGraph { public: uAMGraph(int size, int initialValue = 0) : amGraph(size) { } virtual void addEdge (int fromV, int toV); };
Implementation of UA Matrix Class
void uAMGraph::addEdge(int fromV, int toV) { assert(fromV < n && toV < n && fromV >= 0 && toV >= 0); if (!edgeMember(fromV, toV)) { m++; am[fromV][toV] = 1; am[toV][fromV] = 1; } }
Using UAM class The problem: Nodes on a network can
communicate between each other if there is a path.
Construct a ‘transitive closure’ graph that indicates all the possible communications links between nodes along paths
A Communications Network
1 3
2 4 6
5 7
8
dAM graph implementation
1 2 3 4 5 6 7 81 1 1 1 1 0 0 0 02 1 1 0 0 0 0 0 03 1 0 1 0 0 1 0 04 1 0 0 1 0 0 0 05 0 0 0 0 1 0 1 06 0 0 1 0 0 1 0 07 0 0 0 0 1 0 1 18 0 0 0 0 0 0 1 1
Definition: path
A sequence [v0,v1,…,vk] is a path from v0 to vk in an undirected graph G = (V,E) if and only if {vi, vj+1} E for all 1<= i < k. The length of the path is k.
OR... A sequence of vertices is a path if each pair of
vertices constitutes an edge.
Definition: transitive closure
For a graph G = (V, E), the transitive closure graph G* = (V, E*) has edge {v1,v2} E* if and only if there's a path [v1,…,v2] in graph G.
Transitive Closure of Graph
1 3
2 4 6
5 7
8
Transitive closure graph
1 2 3 4 5 6 7 81 1 1 1 1 0 1 0 02 1 1 1 1 0 1 0 03 1 1 1 1 0 1 0 04 1 1 1 1 0 1 0 05 0 0 0 0 1 0 1 16 1 1 1 1 0 1 0 07 0 0 0 0 1 0 1 18 0 0 0 0 1 0 1 1
Transitive closure algorithm
Mark all length 1 paths Revisit all vertices at the end of length 1
paths. If they have links other than the original, mark the length 2 paths for the original
Continue until all possible path lengths have been examined. (many nested for loops)
Transitive closure int main() { const char * inFileName = "graph2.dat"; // read graph from a file // first entry is size of graph ifstream ifs(inFileName); assert(ifs); int n; ifs >> n; uAMGraph G(n); cout << "Created graph; n = " << G.vertexSize() << endl; int u, v;
continued while ( ifs >> u ) { ifs >> v; G.addEdge(u,v); } cout << "Edges in graph: m = " << G.edgeSize() << endl; int step; for (step=0; step < n; step++) for (u = 0; u < n; u++) // rows for (v = 0; v < n; v++) // columns if (G.edgeMember(u,step) && G.edgeMember(step,v)) G.addEdge(u,v);
continued // print results for (u=0; u < n; u++) { cout << u << "\t: "; for (v = 0; v < n; v++) cout << (G.edgeMember(u,v)? "T " : "F "); cout << endl; } return 0; }
Chapter Summary
A graph represents relationships among items. Vertices represent the items, and edges represent the relationships.
In an undirected graph, an edge is a set; in a directed graph, it’s a directed pair.
Summary continued
An adjacency list is a data structure for representing a graph, by keeping a list of the neighbor vertices of each vertex.
An adjacency matrix is a data structure for representing a graph, by keeping a matrix of 0’s and 1’s in which each 1 corresponds with an edge.
Summary (continued)
An inheritance hierarchy can be used to represent the variations on a graph.
Abstract base classes allow the programmer to specify an interface for inherited objects.
Summary (continued) Iterator classes, implemented via friend
classes, provide the most flexible way to create iterators.
A topological sort finds an ordering of vertices consistent with the partial order represented by the edges.
Transitive closure of a graph contains an edge for every path in the original graph.
Searching Graphs Must be able to traverse the entire
structure without getting caught up endlessly in a cycle.
Two strategies depth-first breadth-first
Sample graph
B
E
H
C
F
I
G
J
D
Depth first search Choose a node to start at and mark it visited Find a connected node which has nost been
marked and mark it visited Repeat step 2 until the end of the path has
been found Return to the last node with unvisited
neighbors and repeat steps 2-4 until there are no unvisited nodes
DFS The Depth-First Search (DFS) algorithm can be
used to traverse all the vertices of a graph. DFS works as follows:
put the initial vertex into a stack; while the stack is not empty do{ pop a vertex v from the stack; visit vertex v; push all unvisited neighbors of v onto the
stack; }
Example
B
E
H
C
F
I
G
J
D
Visit BVisit EVisit HReturn to EVisit IReturn to EReturn to BVisit FVisit JReturn to FVisit CVisit GReturn to CVisit DReturn to C,F,B
B E H I F J C G D
Depth first considerations
There are many possible depth-first traversal orders depending on What node you start at What order you visit nodes in
However, from a given node, there is only one unique traversal for a given ordering principle (ie. left to right)
Implementing DFSearch
Use a UAM ADT G.connected(node1, node2) returns T/F
Use UAM or UAL Graph ADT to implement G.visited(node) returns visited T/F
Use boolean array indexed by node Use a queue to store visited node names Use a stack to store previously visited nodes
Stacks and queues
B E H I F J C G D
front rear
BBEBEHBEBEIBEBBFBFJBFBFCBFCGBFCBFCDBFCBFB
B
E
H
C
F
I
G
J
D
Breadth first Search Like DFS, except we visit all edges of a
node first instead of travelling down a path
BFS algorithm Choose a node to start at and mark it visited Find a connected node which has not been
marked visited and mark it visited Find another connected node and mark it
visited Repeat until no more unvisited connected
nodes Do for each node
Sample graph
B
E
H
C
F
I
G
J
D
Example
B
E
H
C
F
I
G
J
D Visit BVisit EVisit FVisit CReturn to EVisit HVisit IReturn to FVisit JReturn to CVisit GVisit D
B E F C H I J G D
Two queues, no stack
B E F C H I J G D
front rear
BBEBEFBEFCEFCEFCHEFCHIFCHIFCHIJCHIJCHIJGCHIJGDHIJGDIJGDJGDGDD
B
E
H
C
F
I
G
J
D