From bdd85a8588e2852330b4c4f69a5476da184192f9 Mon Sep 17 00:00:00 2001
From: Stathis Veinoglou
Date: Thu, 14 Aug 2025 11:38:07 +0300
Subject: [PATCH 1/8] Add DFS with parent-completion constraint for DAG
traversal
---
.../thealgorithms/graph/GraphTraversal.java | 154 ++++++++++++++++++
.../graph/GraphTraversalTest.java | 87 ++++++++++
2 files changed, 241 insertions(+)
create mode 100644 src/main/java/com/thealgorithms/graph/GraphTraversal.java
create mode 100644 src/test/java/com/thealgorithms/graph/GraphTraversalTest.java
diff --git a/src/main/java/com/thealgorithms/graph/GraphTraversal.java b/src/main/java/com/thealgorithms/graph/GraphTraversal.java
new file mode 100644
index 000000000000..25793315b951
--- /dev/null
+++ b/src/main/java/com/thealgorithms/graph/GraphTraversal.java
@@ -0,0 +1,154 @@
+package com.thealgorithms.graph;
+
+import java.util.*;
+
+/**
+ * Graph traversals and utilities.
+ *
+ * This class includes a DFS variant that visits a successor only when
+ * all of its predecessors have already been visited. For each step,
+ * it emits an event either for a visit (with an increasing order) or for a skip
+ * explaining that not all parents were visited yet.
+ *
+ * Input is an adjacency list of successors. If a predecessor map is not
+ * provided, it is derived once from the successors map.
+ */
+public final class GraphTraversal {
+
+ private GraphTraversal() {
+ // utility class
+ }
+
+ /** An event emitted by the traversal: either a VISIT with an order, or a SKIP with a note. */
+ public static final class TraversalEvent {
+ private final T node;
+ private final Integer order; // non-null for visit, null for skip
+ private final String note; // non-null for skip, null for visit
+
+ private TraversalEvent(T node, Integer order, String note) {
+ this.node = node;
+ this.order = order;
+ this.note = note;
+ }
+
+ /** A visit event with an increasing order (0,1,2,...) */
+ public static TraversalEvent visit(T node, int order) {
+ return new TraversalEvent<>(Objects.requireNonNull(node), order, null);
+ }
+
+ /** A skip event with an explanatory note (e.g., not all parents visited yet). */
+ public static TraversalEvent skip(T node, String note) {
+ return new TraversalEvent<>(Objects.requireNonNull(node), null, Objects.requireNonNull(note));
+ }
+
+ public boolean isVisit() { return order != null; }
+ public boolean isSkip() { return order == null; }
+ public T node() { return node; }
+ public Integer order() { return order; }
+ public String note() { return note; }
+
+ @Override public String toString() {
+ return isVisit() ? "VISIT(" + node + ", order=" + order + ")" : "SKIP(" + node + ", " + note + ")";
+ }
+ }
+
+ /**
+ * DFS (recursive) that records the order of first visit starting at {@code start},
+ * but only recurses to a child when all its predecessors have been visited.
+ * If a child is encountered early (some parent unvisited), a SKIP event is recorded.
+ *
+ * Equivalent idea to the Python pseudo in the user's description (with successors and predecessors),
+ * but implemented in Java and returning a sequence of {@link TraversalEvent}s.
+ *
+ * @param successors adjacency list: for each node, its outgoing neighbors
+ * @param start start node
+ * @return immutable list of traversal events (VISITs with monotonically increasing order and SKIPs with messages)
+ * @throws IllegalArgumentException if {@code successors} is null
+ */
+ public static List> dfsRecursiveOrder(Map> successors, T start) {
+ if (successors == null) {
+ throw new IllegalArgumentException("successors must not be null");
+ }
+ // derive predecessors once
+ Map> predecessors = derivePredecessors(successors);
+ return dfsRecursiveOrder(successors, predecessors, start);
+ }
+
+ /**
+ * Same as {@link #dfsRecursiveOrder(Map, Object)} but with an explicit predecessors map.
+ */
+ public static List> dfsRecursiveOrder(
+ Map> successors, Map> predecessors, T start) {
+
+ if (successors == null || predecessors == null) {
+ throw new IllegalArgumentException("successors and predecessors must not be null");
+ }
+ if (start == null) {
+ return List.of();
+ }
+ if (!successors.containsKey(start) && !appearsAnywhere(successors, start)) {
+ return List.of(); // start not present in graph
+ }
+
+ List> events = new ArrayList<>();
+ Set visited = new HashSet<>();
+ int[] order = {0};
+ dfs(start, successors, predecessors, visited, order, events);
+ return Collections.unmodifiableList(events);
+ }
+
+ private static void dfs(
+ T u,
+ Map> succ,
+ Map> pred,
+ Set visited,
+ int[] order,
+ List> out) {
+
+ if (!visited.add(u)) {
+ return; // already visited
+ }
+ out.add(TraversalEvent.visit(u, order[0]++)); // record visit and increment
+
+ for (T v : succ.getOrDefault(u, List.of())) {
+ if (visited.contains(v)) {
+ continue;
+ }
+ if (allParentsVisited(v, visited, pred)) {
+ dfs(v, succ, pred, visited, order, out);
+ } else {
+ out.add(TraversalEvent.skip(v, "⛔ Skipping " + v + ": not all parents are visited yet."));
+ // do not mark visited; it may be visited later from another parent
+ }
+ }
+ }
+
+ private static boolean allParentsVisited(T node, Set visited, Map> pred) {
+ for (T p : pred.getOrDefault(node, List.of())) {
+ if (!visited.contains(p)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static boolean appearsAnywhere(Map> succ, T node) {
+ if (succ.containsKey(node)) return true;
+ for (List nbrs : succ.values()) {
+ if (nbrs != null && nbrs.contains(node)) return true;
+ }
+ return false;
+ }
+
+ private static Map> derivePredecessors(Map> succ) {
+ Map> pred = new HashMap<>();
+ // ensure keys exist for all nodes appearing anywhere
+ for (Map.Entry> e : succ.entrySet()) {
+ pred.computeIfAbsent(e.getKey(), k -> new ArrayList<>());
+ for (T v : e.getValue()) {
+ pred.computeIfAbsent(v, k -> new ArrayList<>()).add(e.getKey());
+ }
+ }
+ return pred;
+ }
+}
diff --git a/src/test/java/com/thealgorithms/graph/GraphTraversalTest.java b/src/test/java/com/thealgorithms/graph/GraphTraversalTest.java
new file mode 100644
index 000000000000..b2fe86914b74
--- /dev/null
+++ b/src/test/java/com/thealgorithms/graph/GraphTraversalTest.java
@@ -0,0 +1,87 @@
+package com.thealgorithms.graph;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import com.thealgorithms.graph.GraphTraversal.TraversalEvent;
+import java.util.*;
+import org.junit.jupiter.api.Test;
+
+class GraphTraversalTest {
+
+ // A -> B, A -> C, B -> D, C -> D (classic diamond)
+ private static Map> diamond() {
+ Map> g = new LinkedHashMap<>();
+ g.put("A", List.of("B", "C"));
+ g.put("B", List.of("D"));
+ g.put("C", List.of("D"));
+ g.put("D", List.of());
+ return g;
+ }
+
+ @Test
+ void dfsRecursiveOrder_emitsSkipUntilAllParentsVisited() {
+ List> events = GraphTraversal.dfsRecursiveOrder(diamond(), "A");
+
+ // Expect visits in order and a skip for first time we meet D (via B) before C is visited.
+ var visits = events.stream().filter(TraversalEvent::isVisit).toList();
+ var skips = events.stream().filter(TraversalEvent::isSkip).toList();
+
+ // Visits should be A(0), B(1), C(2), D(3) in some deterministic order given adjacency
+ assertThat(visits).hasSize(4);
+ assertThat(visits.get(0).node()).isEqualTo("A");
+ assertThat(visits.get(0).order()).isEqualTo(0);
+ assertThat(visits.get(1).node()).isEqualTo("B");
+ assertThat(visits.get(1).order()).isEqualTo(1);
+ assertThat(visits.get(2).node()).isEqualTo("C");
+ assertThat(visits.get(2).order()).isEqualTo(2);
+ assertThat(visits.get(3).node()).isEqualTo("D");
+ assertThat(visits.get(3).order()).isEqualTo(3);
+
+ // One skip when we first encountered D from B (before C was visited)
+ assertThat(skips).hasSize(1);
+ assertThat(skips.get(0).node()).isEqualTo("D");
+ assertThat(skips.get(0).note()).contains("not all parents");
+ }
+
+ @Test
+ void returnsEmptyWhenStartNotInGraph() {
+ Map> g = Map.of(1, List.of(2), 2, List.of(1));
+ assertThat(GraphTraversal.dfsRecursiveOrder(g, 99)).isEmpty();
+ }
+
+ @Test
+ void nullSuccessorsThrows() {
+ assertThrows(IllegalArgumentException.class, () -> GraphTraversal.dfsRecursiveOrder(null, "A"));
+ }
+
+ @Test
+ void worksWithExplicitPredecessors() {
+ Map> succ = new HashMap<>();
+ succ.put(10, List.of(20));
+ succ.put(20, List.of(30));
+ succ.put(30, List.of());
+
+ Map> pred = new HashMap<>();
+ pred.put(10, List.of());
+ pred.put(20, List.of(10));
+ pred.put(30, List.of(20));
+
+ var events = GraphTraversal.dfsRecursiveOrder(succ, pred, 10);
+ var visitNodes = events.stream().filter(TraversalEvent::isVisit).map(TraversalEvent::node).toList();
+ assertThat(visitNodes).containsExactly(10, 20, 30);
+ }
+
+ @Test
+ void cycleProducesSkipsButNoInfiniteRecursion() {
+ Map> succ = new LinkedHashMap<>();
+ succ.put("X", List.of("Y"));
+ succ.put("Y", List.of("X")); // 2-cycle
+
+ var events = GraphTraversal.dfsRecursiveOrder(succ, "X");
+ // Only X is visited; encountering Y from X causes skip because Y's parent X is visited,
+ // but when recursing to Y we'd hit back to X (already visited) and stop; no infinite loop.
+ assertThat(events.stream().anyMatch(TraversalEvent::isVisit)).isTrue();
+ assertThat(events.stream().filter(TraversalEvent::isVisit).map(TraversalEvent::node)).contains("X");
+ }
+}
From 7193d96ceee8383d488fbbbbd0a2a57d3aaf2126 Mon Sep 17 00:00:00 2001
From: Stathis Veinoglou
Date: Thu, 14 Aug 2025 11:39:38 +0300
Subject: [PATCH 2/8] warning in PartitionProblem.java affecting tests
---
.../com/thealgorithms/dynamicprogramming/PartitionProblem.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/PartitionProblem.java b/src/main/java/com/thealgorithms/dynamicprogramming/PartitionProblem.java
index 49c4a0a3a008..e1cdbf094d24 100644
--- a/src/main/java/com/thealgorithms/dynamicprogramming/PartitionProblem.java
+++ b/src/main/java/com/thealgorithms/dynamicprogramming/PartitionProblem.java
@@ -1,4 +1,4 @@
-/**
+/*
* @author Md Asif Joardar
*
* Description: The partition problem is a classic problem in computer science
From aa9c093a4642d890cf9bba43cc8f4935e1160934 Mon Sep 17 00:00:00 2001
From: Stathis Veinoglou
Date: Thu, 14 Aug 2025 11:54:26 +0300
Subject: [PATCH 3/8] added clang-format and updated javadoc
---
.../thealgorithms/graph/GraphTraversal.java | 58 +++++++++++--------
.../graph/GraphTraversalTest.java | 2 +-
2 files changed, 34 insertions(+), 26 deletions(-)
diff --git a/src/main/java/com/thealgorithms/graph/GraphTraversal.java b/src/main/java/com/thealgorithms/graph/GraphTraversal.java
index 25793315b951..077319b58dc7 100644
--- a/src/main/java/com/thealgorithms/graph/GraphTraversal.java
+++ b/src/main/java/com/thealgorithms/graph/GraphTraversal.java
@@ -3,16 +3,20 @@
import java.util.*;
/**
- * Graph traversals and utilities.
- *
- * This class includes a DFS variant that visits a successor only when
- * all of its predecessors have already been visited. For each step,
- * it emits an event either for a visit (with an increasing order) or for a skip
- * explaining that not all parents were visited yet.
- *
- * Input is an adjacency list of successors. If a predecessor map is not
- * provided, it is derived once from the successors map.
+ * DFS that visits a successor only when all its predecessors are already visited,
+ * emitting VISIT and SKIP events.
+ *
+ * This class includes a DFS variant that visits a successor only when all of its
+ * predecessors have already been visited
+ *
+ * Related reading:
+ *
+ *
*/
+
public final class GraphTraversal {
private GraphTraversal() {
@@ -23,7 +27,7 @@ private GraphTraversal() {
public static final class TraversalEvent {
private final T node;
private final Integer order; // non-null for visit, null for skip
- private final String note; // non-null for skip, null for visit
+ private final String note; // non-null for skip, null for visit
private TraversalEvent(T node, Integer order, String note) {
this.node = node;
@@ -41,13 +45,24 @@ public static TraversalEvent skip(T node, String note) {
return new TraversalEvent<>(Objects.requireNonNull(node), null, Objects.requireNonNull(note));
}
- public boolean isVisit() { return order != null; }
- public boolean isSkip() { return order == null; }
- public T node() { return node; }
- public Integer order() { return order; }
- public String note() { return note; }
+ public boolean isVisit() {
+ return order != null;
+ }
+ public boolean isSkip() {
+ return order == null;
+ }
+ public T node() {
+ return node;
+ }
+ public Integer order() {
+ return order;
+ }
+ public String note() {
+ return note;
+ }
- @Override public String toString() {
+ @Override
+ public String toString() {
return isVisit() ? "VISIT(" + node + ", order=" + order + ")" : "SKIP(" + node + ", " + note + ")";
}
}
@@ -77,8 +92,7 @@ public static List> dfsRecursiveOrder(Map> succ
/**
* Same as {@link #dfsRecursiveOrder(Map, Object)} but with an explicit predecessors map.
*/
- public static List> dfsRecursiveOrder(
- Map> successors, Map> predecessors, T start) {
+ public static List> dfsRecursiveOrder(Map> successors, Map> predecessors, T start) {
if (successors == null || predecessors == null) {
throw new IllegalArgumentException("successors and predecessors must not be null");
@@ -97,13 +111,7 @@ public static List> dfsRecursiveOrder(
return Collections.unmodifiableList(events);
}
- private static void dfs(
- T u,
- Map> succ,
- Map> pred,
- Set visited,
- int[] order,
- List> out) {
+ private static void dfs(T u, Map> succ, Map> pred, Set visited, int[] order, List> out) {
if (!visited.add(u)) {
return; // already visited
diff --git a/src/test/java/com/thealgorithms/graph/GraphTraversalTest.java b/src/test/java/com/thealgorithms/graph/GraphTraversalTest.java
index b2fe86914b74..185543bf5202 100644
--- a/src/test/java/com/thealgorithms/graph/GraphTraversalTest.java
+++ b/src/test/java/com/thealgorithms/graph/GraphTraversalTest.java
@@ -25,7 +25,7 @@ void dfsRecursiveOrder_emitsSkipUntilAllParentsVisited() {
// Expect visits in order and a skip for first time we meet D (via B) before C is visited.
var visits = events.stream().filter(TraversalEvent::isVisit).toList();
- var skips = events.stream().filter(TraversalEvent::isSkip).toList();
+ var skips = events.stream().filter(TraversalEvent::isSkip).toList();
// Visits should be A(0), B(1), C(2), D(3) in some deterministic order given adjacency
assertThat(visits).hasSize(4);
From cfdad197d81d07a0d0f7e92e8cd9310564b8ef41 Mon Sep 17 00:00:00 2001
From: Stathis Veinoglou
Date: Thu, 14 Aug 2025 12:08:25 +0300
Subject: [PATCH 4/8] optimized imports and rechecked camelCase format in tests
---
.../java/com/thealgorithms/graph/GraphTraversalTest.java | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/src/test/java/com/thealgorithms/graph/GraphTraversalTest.java b/src/test/java/com/thealgorithms/graph/GraphTraversalTest.java
index 185543bf5202..c702f7bd89d4 100644
--- a/src/test/java/com/thealgorithms/graph/GraphTraversalTest.java
+++ b/src/test/java/com/thealgorithms/graph/GraphTraversalTest.java
@@ -4,7 +4,10 @@
import static org.junit.jupiter.api.Assertions.assertThrows;
import com.thealgorithms.graph.GraphTraversal.TraversalEvent;
-import java.util.*;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
import org.junit.jupiter.api.Test;
class GraphTraversalTest {
@@ -20,7 +23,7 @@ private static Map> diamond() {
}
@Test
- void dfsRecursiveOrder_emitsSkipUntilAllParentsVisited() {
+ void dfsRecursiveOrderEmitsSkipUntilAllParentsVisited() {
List> events = GraphTraversal.dfsRecursiveOrder(diamond(), "A");
// Expect visits in order and a skip for first time we meet D (via B) before C is visited.
From 87d208e0b9a2b1d5e8b12e77ac0e99cdf21b090f Mon Sep 17 00:00:00 2001
From: Stathis Veinoglou
Date: Thu, 14 Aug 2025 12:18:01 +0300
Subject: [PATCH 5/8] removed .* import and made small visual change
---
.../com/thealgorithms/graph/GraphTraversal.java | 13 +++++++++++--
1 file changed, 11 insertions(+), 2 deletions(-)
diff --git a/src/main/java/com/thealgorithms/graph/GraphTraversal.java b/src/main/java/com/thealgorithms/graph/GraphTraversal.java
index 077319b58dc7..660cb5212fd6 100644
--- a/src/main/java/com/thealgorithms/graph/GraphTraversal.java
+++ b/src/main/java/com/thealgorithms/graph/GraphTraversal.java
@@ -1,6 +1,13 @@
package com.thealgorithms.graph;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
/**
* DFS that visits a successor only when all its predecessors are already visited,
@@ -141,7 +148,9 @@ private static boolean allParentsVisited(T node, Set visited, Map boolean appearsAnywhere(Map> succ, T node) {
- if (succ.containsKey(node)) return true;
+ if (succ.containsKey(node)) {
+ return true;
+ }
for (List nbrs : succ.values()) {
if (nbrs != null && nbrs.contains(node)) return true;
}
From 0a990c60fbee9497bb9cf92d9b7cf6d543560ebf Mon Sep 17 00:00:00 2001
From: Stathis Veinoglou
Date: Thu, 14 Aug 2025 12:22:42 +0300
Subject: [PATCH 6/8] replaced a inline return with correct {} block
---
src/main/java/com/thealgorithms/graph/GraphTraversal.java | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/main/java/com/thealgorithms/graph/GraphTraversal.java b/src/main/java/com/thealgorithms/graph/GraphTraversal.java
index 660cb5212fd6..7f35c4c036e2 100644
--- a/src/main/java/com/thealgorithms/graph/GraphTraversal.java
+++ b/src/main/java/com/thealgorithms/graph/GraphTraversal.java
@@ -152,7 +152,9 @@ private static boolean appearsAnywhere(Map> succ, T node) {
return true;
}
for (List nbrs : succ.values()) {
- if (nbrs != null && nbrs.contains(node)) return true;
+ if (nbrs != null && nbrs.contains(node)) {
+ return true;
+ }
}
return false;
}
From b113746feb371183d934cd9bd40e49487de4c949 Mon Sep 17 00:00:00 2001
From: StathisVeinoglou
Date: Fri, 15 Aug 2025 00:30:04 +0300
Subject: [PATCH 7/8] Removed changed in PartitionProblem.java, Renamed class
name to be straightforward about the implementation.Added full names instead
of shortcuts, and included record.
---
.../dynamicprogramming/PartitionProblem.java | 2 +-
...al.java => PredecessorConstrainedDfs.java} | 83 +++++++++----------
...ava => PredecessorConstrainedDfsTest.java} | 38 ++++-----
3 files changed, 58 insertions(+), 65 deletions(-)
rename src/main/java/com/thealgorithms/graph/{GraphTraversal.java => PredecessorConstrainedDfs.java} (66%)
rename src/test/java/com/thealgorithms/graph/{GraphTraversalTest.java => PredecessorConstrainedDfsTest.java} (67%)
diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/PartitionProblem.java b/src/main/java/com/thealgorithms/dynamicprogramming/PartitionProblem.java
index e1cdbf094d24..49c4a0a3a008 100644
--- a/src/main/java/com/thealgorithms/dynamicprogramming/PartitionProblem.java
+++ b/src/main/java/com/thealgorithms/dynamicprogramming/PartitionProblem.java
@@ -1,4 +1,4 @@
-/*
+/**
* @author Md Asif Joardar
*
* Description: The partition problem is a classic problem in computer science
diff --git a/src/main/java/com/thealgorithms/graph/GraphTraversal.java b/src/main/java/com/thealgorithms/graph/PredecessorConstrainedDfs.java
similarity index 66%
rename from src/main/java/com/thealgorithms/graph/GraphTraversal.java
rename to src/main/java/com/thealgorithms/graph/PredecessorConstrainedDfs.java
index 7f35c4c036e2..0fa01295c00e 100644
--- a/src/main/java/com/thealgorithms/graph/GraphTraversal.java
+++ b/src/main/java/com/thealgorithms/graph/PredecessorConstrainedDfs.java
@@ -24,53 +24,46 @@
*
*/
-public final class GraphTraversal {
+public final class PredecessorConstrainedDfs {
- private GraphTraversal() {
+ private PredecessorConstrainedDfs() {
// utility class
}
/** An event emitted by the traversal: either a VISIT with an order, or a SKIP with a note. */
- public static final class TraversalEvent {
- private final T node;
- private final Integer order; // non-null for visit, null for skip
- private final String note; // non-null for skip, null for visit
-
- private TraversalEvent(T node, Integer order, String note) {
- this.node = node;
- this.order = order;
- this.note = note;
+ public record TraversalEvent(
+ T node,
+ Integer order, // non-null for visit, null for skip
+ String note // non-null for skip, null for visit
+ ) {
+ public TraversalEvent {
+ Objects.requireNonNull(node);
+ // order and note can be null based on event type
}
/** A visit event with an increasing order (0,1,2,...) */
public static TraversalEvent visit(T node, int order) {
- return new TraversalEvent<>(Objects.requireNonNull(node), order, null);
+ return new TraversalEvent<>(node, order, null);
}
/** A skip event with an explanatory note (e.g., not all parents visited yet). */
public static TraversalEvent skip(T node, String note) {
- return new TraversalEvent<>(Objects.requireNonNull(node), null, Objects.requireNonNull(note));
+ return new TraversalEvent<>(node, null, Objects.requireNonNull(note));
}
public boolean isVisit() {
return order != null;
}
+
public boolean isSkip() {
return order == null;
}
- public T node() {
- return node;
- }
- public Integer order() {
- return order;
- }
- public String note() {
- return note;
- }
@Override
public String toString() {
- return isVisit() ? "VISIT(" + node + ", order=" + order + ")" : "SKIP(" + node + ", " + note + ")";
+ return isVisit()
+ ? "VISIT(" + node + ", order=" + order + ")"
+ : "SKIP(" + node + ", " + note + ")";
}
}
@@ -118,56 +111,56 @@ public static List> dfsRecursiveOrder(Map> succ
return Collections.unmodifiableList(events);
}
- private static void dfs(T u, Map> succ, Map> pred, Set visited, int[] order, List> out) {
+ private static void dfs(T currentNode, Map> successors, Map> predecessors, Set visited, int[] order, List> result) {
- if (!visited.add(u)) {
+ if (!visited.add(currentNode)) {
return; // already visited
}
- out.add(TraversalEvent.visit(u, order[0]++)); // record visit and increment
+ result.add(TraversalEvent.visit(currentNode, order[0]++)); // record visit and increment
- for (T v : succ.getOrDefault(u, List.of())) {
- if (visited.contains(v)) {
+ for (T childNode : successors.getOrDefault(currentNode, List.of())) {
+ if (visited.contains(childNode)) {
continue;
}
- if (allParentsVisited(v, visited, pred)) {
- dfs(v, succ, pred, visited, order, out);
+ if (allParentsVisited(childNode, visited, predecessors)) {
+ dfs(childNode, successors, predecessors, visited, order, result);
} else {
- out.add(TraversalEvent.skip(v, "⛔ Skipping " + v + ": not all parents are visited yet."));
+ result.add(TraversalEvent.skip(childNode, "⛔ Skipping " + childNode + ": not all parents are visited yet."));
// do not mark visited; it may be visited later from another parent
}
}
}
- private static boolean allParentsVisited(T node, Set visited, Map> pred) {
- for (T p : pred.getOrDefault(node, List.of())) {
- if (!visited.contains(p)) {
+ private static boolean allParentsVisited(T node, Set visited, Map> predecessors) {
+ for (T parent : predecessors.getOrDefault(node, List.of())) {
+ if (!visited.contains(parent)) {
return false;
}
}
return true;
}
- private static boolean appearsAnywhere(Map> succ, T node) {
- if (succ.containsKey(node)) {
+ private static boolean appearsAnywhere(Map> successors, T node) {
+ if (successors.containsKey(node)) {
return true;
}
- for (List nbrs : succ.values()) {
- if (nbrs != null && nbrs.contains(node)) {
+ for (List neighbours : successors.values()) {
+ if (neighbours != null && neighbours.contains(node)) {
return true;
}
}
return false;
}
- private static Map> derivePredecessors(Map> succ) {
- Map> pred = new HashMap<>();
+ private static Map> derivePredecessors(Map> successors) {
+ Map> predecessors = new HashMap<>();
// ensure keys exist for all nodes appearing anywhere
- for (Map.Entry> e : succ.entrySet()) {
- pred.computeIfAbsent(e.getKey(), k -> new ArrayList<>());
- for (T v : e.getValue()) {
- pred.computeIfAbsent(v, k -> new ArrayList<>()).add(e.getKey());
+ for (Map.Entry> entry : successors.entrySet()) {
+ predecessors.computeIfAbsent(entry.getKey(), key -> new ArrayList<>());
+ for (T childNode : entry.getValue()) {
+ predecessors.computeIfAbsent(childNode, key -> new ArrayList<>()).add(entry.getKey());
}
}
- return pred;
+ return predecessors;
}
}
diff --git a/src/test/java/com/thealgorithms/graph/GraphTraversalTest.java b/src/test/java/com/thealgorithms/graph/PredecessorConstrainedDfsTest.java
similarity index 67%
rename from src/test/java/com/thealgorithms/graph/GraphTraversalTest.java
rename to src/test/java/com/thealgorithms/graph/PredecessorConstrainedDfsTest.java
index c702f7bd89d4..e2c6d468768f 100644
--- a/src/test/java/com/thealgorithms/graph/GraphTraversalTest.java
+++ b/src/test/java/com/thealgorithms/graph/PredecessorConstrainedDfsTest.java
@@ -3,14 +3,14 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
-import com.thealgorithms.graph.GraphTraversal.TraversalEvent;
+import com.thealgorithms.graph.PredecessorConstrainedDfs.TraversalEvent;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.junit.jupiter.api.Test;
-class GraphTraversalTest {
+class PredecessorConstrainedDfsTest {
// A -> B, A -> C, B -> D, C -> D (classic diamond)
private static Map> diamond() {
@@ -24,7 +24,7 @@ private static Map> diamond() {
@Test
void dfsRecursiveOrderEmitsSkipUntilAllParentsVisited() {
- List> events = GraphTraversal.dfsRecursiveOrder(diamond(), "A");
+ List> events = PredecessorConstrainedDfs.dfsRecursiveOrder(diamond(), "A");
// Expect visits in order and a skip for first time we meet D (via B) before C is visited.
var visits = events.stream().filter(TraversalEvent::isVisit).toList();
@@ -49,39 +49,39 @@ void dfsRecursiveOrderEmitsSkipUntilAllParentsVisited() {
@Test
void returnsEmptyWhenStartNotInGraph() {
- Map> g = Map.of(1, List.of(2), 2, List.of(1));
- assertThat(GraphTraversal.dfsRecursiveOrder(g, 99)).isEmpty();
+ Map> graph = Map.of(1, List.of(2), 2, List.of(1));
+ assertThat(PredecessorConstrainedDfs.dfsRecursiveOrder(graph, 99)).isEmpty();
}
@Test
void nullSuccessorsThrows() {
- assertThrows(IllegalArgumentException.class, () -> GraphTraversal.dfsRecursiveOrder(null, "A"));
+ assertThrows(IllegalArgumentException.class, () -> PredecessorConstrainedDfs.dfsRecursiveOrder(null, "A"));
}
@Test
void worksWithExplicitPredecessors() {
- Map> succ = new HashMap<>();
- succ.put(10, List.of(20));
- succ.put(20, List.of(30));
- succ.put(30, List.of());
+ Map> successors = new HashMap<>();
+ successors.put(10, List.of(20));
+ successors.put(20, List.of(30));
+ successors.put(30, List.of());
- Map> pred = new HashMap<>();
- pred.put(10, List.of());
- pred.put(20, List.of(10));
- pred.put(30, List.of(20));
+ Map> predecessors = new HashMap<>();
+ predecessors.put(10, List.of());
+ predecessors.put(20, List.of(10));
+ predecessors.put(30, List.of(20));
- var events = GraphTraversal.dfsRecursiveOrder(succ, pred, 10);
+ var events = PredecessorConstrainedDfs.dfsRecursiveOrder(successors, predecessors, 10);
var visitNodes = events.stream().filter(TraversalEvent::isVisit).map(TraversalEvent::node).toList();
assertThat(visitNodes).containsExactly(10, 20, 30);
}
@Test
void cycleProducesSkipsButNoInfiniteRecursion() {
- Map> succ = new LinkedHashMap<>();
- succ.put("X", List.of("Y"));
- succ.put("Y", List.of("X")); // 2-cycle
+ Map> successors = new LinkedHashMap<>();
+ successors.put("X", List.of("Y"));
+ successors.put("Y", List.of("X")); // 2-cycle
- var events = GraphTraversal.dfsRecursiveOrder(succ, "X");
+ var events = PredecessorConstrainedDfs.dfsRecursiveOrder(successors, "X");
// Only X is visited; encountering Y from X causes skip because Y's parent X is visited,
// but when recursing to Y we'd hit back to X (already visited) and stop; no infinite loop.
assertThat(events.stream().anyMatch(TraversalEvent::isVisit)).isTrue();
From f0fe5f76b2a56bad17c2ba51f8bb59b755372af1 Mon Sep 17 00:00:00 2001
From: StathisVeinoglou
Date: Fri, 15 Aug 2025 00:36:27 +0300
Subject: [PATCH 8/8] updated for clang format
---
.../graph/PredecessorConstrainedDfs.java | 11 ++++-------
1 file changed, 4 insertions(+), 7 deletions(-)
diff --git a/src/main/java/com/thealgorithms/graph/PredecessorConstrainedDfs.java b/src/main/java/com/thealgorithms/graph/PredecessorConstrainedDfs.java
index 0fa01295c00e..2cf4ed23c44f 100644
--- a/src/main/java/com/thealgorithms/graph/PredecessorConstrainedDfs.java
+++ b/src/main/java/com/thealgorithms/graph/PredecessorConstrainedDfs.java
@@ -31,10 +31,9 @@ private PredecessorConstrainedDfs() {
}
/** An event emitted by the traversal: either a VISIT with an order, or a SKIP with a note. */
- public record TraversalEvent(
- T node,
- Integer order, // non-null for visit, null for skip
- String note // non-null for skip, null for visit
+ public record TraversalEvent(T node,
+ Integer order, // non-null for visit, null for skip
+ String note // non-null for skip, null for visit
) {
public TraversalEvent {
Objects.requireNonNull(node);
@@ -61,9 +60,7 @@ public boolean isSkip() {
@Override
public String toString() {
- return isVisit()
- ? "VISIT(" + node + ", order=" + order + ")"
- : "SKIP(" + node + ", " + note + ")";
+ return isVisit() ? "VISIT(" + node + ", order=" + order + ")" : "SKIP(" + node + ", " + note + ")";
}
}