Skip to content

Commit 3d3a885

Browse files
authored
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.
1 parent 92284a1 commit 3d3a885

File tree

2 files changed

+197
-0
lines changed

2 files changed

+197
-0
lines changed
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
package com.williamfiset.algorithms.dp;
2+
3+
import java.util.*;
4+
5+
/**
6+
* Dynamic Programming on Directed Acyclic Graphs (DAG).
7+
*
8+
* <p>
9+
* This implementation demonstrates how to apply dynamic programming on a DAG
10+
* using a topological ordering (Kahn's algorithm).
11+
*
12+
* <p>
13+
* Example use-case: counting the number of ways to reach each node from a
14+
* source.
15+
*
16+
* <p>
17+
* Time Complexity: O(V + E)
18+
* Space Complexity: O(V + E)
19+
*/
20+
public class DagDynamicProgramming {
21+
22+
// Minimal edge representation (only what is needed)
23+
public static class Edge {
24+
int to;
25+
26+
public Edge(int to) {
27+
this.to = to;
28+
}
29+
}
30+
31+
/**
32+
* Performs topological sorting using Kahn's algorithm.
33+
*/
34+
public static int[] kahnTopoSort(Map<Integer, List<Edge>> graph, int numNodes) {
35+
36+
int[] indegree = new int[numNodes];
37+
38+
// Compute indegree
39+
for (int u = 0; u < numNodes; u++) {
40+
for (Edge edge : graph.getOrDefault(u, Collections.emptyList())) {
41+
indegree[edge.to]++;
42+
}
43+
}
44+
45+
Queue<Integer> q = new ArrayDeque<>();
46+
for (int i = 0; i < numNodes; i++) {
47+
if (indegree[i] == 0)
48+
q.add(i);
49+
}
50+
51+
int[] topo = new int[numNodes];
52+
int index = 0;
53+
54+
while (!q.isEmpty()) {
55+
int u = q.poll();
56+
topo[index++] = u;
57+
58+
for (Edge edge : graph.getOrDefault(u, Collections.emptyList())) {
59+
if (--indegree[edge.to] == 0) {
60+
q.add(edge.to);
61+
}
62+
}
63+
}
64+
65+
// Cycle detection
66+
if (index != numNodes)
67+
return new int[0];
68+
69+
return topo;
70+
}
71+
72+
/**
73+
* Counts number of ways to reach each node from a source in a DAG.
74+
*/
75+
public static long[] countWaysDAG(
76+
Map<Integer, List<Edge>> graph, int source, int numNodes) {
77+
78+
int[] topo = kahnTopoSort(graph, numNodes);
79+
if (topo.length == 0)
80+
return null;
81+
82+
long[] dp = new long[numNodes];
83+
dp[source] = 1;
84+
85+
for (int u : topo) {
86+
if (dp[u] == 0L)
87+
continue;
88+
89+
for (Edge edge : graph.getOrDefault(u, Collections.emptyList())) {
90+
dp[edge.to] += dp[u];
91+
}
92+
}
93+
94+
return dp;
95+
}
96+
97+
public static void main(String[] args) {
98+
99+
final int N = 6;
100+
Map<Integer, List<Edge>> graph = new HashMap<>();
101+
102+
for (int i = 0; i < N; i++) {
103+
graph.put(i, new ArrayList<>());
104+
}
105+
106+
// Example DAG
107+
graph.get(0).add(new Edge(1));
108+
graph.get(0).add(new Edge(2));
109+
graph.get(1).add(new Edge(3));
110+
graph.get(2).add(new Edge(3));
111+
graph.get(3).add(new Edge(4));
112+
113+
int source = 0;
114+
115+
long[] dp = countWaysDAG(graph, source, N);
116+
117+
if (dp == null) {
118+
System.out.println("Graph contains a cycle!");
119+
return;
120+
}
121+
122+
System.out.println("Ways from source:");
123+
System.out.println(Arrays.toString(dp));
124+
}
125+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package com.williamfiset.algorithms.dp;
2+
3+
import static org.junit.jupiter.api.Assertions.*;
4+
5+
import java.util.*;
6+
7+
import org.junit.jupiter.api.Test;
8+
9+
public class DagDynamicProgrammingTest {
10+
11+
private Map<Integer, List<DagDynamicProgramming.Edge>> createGraph(int n) {
12+
Map<Integer, List<DagDynamicProgramming.Edge>> graph = new HashMap<>();
13+
for (int i = 0; i < n; i++) {
14+
graph.put(i, new ArrayList<>());
15+
}
16+
return graph;
17+
}
18+
19+
@Test
20+
public void testSimpleDAGWays() {
21+
22+
int n = 5;
23+
Map<Integer, List<DagDynamicProgramming.Edge>> graph = createGraph(n);
24+
25+
graph.get(0).add(new DagDynamicProgramming.Edge(1));
26+
graph.get(0).add(new DagDynamicProgramming.Edge(2));
27+
graph.get(1).add(new DagDynamicProgramming.Edge(3));
28+
graph.get(2).add(new DagDynamicProgramming.Edge(3));
29+
graph.get(3).add(new DagDynamicProgramming.Edge(4));
30+
31+
long[] dp = DagDynamicProgramming.countWaysDAG(graph, 0, n);
32+
33+
assertNotNull(dp);
34+
assertEquals(1, dp[0]);
35+
assertEquals(1, dp[1]);
36+
assertEquals(1, dp[2]);
37+
assertEquals(2, dp[3]); // 0->1->3 and 0->2->3
38+
assertEquals(2, dp[4]);
39+
}
40+
41+
@Test
42+
public void testDisconnectedGraph() {
43+
44+
int n = 4;
45+
Map<Integer, List<DagDynamicProgramming.Edge>> graph = createGraph(n);
46+
47+
graph.get(0).add(new DagDynamicProgramming.Edge(1));
48+
49+
long[] dp = DagDynamicProgramming.countWaysDAG(graph, 0, n);
50+
51+
assertNotNull(dp);
52+
assertEquals(1, dp[0]);
53+
assertEquals(1, dp[1]);
54+
assertEquals(0, dp[2]); // unreachable
55+
assertEquals(0, dp[3]); // unreachable
56+
}
57+
58+
@Test
59+
public void testCycleDetection() {
60+
61+
int n = 3;
62+
Map<Integer, List<DagDynamicProgramming.Edge>> graph = createGraph(n);
63+
64+
graph.get(0).add(new DagDynamicProgramming.Edge(1));
65+
graph.get(1).add(new DagDynamicProgramming.Edge(2));
66+
graph.get(2).add(new DagDynamicProgramming.Edge(0)); // cycle
67+
68+
long[] dp = DagDynamicProgramming.countWaysDAG(graph, 0, n);
69+
70+
assertNull(dp); // cycle detected
71+
}
72+
}

0 commit comments

Comments
 (0)