From 01706119d734ae32c17d64431c24226238ff7c26 Mon Sep 17 00:00:00 2001 From: sujal Date: Wed, 1 Apr 2026 19:38:02 +0530 Subject: [PATCH 1/3] Add dynamic programming on DAG using Kahn's algorithm with tests --- .../algorithms/dp/DagDynamicProgramming.java | 133 ++++++++++++++++++ .../dp/DagDynamicProgrammingTest.java | 72 ++++++++++ 2 files changed, 205 insertions(+) create mode 100644 src/main/java/com/williamfiset/algorithms/dp/DagDynamicProgramming.java create mode 100644 src/test/java/com/williamfiset/algorithms/dp/DagDynamicProgrammingTest.java diff --git a/src/main/java/com/williamfiset/algorithms/dp/DagDynamicProgramming.java b/src/main/java/com/williamfiset/algorithms/dp/DagDynamicProgramming.java new file mode 100644 index 000000000..de2ce61f6 --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/dp/DagDynamicProgramming.java @@ -0,0 +1,133 @@ +package com.williamfiset.algorithms.dp; + +import java.util.*; + +/** + * Dynamic Programming on Directed Acyclic Graphs (DAG). + * + *

This implementation demonstrates how to apply dynamic programming on a DAG + * using a topological ordering (Kahn's algorithm). + * + *

Although DAGs are graph structures, this implementation emphasizes the DP + * formulation where each state depends on previously computed states in a + * linearized order. + * + *

Example use-case: counting the number of ways to reach each node from a source. + * + *

Time Complexity: O(V + E) + *

Space Complexity: O(V + E) + */ +public class DagDynamicProgramming { + + private static final long MOD = 1_000_000_007; + + // Make public so test classes can access it + public static class Edge { + int from, to, weight; + + public Edge(int from, int to, int weight) { + this.from = from; + this.to = to; + this.weight = weight; + } + } + + /** + * Performs topological sorting using Kahn's algorithm. + * + * @param graph adjacency list + * @param numNodes number of nodes + * @return topological ordering or empty array if cycle exists + */ + public static int[] kahnTopoSort(Map> graph, int numNodes) { + + int[] indegree = new int[numNodes]; + + // Compute indegree of each node + for (int u = 0; u < numNodes; u++) { + for (Edge edge : graph.getOrDefault(u, Collections.emptyList())) { + indegree[edge.to]++; + } + } + + Queue q = new ArrayDeque<>(); + for (int i = 0; i < numNodes; i++) { + if (indegree[i] == 0) q.add(i); + } + + int[] topo = new int[numNodes]; + int index = 0; + + while (!q.isEmpty()) { + int u = q.poll(); + topo[index++] = u; + + for (Edge edge : graph.getOrDefault(u, Collections.emptyList())) { + if (--indegree[edge.to] == 0) q.add(edge.to); + } + } + + // Cycle detection + if (index != numNodes) return new int[0]; // Graph contains a cycle + + return topo; + } + + /** + * Counts number of ways to reach each node from a source in a DAG. + * + * @param graph adjacency list + * @param source starting node + * @param numNodes number of nodes + * @return dp array where dp[i] = number of ways to reach node i, + * or null if the graph contains a cycle + */ + public static long[] countWaysDAG( + Map> graph, int source, int numNodes) { + + int[] topo = kahnTopoSort(graph, numNodes); + if (topo.length == 0) return null; // Graph contains a cycle + + long[] dp = new long[numNodes]; + dp[source] = 1; + + for (int u : topo) { + if (dp[u] == 0L) continue; + + for (Edge edge : graph.getOrDefault(u, Collections.emptyList())) { + dp[edge.to] = (dp[edge.to] + dp[u]) % MOD; + } + } + + return dp; + } + + public static void main(String[] args) { + + final int N = 6; + Map> graph = new HashMap<>(); + + for (int i = 0; i < N; i++) { + graph.put(i, new ArrayList<>()); + } + + // Example DAG + graph.get(0).add(new Edge(0, 1, 1)); + graph.get(0).add(new Edge(0, 2, 1)); + graph.get(1).add(new Edge(1, 3, 1)); + graph.get(2).add(new Edge(2, 3, 1)); + graph.get(3).add(new Edge(3, 4, 1)); + + int source = 0; + + long[] dp = countWaysDAG(graph, source, N); + + if (dp == null) { + System.out.println("Graph contains a cycle!"); + return; + } + + System.out.println("Ways from source:"); + System.out.println(Arrays.toString(dp)); + } +} \ No newline at end of file diff --git a/src/test/java/com/williamfiset/algorithms/dp/DagDynamicProgrammingTest.java b/src/test/java/com/williamfiset/algorithms/dp/DagDynamicProgrammingTest.java new file mode 100644 index 000000000..9251bf9fb --- /dev/null +++ b/src/test/java/com/williamfiset/algorithms/dp/DagDynamicProgrammingTest.java @@ -0,0 +1,72 @@ +package com.williamfiset.algorithms.dp; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.*; + +import org.junit.jupiter.api.Test; + +public class DagDynamicProgrammingTest { + + private Map> createGraph(int n) { + Map> graph = new HashMap<>(); + for (int i = 0; i < n; i++) { + graph.put(i, new ArrayList<>()); + } + return graph; + } + + @Test + public void testSimpleDAGWays() { + + int n = 5; + Map> graph = createGraph(n); + + graph.get(0).add(new DagDynamicProgramming.Edge(0, 1, 1)); + graph.get(0).add(new DagDynamicProgramming.Edge(0, 2, 1)); + graph.get(1).add(new DagDynamicProgramming.Edge(1, 3, 1)); + graph.get(2).add(new DagDynamicProgramming.Edge(2, 3, 1)); + graph.get(3).add(new DagDynamicProgramming.Edge(3, 4, 1)); + + long[] dp = DagDynamicProgramming.countWaysDAG(graph, 0, n); + + assertNotNull(dp); + assertEquals(1, dp[0]); + assertEquals(1, dp[1]); + assertEquals(1, dp[2]); + assertEquals(2, dp[3]); // two paths: 0->1->3 and 0->2->3 + assertEquals(2, dp[4]); + } + + @Test + public void testDisconnectedGraph() { + + int n = 4; + Map> graph = createGraph(n); + + graph.get(0).add(new DagDynamicProgramming.Edge(0, 1, 1)); + + long[] dp = DagDynamicProgramming.countWaysDAG(graph, 0, n); + + assertNotNull(dp); + assertEquals(1, dp[0]); + assertEquals(1, dp[1]); + assertEquals(0, dp[2]); // unreachable + assertEquals(0, dp[3]); // unreachable + } + + @Test + public void testCycleDetection() { + + int n = 3; + Map> graph = createGraph(n); + + graph.get(0).add(new DagDynamicProgramming.Edge(0, 1, 1)); + graph.get(1).add(new DagDynamicProgramming.Edge(1, 2, 1)); + graph.get(2).add(new DagDynamicProgramming.Edge(2, 0, 1)); // cycle + + long[] dp = DagDynamicProgramming.countWaysDAG(graph, 0, n); + + assertNull(dp); // cycle detected + } +} \ No newline at end of file From 4e5ca53bd397697a4dad92c3fa5a96273ab5f021 Mon Sep 17 00:00:00 2001 From: sujal Date: Wed, 1 Apr 2026 22:48:37 +0530 Subject: [PATCH 2/3] Remove unused edge weight and MOD, also simplify edge representation for unweighted DAG DP --- .../algorithms/dp/DagDynamicProgramming.java | 66 ++++++++----------- 1 file changed, 29 insertions(+), 37 deletions(-) diff --git a/src/main/java/com/williamfiset/algorithms/dp/DagDynamicProgramming.java b/src/main/java/com/williamfiset/algorithms/dp/DagDynamicProgramming.java index de2ce61f6..025fe7808 100644 --- a/src/main/java/com/williamfiset/algorithms/dp/DagDynamicProgramming.java +++ b/src/main/java/com/williamfiset/algorithms/dp/DagDynamicProgramming.java @@ -5,45 +5,37 @@ /** * Dynamic Programming on Directed Acyclic Graphs (DAG). * - *

This implementation demonstrates how to apply dynamic programming on a DAG + *

+ * This implementation demonstrates how to apply dynamic programming on a DAG * using a topological ordering (Kahn's algorithm). * - *

Although DAGs are graph structures, this implementation emphasizes the DP - * formulation where each state depends on previously computed states in a - * linearized order. + *

+ * Example use-case: counting the number of ways to reach each node from a + * source. * - *

Example use-case: counting the number of ways to reach each node from a source. - * - *

Time Complexity: O(V + E) - *

Space Complexity: O(V + E) + *

+ * Time Complexity: O(V + E) + * Space Complexity: O(V + E) */ public class DagDynamicProgramming { - private static final long MOD = 1_000_000_007; - - // Make public so test classes can access it + // Minimal edge representation (only what is needed) public static class Edge { - int from, to, weight; + int to; - public Edge(int from, int to, int weight) { - this.from = from; + public Edge(int to) { this.to = to; - this.weight = weight; } } /** * Performs topological sorting using Kahn's algorithm. - * - * @param graph adjacency list - * @param numNodes number of nodes - * @return topological ordering or empty array if cycle exists */ public static int[] kahnTopoSort(Map> graph, int numNodes) { int[] indegree = new int[numNodes]; - // Compute indegree of each node + // Compute indegree for (int u = 0; u < numNodes; u++) { for (Edge edge : graph.getOrDefault(u, Collections.emptyList())) { indegree[edge.to]++; @@ -52,7 +44,8 @@ public static int[] kahnTopoSort(Map> graph, int numNodes) { Queue q = new ArrayDeque<>(); for (int i = 0; i < numNodes; i++) { - if (indegree[i] == 0) q.add(i); + if (indegree[i] == 0) + q.add(i); } int[] topo = new int[numNodes]; @@ -63,39 +56,38 @@ public static int[] kahnTopoSort(Map> graph, int numNodes) { topo[index++] = u; for (Edge edge : graph.getOrDefault(u, Collections.emptyList())) { - if (--indegree[edge.to] == 0) q.add(edge.to); + if (--indegree[edge.to] == 0) { + q.add(edge.to); + } } } // Cycle detection - if (index != numNodes) return new int[0]; // Graph contains a cycle + if (index != numNodes) + return new int[0]; return topo; } /** * Counts number of ways to reach each node from a source in a DAG. - * - * @param graph adjacency list - * @param source starting node - * @param numNodes number of nodes - * @return dp array where dp[i] = number of ways to reach node i, - * or null if the graph contains a cycle */ public static long[] countWaysDAG( Map> graph, int source, int numNodes) { int[] topo = kahnTopoSort(graph, numNodes); - if (topo.length == 0) return null; // Graph contains a cycle + if (topo.length == 0) + return null; long[] dp = new long[numNodes]; dp[source] = 1; for (int u : topo) { - if (dp[u] == 0L) continue; + if (dp[u] == 0L) + continue; for (Edge edge : graph.getOrDefault(u, Collections.emptyList())) { - dp[edge.to] = (dp[edge.to] + dp[u]) % MOD; + dp[edge.to] += dp[u]; } } @@ -112,11 +104,11 @@ public static void main(String[] args) { } // Example DAG - graph.get(0).add(new Edge(0, 1, 1)); - graph.get(0).add(new Edge(0, 2, 1)); - graph.get(1).add(new Edge(1, 3, 1)); - graph.get(2).add(new Edge(2, 3, 1)); - graph.get(3).add(new Edge(3, 4, 1)); + graph.get(0).add(new Edge(1)); + graph.get(0).add(new Edge(2)); + graph.get(1).add(new Edge(3)); + graph.get(2).add(new Edge(3)); + graph.get(3).add(new Edge(4)); int source = 0; From a57a08f7c2ad636175e2a589ae8135be62c87888 Mon Sep 17 00:00:00 2001 From: sujal Date: Wed, 1 Apr 2026 22:59:10 +0530 Subject: [PATCH 3/3] Removed unused edge weight and simplified edge structure to match an unweighted DAG. Updated tests accordingly. --- .../dp/DagDynamicProgrammingTest.java | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/test/java/com/williamfiset/algorithms/dp/DagDynamicProgrammingTest.java b/src/test/java/com/williamfiset/algorithms/dp/DagDynamicProgrammingTest.java index 9251bf9fb..9fdb47326 100644 --- a/src/test/java/com/williamfiset/algorithms/dp/DagDynamicProgrammingTest.java +++ b/src/test/java/com/williamfiset/algorithms/dp/DagDynamicProgrammingTest.java @@ -22,11 +22,11 @@ public void testSimpleDAGWays() { int n = 5; Map> graph = createGraph(n); - graph.get(0).add(new DagDynamicProgramming.Edge(0, 1, 1)); - graph.get(0).add(new DagDynamicProgramming.Edge(0, 2, 1)); - graph.get(1).add(new DagDynamicProgramming.Edge(1, 3, 1)); - graph.get(2).add(new DagDynamicProgramming.Edge(2, 3, 1)); - graph.get(3).add(new DagDynamicProgramming.Edge(3, 4, 1)); + graph.get(0).add(new DagDynamicProgramming.Edge(1)); + graph.get(0).add(new DagDynamicProgramming.Edge(2)); + graph.get(1).add(new DagDynamicProgramming.Edge(3)); + graph.get(2).add(new DagDynamicProgramming.Edge(3)); + graph.get(3).add(new DagDynamicProgramming.Edge(4)); long[] dp = DagDynamicProgramming.countWaysDAG(graph, 0, n); @@ -34,7 +34,7 @@ public void testSimpleDAGWays() { assertEquals(1, dp[0]); assertEquals(1, dp[1]); assertEquals(1, dp[2]); - assertEquals(2, dp[3]); // two paths: 0->1->3 and 0->2->3 + assertEquals(2, dp[3]); // 0->1->3 and 0->2->3 assertEquals(2, dp[4]); } @@ -44,7 +44,7 @@ public void testDisconnectedGraph() { int n = 4; Map> graph = createGraph(n); - graph.get(0).add(new DagDynamicProgramming.Edge(0, 1, 1)); + graph.get(0).add(new DagDynamicProgramming.Edge(1)); long[] dp = DagDynamicProgramming.countWaysDAG(graph, 0, n); @@ -61,9 +61,9 @@ public void testCycleDetection() { int n = 3; Map> graph = createGraph(n); - graph.get(0).add(new DagDynamicProgramming.Edge(0, 1, 1)); - graph.get(1).add(new DagDynamicProgramming.Edge(1, 2, 1)); - graph.get(2).add(new DagDynamicProgramming.Edge(2, 0, 1)); // cycle + graph.get(0).add(new DagDynamicProgramming.Edge(1)); + graph.get(1).add(new DagDynamicProgramming.Edge(2)); + graph.get(2).add(new DagDynamicProgramming.Edge(0)); // cycle long[] dp = DagDynamicProgramming.countWaysDAG(graph, 0, n);