website/themes/cactus/exampleSite/content/posts/2020-05-01-algorithms-graphs.md
Real-world graphs tend to be sparse (huge number of vertices, small average vertex degree).
Typical applications:
Algorithm:
public class DepthFirstPaths{
private blloean[] marked;
private int[] edgeTO;
private int s;
public DepthFirstPaths(Graph G, int s)
{
// ...
dfs(G, s);
}
private void dfs(Graph Gm int v)
{
marked[v] = true;
for (int w : G.adj(v))
if (!marked[v])
{
dfs(G, w)
edgeTo[w] = v;
}
}
}
Propositions:
Typical applications:
Algorithm:
public class BreadthFirstPaths
{
private boolean[] marked;
private int[] edgeTo;
// ...
private void bfs(Graph G, int s)
{
Queue<Integer> q = new Queue<>();
q.enqueue(s);
marked[s] = ture;
while (!q.isEmpty())
{
int v = q.dequeue();
for (int w: G.adj(v))
{
if (!marked[w])
{
q.enqueue(w);
marked[w] = true;
edgeTo[w] = v;
}
}
}
}
}
Proposition:
The goal is to preprocess graph to answer queries of the form is v connected to w? in constant time.
The relation is connected to is an equivalence relation:
public class CC {
private boolean[] marked;
private int[] id;
private int count;
public CC(Graph G) {
marked = new boolean[G.V()];
id = new int[G.V()];
for (int v = 0, v < G.V(); v++) {
if (!marked[v]) {
dfs(G, v);
count++;
}
}
}
// ...
private void dfs(Graph G, int v) {
marked[v] = true;
id[v] = count;
for (int w : G.adj(v)) {
if (!marked[w]) {
dfs(G, w)
}
}
}
}
Problem: Is a given graph acylic?
TODO
Problem: Is the graph bipartite?
TODO
TODO
TODO
A directed graph (or digraph) is a set of vertices and a collection of directed edges. Each directed edge connects an ordered pair of vertices.
Again, use adjacency-lists representation
public class Digraph {
private final int V;
private final Bag<Integer>[] adj;
public Digraph(int V) {
this.V = V;
adj = (Bag<Integer>[]) new Bag[V];
for (int v = 0; v < V; v++) {
adj[v] = new Bag<Integer>[];
}
}
public void addEdge(int v, int w) {
adj[v].add(w);
}
public Iterable<Integer> adj(int v) {
return adj[v];
}
}
Reachabiliity problem: Find all vertices reachable from s along a directed path.
We can use the same dfs method as for undirected graphs.
Reachability applications:
Other DFS problems:
BFS problems:
Topological sort: Given a digraph, put the vertices in order such that all its directed edges point from a vertix earlier in the order to a vertex later in the order (or report impossible).
A digraph has a topological order if and only if it is a directed acyclic graph (DAG). Topological sort redraws DAG so all edges poitn upwards.
use DFS again. It can be proved that reverse postorder of a DAG is a topological order. (check P578 for the definition of Preorder/Postorder)
public class DepthFirstOrder {
private boolean[] marked;
private Stack<Integer> reversePost;
publiv DepthFirstOrder(Digraph G) {
reversePost = new Stack<Integer>();
marked = new boolean[G.V()];
for (int v = 0; v < G.V(); v++) {
if (!marked[v]) dfs(G, v);
}
}
private void dfs(Digrapg G, int v) {
marked[v] = true;
for (int w : G.adj(v)) {
if (!marked[w]) dfs(G, w)
}
reversePost.push(v);
}
}
To find out if a given digraph is a DAG, we can try to find a directec cycle in the digraph. Use DFS and a stack to track the cycle.
// TODO
Some very typical applications of directed cycle detection and topological sort: (A directed cycle means the problem is infeasible)
Vertices v and w are strongly connected if there is both a directed path from v to w and a directed path from w to v. Strong connectivity is an equvicalence relation.
Kosaraju-Sharir is easy to implement but difficutl to understand. It runs DFS twice:
TODO: ADD Proof
https://algs4.cs.princeton.edu/code/edu/princeton/cs/algs4/KosarajuSharirSCC.java.html
public class KosarajuSharirSCC {
private boolean[] marked; // marked[v] = has vertex v been visited?
private int[] id; // id[v] = id of strong component containing v
private int count; // number of strongly-connected components
/**
* Computes the strong components of the digraph {@code G}.
* @param G the digraph
*/
public KosarajuSharirSCC(Digraph G) {
// compute reverse postorder of reverse graph
DepthFirstOrder dfs = new DepthFirstOrder(G.reverse());
// run DFS on G, using reverse postorder to guide calculation
marked = new boolean[G.V()];
id = new int[G.V()];
for (int v : dfs.reversePost()) {
if (!marked[v]) {
dfs(G, v);
count++;
}
}
}
// DFS on graph G
private void dfs(Digraph G, int v) {
marked[v] = true;
id[v] = count;
for (int w : G.adj(v)) {
if (!marked[w]) dfs(G, w);
}
}
// ...
}
An edge-weighted-graph is a graph where we associate weight or costs with each edge. A spanning tree of an undirected edge-weighted graph G is a subgraph T that is both a tree (conneted and acyclic) and spanning (includes all of the vertices). Given an (connected) undirected edge-weighted graph G with V vertices and E edges, the MST of it must have V - 1 edges. If the graph is not connceted, we compute minimum spanning forest (MST of each component).
Edge: https://algs4.cs.princeton.edu/code/edu/princeton/cs/algs4/Edge.java.html
EdgeWeigthedGraph: https://algs4.cs.princeton.edu/code/edu/princeton/cs/algs4/EdgeWeightedGraph.java.html
For edges in ascending order of weight:
To efficiently solve this problem, use union-find :
TODO: Add code
The key to solve this problem is how do we find the crossing edge of minimal weight efficiently.
A lazy solution (in time proportional to $ElogE$, fair enough):
TODO: add code
A eager solution (in time proprotional to $ElogV$, better):
This solution uses an indexed priority queue data structure.
TODO: add code
Some variants:
Weighted directed edge: https://algs4.cs.princeton.edu/code/edu/princeton/cs/algs4/DirectedEdge.java.html
Edge-weighted digraph: https://algs4.cs.princeton.edu/code/edu/princeton/cs/algs4/EdgeWeightedDigraph.java.html
Use adjacency-lists implementation same as EdgeWeightedGraph
Our goal is to find the shortest path from s to every other vertex. As a result, what we find will be the shortest-paths tree (SPT) for source s.
private void relax(DirectedEdge e) {
int v = e.from(), w = e.to();
if (distTo[w] > distTo[v] + e.weight()) {
distTo[w] = distTo[v] + e.weight();
edgeTo[w] = e;
}
}
Given an edge-weighted digraph G, distTo[] are the shortest path distances from s iff:
Generic algorithm (to compute SPT from s) {
Initialize distTo[s] = 0 and distTo[v] = $\infty$
Repeat until optimality conditions are satisfied:
- Relax any edge
}
Efficient implementations:
When there is no nonnegative weight exists, we can use Dijkstra's algorithm.
public class DijkstraSP{
// ...
public DijkstraSP(EdgeWeightedDigraph G, int s) {
edgeTo = new DirectedEdge[G.V()];
distTo = new double[G.V()];
pq = new IndexMinPQ<Double>(G.V());
for (int v = 0; v < G.V(); v++) {
distTo[v] = Double.POSITIVE_INFINITY;
}
distTo[s] = 0;
pq.insert(s, 0.0);
while(!pq.isEmpty()) {
int v= pq.delMin();
for (DirectedEdge e : G.adj(v)) {
relax(e);
}
}
}
private void relax(DirectedEdge e) {
int v = e.from(), w = e.to();
if (distTo[w] > distTo[v] + e.weight()) {
distTo[w] = distTo[v] + e.weight();
edgeTo[w] = e;
if (pq.contains(w)) pq.decreaseKey(w, distTo[w]);
else pq.insert(w, distTo[w]);
}
}
}
Compare to Prim's algorithm:
When the graph is a DAG, we can consider vertices in topological order and do relaxing.
// ...
Topological topological = new Topological(G);
for (int v : topological.order()) {
for (DirectedEdge e : G.adj(v)) {
relax(e);
}
}
Seam carving: Resize an image without distortion.
Longest paths:
A SPT exists iff no negative cycles (a directed cycle whose sum of edge weights is negative).
When we want to find shortest paths with nagative weights, Dijkstra's algorithms doesn't work. We can use Bellman-Ford algorithm as long as there is no negative cycle in the graph. (Bellman-Ford algorithm is a dynamic programming algorithm)
// ...
public BellmanFordSP(EdgeWeightedDigraph G, int s) {
distTo = new double[G.V()];
edgeTo = new DirectedEdge[G.V()];
onQueue = new boolean[G.V()];
for (int v = 0; v < G.V(); v++)
distTo[v] = Double.POSITIVE_INFINITY;
distTo[s] = 0.0;
// Bellman-Ford algorithm
queue = new Queue<Integer>();
queue.enqueue(s);
onQueue[s] = true;
while (!queue.isEmpty() && !hasNegativeCycle()) {
int v = queue.dequeue();
onQueue[v] = false;
relax(G, v);
}
}
private void relax(EdgeWeightedDigraph G, int v) {
for (DirectedEdge e : G.adj(v)) {
int w = e.to();
if (distTo[w] > distTo[v] + e.weight()) {
distTo[w] = distTo[v] + e.weight();
edgeTo[w] = e;
if (!onQueue[w]) {
queue.enqueue(w);
onQueue[w] = true;
}
}
if (++cost % G.V() == 0) {
findNegativeCycle();
if (hasNegativeCycle()) return; // found a negative cycle
}
}
}
Bellman-Ford algorithm can also be used for finding a negative cycle.
Negative cycle application: arbitrage detection.