Posts /

CS2040 Graph Summary Notes

Twitter Facebook Google+
25 Mar 2021

Graph revision

Basics

Graph consists of

Types of graph

Graph G = <V,E>

Terminology

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

Special graph

  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

Bipartite Graph

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

Representing a graph

Adjacency List

Graph consists of nodes + edges

Adjacency Matrix

A[v][w] = 1 iff (v,w) subset of E A^2 = length 2 paths

Adj List VS Adj Matrix

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

Searhcing a graph

Concept

Code implementation

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;
            }
        }
    }
}

Analysis

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

Shortest Path

Applications

Source of applications

Concept

Code

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);
        }
    }
}

Analysis

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.

BFS + DFS

using same algorithm

BFS -> queue

DFS -> stack

DFS + BFS: visited every node and edge but not every path!

Shortest Paths for Direted Graph

Bellman-Ford

Code

n = V.length;
for (i=0; i<n; i++)
    for (Edge e : graph)
        relax(e)

Analysis

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

Dijkstra

Concept

Code

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]);
        }
    }
}

Analysis

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!

Directed Graph

Topological order

  1. Sequential total ordering of all nodes
  2. Edges only point forward
  3. Not every directed graph has such ordering

    MUST BE ACYCLIC

DAG Topological Order using DFS

Using Kahn’s Algo

Concept

Implementation

Analysis

Running time:

  • O(E log V)

Shortest Path for DAG

Shortest Path for Tree

Minimum spanning tree

working on weighted, undirected graph

Properties

  1. No cycle
  2. If you cut a mst, the two copies are both msts
  3. (Cycle property) For every cycle, the maximum weight edge is not in mst 3.5. For every cycle, the minimum may/ may not in mst
  4. (Cut property) For every partition of the nodes, the minimum weight edge across the cut is in the MST.

For every vertex, the minimum outgoing edge is always part of the MST

Algorithm to find

General algo:

Bad algo:

  1. If the number of vertices is 1, then return.
  2. Divide the nodes into two sets.
  3. Recursively calculate the MST of each set.
  4. Find the lightest edge the connects the two sets and add it to the MST.
  5. Return.

Prim’s 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);
        }
    }
}

Analysis

Kruskal’s Algorithm

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
    }
}

Analysis

Performance:

Boruvka’s Algorithm

Initially:

One “Boruvka” Step:

Analysis

Initially:

At each step:

Termination:

Conclusion:

Total time:

variant

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:

Directed MST

only those with one root:

Steiner Tree

NP hard

Algorithm SteinerMST:

  1. For every pair of required vertices (v,w), calculate the shortest path from (v to w).
    • Use Dijkstra V times.
    • Or All-Pairs-Shortest-Paths
  2. Construct new graph on required nodes.
    • V = required nodes
    • E = shortest path distances
  3. Run MST on new graph.
    • Use Prim’s or Kruskal’s or Boruvka’s
    • MST gives edges on new graph
  4. Map new edges back to original graph.
    • Use shortest path discovered in Step 1.
    • Add these edges to Steiner MST.
    • Remove duplicates.

O(T) < 2* OPT(G)

Heap and Disjoint set

Priority queue

Operation Unsorted Array Sorted Array AVL tree
insert O(1) O(n) O(logn)
extractMax O(n) O(1) O(logn)

Using a Heap

Concept

  1. Heap ordering
    • priority[parent] >= priority[child]
  2. Complete binary tree
    • Every level is full, except the last
    • All nodes are as far left as possible

      Max height - Math.floor(log n)

implementation

Analysis

Store in array

Heap sort

STEP 1

Disjoint set

Quick-find

Quick-union

Code

{
  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;
}

Analysis

Optimisation

Weighted Union

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];
      }
}
Analysis

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)

Path compression

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;
}
Analysis

Weighted-union + path compression

Theorem: Starting from empty, any sequence of m union/find operations on n objects takes: O(n +mα(m,n)time.