Graph consists of
Graph G = <V,E>
Degree of a node : number of adjacent edges.
Degree of a graph : max number of adjacent edges.
Diameter: max distance between two ndoes, following the shortest path
Star | Clique/Complete Graph | Line/Path | Cycle | Bipartite Graph | |
---|---|---|---|---|---|
Diameter | 2 | 1 | n-1 | n/2 or n/2-1 | Max: n-1 |
Degree | n-1 | n-1 | 2 | 2 | 2 |
Nodes divided into two sets, with no edges between nodes in the same set.
Max diameter: n-1 In cycle, count no of nodes -> even yes, odd not a bipartite
Graph consists of nodes + edges
A[v][w] = 1 iff (v,w) subset of E A^2 = length 2 paths
Using Adj List:
for a graph G = (V,E)
Using Adj Matrix:
Rule: If the graph is dense, using adjacent matrix or else use adjacent list
dense: E = theta(V^2)
Adjacency Matrix | Adjacency List | |
---|---|---|
fast query | are v,w neighbours | find me any neighbours && enumerate all neighbours |
slow query | find me any neighbour of v && enumerate all neighbours | are v,w neighbours |
level[i]
from level[i-1]
BFS(Node[] nodeList, int startId) {
boolean[] visited = new boolean[nodeList.length];
Arrays.fill(visited, false);
int[] parent = new int[nodelist.length];
Arrays.fill(parent, -1);
for (int start = 0; start < nodeList.length; start++) {
if (!visited[start]){
// Main code goes here!
Collection<Integer> frontier = new Collection<Integer>;
frontier.add(startId);
while (!frontier.isEmpty()){
Collection<Integer> nextFrontier = new … ;
for (Integer v : frontier) {
for (Integer w : nodeList[v].nbrList) {
if (!visited[w]) {
visited[w] = true;
parent[w] = v;
nextFrontier.add(w);
}
}
}
frontier = nextFrontier;
}
}
}
}
When does BFS failed to visit every node?
- when the graph has two components
Running time using adjacency List: O(V+E)
- Vertex v = “start” once ==> O(V)
- Vertex v add to nextFrontier once ==> O(V)
- after added never re-added
- Each v.nbrList is enumerated once. ==> O(E)
- when v is removed from frontier
using an adjacency matrix: O(V^2)
DFS-visit(Node[] nodeList, boolean[] visited, int startId){
for (Integer v : nodeList[startId].nbrList) {
if (!visited[v]) {
visited[v] = true;
DFS-visit(nodeList, visited, v);
}
}
}
DFS(Node[] nodeList){
boolean[] visited = new boolean[nodeList.length];
Arrays.fill(visited, false);
for (start = i; start<nodeList.length; start++) {
if (!visited[start]){
visited[start] = true;
DFS-visit(nodeList, visited, start);
}
}
}
DFS parent graph is a Tree. DFS does not contain the shortest path.
Running time of DFS is
O(V+E)
using Adj List.
Running time of DFS is
O(V^2)
using Adj Matrix.
using same algorithm
BFS -> queue
DFS -> stack
DFS + BFS: visited every node and edge but not every path!
n = V.length;
for (i=0; i<n; i++)
for (Edge e : graph)
relax(e)
When to stop early
- When an entire sequencce of E relax operations have no effect.
Running Time:
- O(EV) Negative weights:
- No issues, but negative weight cycle cannot Detect negative weights cycles:
- Run bell-man ford for v+1 iterations, if last iteration has changes -> negative weight cycle if all same weight -> Use bfs
public Dijkstra{
private Graph G;
private IPriorityQueue pq = new PriQueue();
private double[] distTo;
searchPath(int start) {
pq.insert(start, 0.0);
distTo = new double[G.size()];
Arrays.fill(distTo, INFTY);
distTo[start] = 0;
while (!pq.isEmpty()) {
int w = pq.deleteMin();
for (Edge e : G[w].nbrList)
relax(e);
}
}
relax(Edge e) {
int v = e.from();
int w = e.to();
double weight = e.weight();
if (distTo[w] > distTo[v] + weight) {
distTo[w] = distTo[v] + weight;
parent[w] = v;
if (pq.contains(w))
pq.decreaseKey(w, distTo[w]);
else
pq.insert(w, distTo[w]);
}
}
}
AVL tree for priority queue
- insert, deletemin, decreasekey, contain
- logn , logn, logn, 1 Running time:
- O(E log v)
- edge is relaxed once, node is added to pq once
- O((V+E)log V) = O(E log V)
Can we stop as soon as we dequeue the dest
- yes. A vertex is finished when it is dequeued. No negative weights and No reweight!
MUST BE ACYCLIC
Running time
Unique?
Running time:
- O(E log V)
Running time
Longest path?
working on weighted, undirected graph
For every vertex, the minimum outgoing edge is always part of the MST
General algo:
Bad algo:
Basic idea:
// Initialize priority queue
PriorityQueue pq = new PriorityQueue();
for (Node v : G.V()) {
pq.insert(v, INFTY);
}
pq.decreaseKey(start, 0);
// Initialize set S
HashSet<Node> S = new HashSet<Node>();
S.put(start);
// Initialize parent hash table
HashMap<Node,Node> parent = new HashMap<Node,Node>();
parent.put(start, null);
while (!pq.isEmpty()){
Node v = pq.deleteMin();
S.put(v);
for each (Edge e : v.edgeList()){
Node w = e.otherNode(v);
if (!S.get(w)) {
pq.decreaseKey(w, e.getWeight());
parent.put(w, v);
}
}
}
Basic idea:
Data structure:
// Sort edges and initialize
Edge[] sortedEdges = sort(G.E());
ArrayList<Edge> mstEdges = new ArrayList<Edge>();
UnionFind uf = new UnionFind(G.V());
// Iterate through all the edges, in order
for (int i=0; i<sortedEdges.length; i++) {
Edge e = sortedEdges[i]; // get edge
Node v = e.one(); // get node endpoints
Node w = e.two();
if (!uf.find(v,w)) { // in the same tree?
mstEdges.add(e); // save edge
uf.union(v,w); // combine trees
}
}
Performance:
Initially:
One “Boruvka” Step:
Initially:
At each step:
Termination:
Conclusion:
Total time:
what if all edges have same weight => O(E) => DFS/BFS
- A MST contains exactly (V-1) edges
- Every spanning tree contains (V-1) edges
- thus any spanning tree find by DFS/BFS is MST
What if all the edges have weights from {1..10}?
Idea: Use an array of size 10
What if all the edges have weights from {1..10}?
Implement Priority Queue:
only those with one root:
NP hard
Algorithm SteinerMST:
O(T) < 2* OPT(G)
Operation | Unsorted Array | Sorted Array | AVL tree |
---|---|---|---|
insert | O(1) | O(n) | O(logn) |
extractMax | O(n) | O(1) | O(logn) |
Max height - Math.floor(log n)
{
find(int p, int q)
return(componentId[p] == componentId[q]);
union(int p, int q)
updateComponent = componentId[q]
for (int i=0; i<componentId.length; i++)
if (componentId[i] == updateComponent)
componentId[i] = componentId[p];
}
{
find(int p, int q)
while (parent[p] != p) p = parent[p];
while (parent[q] != q) q = parent[q];
return (p == q);
union(int p, int q)
while (parent[p] != p) p = parent[p];
while (parent[q] != q) q= parent[q];
parent[p] = q;
}
union(1,4) => which tree to make as the root? the one with larger weight
{
union(int p, int q)
while (parent[p] !=p) p = parent[p];
while (parent[q] !=q) q = parent[q];
if (size[p] > size[q] {
parent[q] = p; // Link q to p
size[p] = size[p] + size[q];
}
else {
parent[p] = q; // Link p to q
size[q] = size[p] + size[q];
}
}
Maximum depth of tree
- O(logn)
- when T1 is merged with T2, depth increased only when size(T1+T2) > 2* size(T1)
- Running time
- find: O(logn)
- Union: O(logn)
findRoot(int p) {
root = p;
while (parent[root] != root) root = parent[root];
while (parent[p] != p) {
temp = parent[p];
parent[p] = root;
p = temp;
}
return root;
}
Theorem: Starting from empty, any sequence of m union/find operations on n objects takes: O(n +mα(m,n)time.