Trees and Traversals
Tree Definition - A tree consist of :
- A set of nodes
- A set of edges that connect those nodes
- Constrain : There is exactly one path between any two nodes. (No circle)
Rooted Trees Definition - A rooted tree is a tree where we have chosen one node as the root
- Every node N except the root has exactly one parent, defined as the first node on the path from N to the root
- A node with no child is called a leaf
Tree Traversal Orderings
Level order
- Visit top to bottom, left to right : DBFACEG
Depth First Traversals
-
PreOrder
-
Visit a node , then traverse its children.
-
KEY -> LEFT -> RIGHT: DBACFEG
PreOrder(BSTNode x){ if (x == null) { return;} print(x.key); preOrder(x.left); preOrder(x.right); }
-
-
InOrder
- Traverse left child , visit, then traverse right child .
- LEFT -> KEY -> RIGHT : ABCDEFG
inOrder(BSTNode x){ if (x == null) { return;} inOrder(x.left); print(x.key); inOrder(x.right); }
-
PostOrder
- Traverse left, traverse right ,then visit
- LEFT -> RIGHT -> KEY : ACBEGFD
postOrder(BSTNode x){ if (x == null) { return;} postOrder(x.left); postOrder(x.right); print(x.left); }
Graph
Graph definition - A graph consists of :
- A set of nodes
- A set of zero or more edges, each of which connects two nodes.
Simple Graph
- No edges that connect a vertex to itself ,i.e. NO “loop”
- No two edges that connect the same vertices ,i.e. NO “parallel edges”.
In cs61b , unless otherwise explicitly stated, all graphs will be simple
Graph types : Acyclic , cylic, directed ,undirected, with edges labels.
Some terminologies about Graph
- Graph
- Set of vertices, a.k.a. nodes
- Set of edges: pairs of vertices
- Vertices with an edge between are adjacent
- Optional : Vertices or edges may have labels (or weights)
- A path is s sequence of vertices connected by edges
- A simple path is a path without repeated vertices
- A cycle is a path whose first and last vertices are the same
- A graph with a cycle is ‘cyclic’
- Two vertices are connected if there is path between them.
- If all vertices are connected , we say the graph is connected.
Depth-First Traversal
s-t Connectivity
Problem Description : Given source vertex s and a target vertex t , is there a path between s and t?
A recursive algorithm for connected(s,t) :
- Mark s
- Does s==t ? If so return true
- Otherwise , if connected(v,t) for any unmarked neighbor v of s , return true
- Return False
- Note: Marking the node we have visited prevents us from a infinite loop*
- When the marked vertex has many neighbors , we often choose the smallest item first.
Depth First Search
-
DFS Preorder (dfs calls)
- Action is before DFS calls to neighbors
- E.g.: edgeTo[1] was set before DFS calls to neighbor 2 and 4
- One valid DFS preorder for this graph : 012543678
- Equivalent to the order of dfs calls.
- that is ,when we first mark the vertax , the vertax will be print
-
DFS Postorder (dfs returns)
- Action is after DFS calls to neighbors
- Example : dfs(s)
- Mark(s)
- For each unmarked neighbor n of s , dfs(n)
- print(s)
- Results for dfs(0) would be 347685210
- Equivalent to the order of dfs returns.
-
BFS Order
- Act in order of distance from s
- Analogous to “level order”. Search is wide , not deep
- 0 1 24 53 68 7
- Breadth First Search
- Initialize a queue with a starting vertex s and mark that vertex
- A queue is a list that has two operations : enqueue(addLast) and dequeue(removeFirst)
- Let us call this queue our fringe
- Repeat until queue is empty
- Remove vertex v from the front of the queue
- For each unmarked neighbor n of v
- Mark n
- Set edgeTo[n] = v (and / or distTo[n] = distTo[v] + 1)
- Add n to the end of queue
- Initialize a queue with a starting vertex s and mark that vertex
Graph API
To implement the graph algorithm like BFS and DFS , we need
- An API (Application Programming Interface)
- These are the Graph methods , including their signitures and behaviors
- Define how Graph client programmers must think.
- An underlying data structure to represent the graph
- Need to consider
- Runtime
- Memory Usage
- Difficulty of implementing various graph algorithm
Decision 1 # Integer Vertices
Common convention: Number nodes irrespective of “label” , and use number throughout the graph implementation. To lookup a vertex by label, we need to use a Map<Label,Integer>
public class Graph{
public Graph(int V); // Create empty graph with v vertices
public void addEdge(int v, int w); // add an edge v-w
Interable<Integer> adj(int v); // vertices adjacent to v
int V();
int E();
/** degree of vertex v in graph G ,degree = #edges*/
public static int degree(Graph G, int v){
int degree = 0;
for (int w : G.adj(v)){
degree += 1;
}
return degree;
}
/** print the edge*/
public static void print(Graph G){
for(int v = 0; v < G.V(); v++){
for (int w : G.adj(v)){
System.out.println(v+"-"+w);
}
}
}
}
Graph Representations
Adjacency Matrix
s/t directed | 0 | 1 | 2 |
---|---|---|---|
0 | 0 | 1 | 1 |
1 | 0 | 0 | 1 |
2 | 0 | 0 | 0 |
- For the undirected graph, each edge is represented twice in the matrix. Simplicity at the expense of space.
v-w undirected | 0 | 1 | 2 | 3 |
---|---|---|---|---|
0 | 0 | 1 | 0 | 0 |
1 | 1 | 0 | 1 | 0 |
2 | 0 | 1 | 0 | 1 |
3 | 0 | 0 | 1 | 0 |
-
For the undirected example :
- G.adj(2) would return an iterator where we can call next() up to two times
- next() return 1
- next() return 3
- Total runtime to iterate over all neighbors of v is Θ ( V ) \Theta(V) Θ(V)
- Underlying code has to iterate through entire array to handle next() and hasNext() calls
- G.adj(2) would return an iterator where we can call next() up to two times
-
Graph Pringting Runtime
for(int v = 0; v < G.V(); v+=1){ for(int w :G.adj(v){ System.out.println(v+'-'+w); } }
- To figure out the order of growth of the running time of the print client from before if the graph uses an adjacency-matrix representation, where V is the number of vertices , and E is the total number of edges
- Θ ( N 2 ) \Theta(N^2) Θ(N2)
- Runtime to iterate over v’neighbors
- Θ ( N ) \Theta(N) Θ(N)
- How many vertices do we consider?
- V times
- To figure out the order of growth of the running time of the print client from before if the graph uses an adjacency-matrix representation, where V is the number of vertices , and E is the total number of edges
Edge sets : Collection of all edges
Consider the directed graph , we can use the HashSet<Edge> , where each Edge is a pair of ints.
{ ( 0 , 1 ) , ( 0 , 2 ) , ( 1 , 2 ) } \{(0,1),(0,2),(1,2)\} {(0,1),(0,2),(1,2)}
Adjancency List
-
Common approach : Maintain array of lists indexed by vertex number
-
Most popular approach for representing graphs
-
Graph Pringting Runtime
for(int v = 0; v < G.V(); v+=1){ for(int w :G.adj(v){ System.out.println(v+'-'+w); } }
- To figure out the order of growth of the running time of the print client from before if the graph uses an adjacency-list representation, where V is the number of vertices , and E is the total number of edges
- Best case: Θ ( N ) \Theta(N) Θ(N) Worst case: Θ ( N 2 ) \Theta(N^2) Θ(N2)
- Runtime to iterate over v’neighbors
- Ω ( 1 ) \Omega(1) Ω(1) - O ( N ) O(N) O(N)
- List can be between 1 and V items
- How many vertices do we consider?
- V times
- All the case : Θ ( V + E ) \Theta(V+E) Θ(V+E)
- Create V iterators
- Print E times
- V is the total number of vertices
- E is total number of edges in the entire graph
- No matter what shape of the increasingly complex graphs we generate , as V and E grow , the runtime will always grow exactly as Θ ( V + E ) \Theta(V+E) Θ(V+E)
- Example Shape 1 : Very Sparse graph where E grows very slowly : every vertex is connected to its square : 2-4, 3-9, 4-16, etc.
- E is Θ ( V ) \Theta(\sqrt V) Θ(V) , runtime is Θ ( V + V ) \Theta(\sqrt V+V) Θ(V+V) , which is just Θ ( V ) \Theta(V) Θ(V)
- Example Shape 2 : Very dense graph where E grows very quickly: every vertex is connected to every other
- E is Θ ( V 2 ) \Theta(V^2) Θ(V2), runtime is Θ ( V 2 + V ) \Theta(V^2+V) Θ(V2+V) , which is just Θ ( V 2 ) \Theta(V^2) Θ(V2)
- Example Shape 1 : Very Sparse graph where E grows very slowly : every vertex is connected to its square : 2-4, 3-9, 4-16, etc.
- To figure out the order of growth of the running time of the print client from before if the graph uses an adjacency-list representation, where V is the number of vertices , and E is the total number of edges
Summary
Type | addEdge(s,t) | for(w : adj(v)) | print() | hasEdge(s,t) | space used |
---|---|---|---|---|---|
Adjacency Matrix | Θ ( 1 ) \Theta(1) Θ(1) | Θ ( V ) \Theta(V) Θ(V) | Θ ( V 2 ) \Theta(V^2) Θ(V2) | Θ ( 1 ) \Theta(1) Θ(1) | Θ ( V 2 ) \Theta(V^2) Θ(V2) |
Edges Set | Θ ( 1 ) \Theta(1) Θ(1) | Θ ( E ) \Theta(E) Θ(E) | Θ ( E ) \Theta(E) Θ(E) | Θ ( E ) \Theta(E) Θ(E) | Θ ( E ) \Theta(E) Θ(E) |
Adjacency List | Θ ( 1 ) \Theta(1) Θ(1) | Θ ( 1 ) \Theta(1) Θ(1) to Θ ( N ) \Theta(N) Θ(N) | Θ ( V + E ) \Theta(V+E) Θ(V+E) | Θ ( d e g r e e ( V ) ) \Theta(degree(V)) Θ(degree(V)) | Θ ( E + V ) \Theta(E+V) Θ(E+V) |
NOTES : print() and hasEdge(s,t) are not part of the Graph class’s API
Bare-Bones Undirected Graph Implementation
public class Graph{
private final int V;
private List<Integer>[] adj;
public Graph(int V){
this.V = V;
adj = (List<Integer>[