From 3d3a8855d7651d0b00c9723496d40dc6153cb0c3 Mon Sep 17 00:00:00 2001 From: Sujal Verma Date: Thu, 2 Apr 2026 00:38:09 +0530 Subject: [PATCH] Add Dynamic Programming on DAG (Kahn's Algorithm) with test cases (#1313) * Add dynamic programming on DAG using Kahn's algorithm with tests * Remove unused edge weight and MOD, also simplify edge representation for unweighted DAG DP * Removed unused edge weight and simplified edge structure to match an unweighted DAG. Updated tests accordingly. --- .../algorithms/dp/DagDynamicProgramming.java | 125 ++++++++++++++++++ .../dp/DagDynamicProgrammingTest.java | 72 ++++++++++ 2 files changed, 197 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..025fe7808 --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/dp/DagDynamicProgramming.java @@ -0,0 +1,125 @@ +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). + * + *

+ * 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 { + + // Minimal edge representation (only what is needed) + public static class Edge { + int to; + + public Edge(int to) { + this.to = to; + } + } + + /** + * Performs topological sorting using Kahn's algorithm. + */ + public static int[] kahnTopoSort(Map> graph, int numNodes) { + + int[] indegree = new int[numNodes]; + + // Compute indegree + 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]; + + return topo; + } + + /** + * Counts number of ways to reach each node from a source in a DAG. + */ + public static long[] countWaysDAG( + Map> graph, int source, int numNodes) { + + int[] topo = kahnTopoSort(graph, numNodes); + if (topo.length == 0) + return null; + + 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[u]; + } + } + + 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(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; + + 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..9fdb47326 --- /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(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); + + assertNotNull(dp); + assertEquals(1, dp[0]); + assertEquals(1, dp[1]); + assertEquals(1, dp[2]); + assertEquals(2, dp[3]); // 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(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(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); + + assertNull(dp); // cycle detected + } +} \ No newline at end of file