CS 2223 Dec 03 2015
Expected reading: pp. 528-537
Daily Exercise:
A philosopher is a blind man in a dark room looking for a black cat that isn’t there. A theologian is the man who finds it.
H. L. Mencken
1 Working With Graphs
1.1 Homework4 Adjustment
On question 2, I had asked students to run 512 trials where N varies from 8 to 1,048,576. This simply takes too long. You should have your code only run from 8 to 65,536 which will be enough time to demonstrate the results.
1.2 Graphs
Alright. Time to get back on track with our lectures. We are down to nine more to go! I hope you’ve had the chance to read up on the Graph concept from yesterday’s lecture.
I want to start by considering the fundamental operations for graphs as shown in the following table:
public class | Graph |
|
Graph(int V) | create graph with V vertices an no edges | |
Graph(In in) | load graph from input | |
int | V() | number of vertices |
int | E() | number of edges |
void | addEdge(v,w) | add v-w to graph |
Iterable<Integer> | adj(int v) | vertices adjacent to v |
String | toString() | string representation |
As we will see, you can implement a wide variety of algorithms simply by working with this Graph API. Now there are two proposed strategies for storing graphs.
Adjacency Matrix – Use a V-by-V boolean two dimensional array where entry [v][w] determines whether there is an edge from v to w. Space is prohibitive and would be useful only for very dense graphs.
Array of Adjacency Lists – Use a one-dimensional vector of size V to record the collection of edges that are adjacent to each vertex. In an undirected graph, each edge appears twice.
We will work in lecture using the Adjacency Lists implementation and you will get a chance to see the Adjacency Matrix implementation on a homework assignment.
As summarized on page 527, the order of growth performance for each of the graph implementations is as follows:
Comparison | Adjacency Matrix | Adjacency lists |
space | V2 | E + V |
add edge v-w | 1 | 1 |
check w adjacent to v | 1 | degree(v) |
iterate vertices adjacent to v | V | degree(v) |
Adjacency matrices have excessive space costs and unexpectedly high cost to iterate over neighbors of v.
1.3 Constructing Graphs
It is not as convenient to work with graphs because it takes so long to set them up "from scratch". It is common to store information in a file and load it up to instantiate the graph. On page 529 of the book there is an example file tinyG.txt which contains the following:
13 13 0 5 4 3 0 1 9 12 6 4 5 4 0 2 11 12 9 10 0 6 7 8 9 11 5 3
I’ve added a new constructor to Graph (still in algs.days.day19) which can load up this file and construct a Graph object represented by it. The first line shows the number of nodes (in this case 13). The second line shows the number of edges (oddly enough, 13 again). Then the next 13 lines all contain a single edge described by two integers representing the vertices in the graph.
When a Graph is constructed from this file, the following is the result.
The following shows the adjacency matrix representation for this graph.
00 01 02 03 04 05 06 07 08 09 10 11 12 00 1 1 1 1 01 1 02 1 03 1 1 04 1 1 1 05 1 1 1 06 1 1 07 1 08 1 09 1 1 1 10 1 11 1 1 12 1 1
1.4 Processing Graph Queries
Let’s get started with a simple request. Given the above graph, can you find all Triangles that exist, namely, three vertices u, v and w in which all three edges (u,v), (v,w) and (u,w) exist and these vertices are all different.
Let’s brainstorm for a bit. How would you imagine doing this? How would you make sure that you don’t double count the number of triangles that you see?
Sample code FindTriangle contains different variations on this problem. The basic premise looks like this:
for (int u = 0; u < g.V(); u++) { // for all vertices u... for (Integer v : g.adj(u)) { // find a neighbor v to u for (Integer w : g.adj(v)) { // then find neighbor w to v for (Integer x : g.adj(u)) { // and check if w is neighbor to u if (x == w) { // Triangle } } } } }
The above will overcount the number of triangles and so you need to add some restrictions. The most common one is to make sure that you only proceed if u < v < w, which eliminates the duplicates; find solution in the code.
This triangle finding example is rather specific and we likely need some more generic searching capability.
There are a number of common queries that you would have given a graph structure. The most common is based on reachability, namely, can you start at a given vertex v and find a path to another vertex w. And you must perform this computation as efficiently as possible.
1.5 Search Graphs
To motivate searching over graphs, consider the following maze:
Think about how you solve this maze (I have also included it on the handout for today). For the purpose of this course, let’s represent the maze as a graph. I only show a fragment of this construction:
In computer science we routinely transform problems; in this case, we are going to solve a maze by transforming it into a graph of NxM vertices (one for every positions in the N rows and M columns. An edge in the graph exists if two positions are neighbors with no wall between them. This is an example of a sparse graph.
1.6 Initial Approach to Searching a Graph
Let’s use some intuition to construct our first algorithm for searching through a graph. If only we had some way to keep track of our progress! Let’s imagine marking nodes that we have already seen.
public DepthFirstSearch (Graph g, int s) { marked = new boolean[g.V()]; this.g = g; dfs(s); } /** Continue DFS search over graph by visiting vertex v. */ void dfs (int v) { marked[v] = true; // we have now seen this vertex count++; // look into neighbors for (int w : g.adj(v)) { if (!marked[w]) { // will never dfs() on this vertex again dfs (w); } } }
This proceeds until all vertices reachable from the initial vertex s have been processed.
Now, in what order are the vertices processed? It depends on the way they are traversed with the for (int w: g.adj(v)) code. And the order unfortunately depends upon the way that the adjacency list stores the neighbors.
Since we are using a Bag to store the vertices, we have no way to know what this ordering will be. But does it really matter? Since this is a blind search the answer is no!
However, when we work on examples in class or perhaps on an exam, I will always assume that neighboring vertices are iterated in ascending order based upon their unique identifier.
1.7 Demonstrate DepthFirstSearch
Demonstrate using Shell and running DepthFirstSearch on the files tinyG.txt and tinyCG.txt.
There are several observations to make:
The number of invocations of dfs will also be ≤ V.
Each edge (u,v) is visited twice, once as a neighbor of u, and once as a neighbor of v.
The Depth of the recursion ≤ V.
1.8 Recovering Path
This small search utility demonstrates the behavior of Depth First Search, but it doesn’t really do much. Let’s add behavior to recover the path from a designated vertex, s, to any vertex that is reachable from s over the existing edges in a graph.
At first it seems we have to keep track of a lot of information, because there are so many directions in which the search might go. However, instead of thinking about the starting point of an arrow, focus on the ending point of the arrow.
The counter-intuitive solution to this problem is to record a separate one-dimensional vector for each vertex that records the previous vertex in a depth first search from the original s source vertex.
This is presented in the handout.
1.9 Maze Application
In the day20 package execute the Launch class to see a program that generates reasonably complicated mazes. One interesting thing about this program is that I use depth-first search (in the Maze class) to actually create the maze in the first place from an initial starting point where every wall exists.
If you left-click in the window you will see the results of a Depth First search over the graph. The trail computed by the solver marks positions as either Blue or Green. A Green position is one that has been confirmed as leading to a dead end.
To increase difficulty of the graphs, change the boxSize in MazePanel to a smaller number.
It happens on some mazes that the Depth First Solver runs into problem and causes a Stack Overflow. You can see this most readily by changing the boxSize to 5 and creating a very large maze.
1.10 Sample Exam Question
You are given the following graph. Assuming that all adjacency lists are ordered by increasing vertex number, conduct a depth first search of the graph starting at vertex 0 and stopping when you reach vertex 7.
Show your progress by coloring completed vertices in Black while marking in-progress vertices with an X.
1.11 Daily Question
Given a graph representation of a rectangular maze of N rows and M columns, what is the most number of edges that can exist? what is the fewest number of edges that can exist?
1.12 Version : 2015/12/04
(c) 2015, George Heineman