Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ $ java -cp classes com.williamfiset.algorithms.search.BinarySearch
### Tree algorithms

- [:movie_camera:](https://www.youtube.com/watch?v=2FFq2_je7Lg) [Rooting an undirected tree](src/main/java/com/williamfiset/algorithms/graphtheory/treealgorithms/RootingTree.java) **- O(V+E)**
- [:movie_camera:](https://www.youtube.com/watch?v=OCKvEMF0Xac) [Identifying isomorphic trees](src/main/java/com/williamfiset/algorithms/graphtheory/treealgorithms/TreeIsomorphism.java) **- O(?)**
- [:movie_camera:](https://www.youtube.com/watch?v=OCKvEMF0Xac) [Identifying isomorphic trees](src/main/java/com/williamfiset/algorithms/graphtheory/treealgorithms/TreeIsomorphism.java) **- O(V*log(V))**
- [:movie_camera:](https://www.youtube.com/watch?v=nzF_9bjDzdc) [Tree center(s)](src/main/java/com/williamfiset/algorithms/graphtheory/treealgorithms/TreeCenter.java) **- O(V+E)**
- [Tree diameter](src/main/java/com/williamfiset/algorithms/graphtheory/treealgorithms/TreeDiameter.java) **- O(V+E)**
- [:movie_camera:](https://www.youtube.com/watch?v=sD1IoalFomA) [Lowest Common Ancestor (LCA, Euler tour)](src/main/java/com/williamfiset/algorithms/graphtheory/treealgorithms/LowestCommonAncestorEulerTour.java) **- O(1) queries, O(nlogn) preprocessing**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,29 @@
/**
* Finds all articulation points on an undirected graph.
* Articulation Points (Cut Vertices) — Adjacency List
*
* <p>Tested against HackerEarth online judge at:
* An articulation point is a vertex whose removal disconnects the graph
* (or increases the number of connected components). This implementation
* uses Tarjan's DFS-based algorithm with low-link values.
*
* For each DFS tree rooted at node r, a non-root node u is an articulation
* point if it has a child v such that no vertex in the subtree rooted at v
* has a back edge to an ancestor of u:
*
* ids[u] <= low[v]
*
* The root node r is an articulation point if it has more than one child
* in the DFS tree.
*
* Works on disconnected graphs by running DFS from every unvisited node.
*
* See also: {@link BridgesAdjacencyList} for finding bridge edges.
*
* Tested against HackerEarth online judge at:
* https://www.hackerearth.com/practice/algorithms/graphs/articulation-points-and-bridges/tutorial
*
* Time: O(V + E)
* Space: O(V)
*
* @author William Fiset, william.alexandre.fiset@gmail.com
*/
package com.williamfiset.algorithms.graphtheory;
Expand All @@ -15,33 +35,38 @@

public class ArticulationPointsAdjacencyList {

private int n, id, rootNodeOutgoingEdgeCount;
private final int n;
private final List<List<Integer>> graph;
private boolean solved;
private int id, rootNodeOutgoingEdgeCount;
private int[] low, ids;
private boolean[] visited, isArticulationPoint;
private List<List<Integer>> graph;

public ArticulationPointsAdjacencyList(List<List<Integer>> graph, int n) {
if (graph == null || n <= 0 || graph.size() != n) throw new IllegalArgumentException();
this.graph = graph;
this.n = n;
}

// Returns the indexes for all articulation points in the graph even if the
// graph is not fully connected.
/**
* Returns a boolean array where index i is true if node i is an articulation point.
* Works even if the graph is not fully connected.
*/
public boolean[] findArticulationPoints() {
if (solved) return isArticulationPoint;

id = 0;
low = new int[n]; // Low link values
ids = new int[n]; // Nodes ids
low = new int[n];
ids = new int[n];
visited = new boolean[n];
isArticulationPoint = new boolean[n];

// Run DFS from each unvisited node to handle disconnected components.
for (int i = 0; i < n; i++) {
if (!visited[i]) {
rootNodeOutgoingEdgeCount = 0;
dfs(i, i, -1);
// Root is an articulation point only if it has 2+ children in the DFS tree.
isArticulationPoint[i] = (rootNodeOutgoingEdgeCount > 1);
}
}
Expand All @@ -51,48 +76,59 @@ public boolean[] findArticulationPoints() {
}

private void dfs(int root, int at, int parent) {

if (parent == root) rootNodeOutgoingEdgeCount++;

visited[at] = true;
low[at] = ids[at] = id++;

List<Integer> edges = graph.get(at);
for (Integer to : edges) {
for (int to : graph.get(at)) {
if (to == parent) continue;
if (!visited[to]) {
dfs(root, to, at);
low[at] = min(low[at], low[to]);
// If no vertex in the subtree rooted at 'to' can reach above 'at',
// then removing 'at' would disconnect 'to's subtree.
if (ids[at] <= low[to]) {
isArticulationPoint[at] = true;
}
} else {
// Back edge: update low-link to the earliest reachable ancestor.
low[at] = min(low[at], ids[to]);
}
}
}

/* Graph helpers */

// Initialize a graph with 'n' nodes.
public static List<List<Integer>> createGraph(int n) {
List<List<Integer>> graph = new ArrayList<>(n);
for (int i = 0; i < n; i++) graph.add(new ArrayList<>());
return graph;
}

// Add an undirected edge to a graph.
public static void addEdge(List<List<Integer>> graph, int from, int to) {
graph.get(from).add(to);
graph.get(to).add(from);
}

/* Example usage: */
// ==================== Main ====================

public static void main(String[] args) {
testExample1();
testExample2();
}

//
// 0 --- 1
// | /
// 2 -------- 3 --- 4
// |
// 5 --- 6
// | |
// 8 --- 7
//
// Articulation points: 2, 3, 5
//
private static void testExample1() {
int n = 9;
List<List<Integer>> graph = createGraph(n);
Expand All @@ -111,16 +147,18 @@ private static void testExample1() {
ArticulationPointsAdjacencyList solver = new ArticulationPointsAdjacencyList(graph, n);
boolean[] isArticulationPoint = solver.findArticulationPoints();

// Prints:
// Node 2 is an articulation
// Node 3 is an articulation
// Node 5 is an articulation
for (int i = 0; i < n; i++)
if (isArticulationPoint[i]) System.out.printf("Node %d is an articulation\n", i);
}

// Tests a graph with 3 nodes in a line: A - B - C
// Only node 'B' should be an articulation point.
//
// 0 --- 1 --- 2
//
// Articulation point: 1
//
private static void testExample2() {
int n = 3;
List<List<Integer>> graph = createGraph(n);
Expand All @@ -131,7 +169,6 @@ private static void testExample2() {
ArticulationPointsAdjacencyList solver = new ArticulationPointsAdjacencyList(graph, n);
boolean[] isArticulationPoint = solver.findArticulationPoints();

// Prints:
// Node 1 is an articulation
for (int i = 0; i < n; i++)
if (isArticulationPoint[i]) System.out.printf("Node %d is an articulation\n", i);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,26 @@
/**
* Often when working with trees we are given them as a graph with undirected edges, however
* sometimes a better representation is a rooted tree.
* Rooting an Undirected Tree
*
* <p>Time Complexity: O(V+E)
* Given an undirected tree as an adjacency list, this algorithm converts it
* into a rooted tree by performing a DFS from a chosen root node. Each node
* in the resulting tree stores its parent and children.
*
* Time: O(V + E)
* Space: O(V)
*
* @author William Fiset, william.alexandre.fiset@gmail.com
*/
package com.williamfiset.algorithms.graphtheory.treealgorithms;

import java.util.*;
import java.util.ArrayList;
import java.util.List;

public class RootingTree {

public static class TreeNode {
private int id;
private TreeNode parent;
private List<TreeNode> children;
private final int id;
private final TreeNode parent;
private final List<TreeNode> children;

// Useful constructor for root node.
public TreeNode(int id) {
Expand All @@ -25,7 +30,7 @@ public TreeNode(int id) {
public TreeNode(int id, TreeNode parent) {
this.id = id;
this.parent = parent;
children = new LinkedList<>();
this.children = new ArrayList<>();
}

public void addChildren(TreeNode... nodes) {
Expand Down Expand Up @@ -66,43 +71,63 @@ public int hashCode() {
}
}

/**
* Roots the undirected tree at the given node and returns the root TreeNode.
*/
public static TreeNode rootTree(List<List<Integer>> graph, int rootId) {
TreeNode root = new TreeNode(rootId);
return buildTree(graph, root);
}

// Do dfs to construct rooted tree.
/**
* Recursively builds the rooted tree via DFS. Skips the edge back to the
* parent to avoid cycles.
*/
private static TreeNode buildTree(List<List<Integer>> graph, TreeNode node) {
for (int childId : graph.get(node.id())) {
// Ignore adding an edge pointing back to parent.
// Ignore the edge pointing back to parent.
if (node.parent() != null && childId == node.parent().id()) {
continue;
}

TreeNode child = new TreeNode(childId, node);
node.addChildren(child);

buildTree(graph, child);
}
return node;
}

/** ********** TESTING ********* */
/* Graph helpers */

// Create a graph as a adjacency list
private static List<List<Integer>> createGraph(int n) {
public static List<List<Integer>> createGraph(int n) {
List<List<Integer>> graph = new ArrayList<>(n);
for (int i = 0; i < n; i++) graph.add(new LinkedList<>());
for (int i = 0; i < n; i++) graph.add(new ArrayList<>());
return graph;
}

private static void addUndirectedEdge(List<List<Integer>> graph, int from, int to) {
public static void addUndirectedEdge(List<List<Integer>> graph, int from, int to) {
graph.get(from).add(to);
graph.get(to).add(from);
}

// ==================== Main ====================

public static void main(String[] args) {

// Undirected tree:
//
// 0 - 1 - 2 - 3 - 4
// | |
// 6 5
// / \
// 7 8
//
// Rooted at 6:
//
// 6
// 2 7 8
// 1 3
// 0 4 5

List<List<Integer>> graph = createGraph(9);
addUndirectedEdge(graph, 0, 1);
addUndirectedEdge(graph, 2, 1);
Expand All @@ -113,12 +138,6 @@ public static void main(String[] args) {
addUndirectedEdge(graph, 6, 7);
addUndirectedEdge(graph, 6, 8);

// Rooted at 6 the tree should look like:
// 6
// 2 7 8
// 1 3
// 0 4 5

TreeNode root = rootTree(graph, 6);

// Layer 0: [6]
Expand All @@ -136,7 +155,8 @@ public static void main(String[] args) {
+ ", "
+ root.children.get(0).children.get(1).children);

// Rooted at 3 the tree should look like:
// Rooted at 3:
//
// 3
// 2 4 5
// 6 1
Expand Down
Loading
Loading