Graph ADT Inheritance

Post on 05-Feb-2016

48 views 0 download

Tags:

description

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) - PowerPoint PPT Presentation

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