Skip to content

Fix DFS root-revisit bug in isConnected; add Prufer sequence report#81

Merged
rostam merged 1 commit intomasterfrom
fix/dfs-bug-and-prufer-sequence
Mar 16, 2026
Merged

Fix DFS root-revisit bug in isConnected; add Prufer sequence report#81
rostam merged 1 commit intomasterfrom
fix/dfs-bug-and-prufer-sequence

Conversation

@rostam
Copy link
Owner

@rostam rostam commented Mar 16, 2026

Issue #41: AlgorithmUtils.isConnected never marked the root node as visited before calling dfs, so any neighbor of node 0 would see parent[0]==-1 and re-enter node 0, causing redundant traversal and potential StackOverflowError on large dense graphs. Fix: set parent[0]=0 before the dfs call.

Issue #38: Add PruferSequence report extension under basicreports. Repeatedly removes the leaf with the smallest vertex id and appends its neighbor's id to build the n-2 length Prufer sequence of a labeled tree. Returns an error string if the graph is not a tree.

Issue #41: AlgorithmUtils.isConnected never marked the root node as
visited before calling dfs, so any neighbor of node 0 would see
parent[0]==-1 and re-enter node 0, causing redundant traversal and
potential StackOverflowError on large dense graphs. Fix: set parent[0]=0
before the dfs call.

Issue #38: Add PruferSequence report extension under basicreports.
Repeatedly removes the leaf with the smallest vertex id and appends its
neighbor's id to build the n-2 length Prufer sequence of a labeled tree.
Returns an error string if the graph is not a tree.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@qodo-code-review
Copy link

Review Summary by Qodo

Fix DFS bug and add Prufer sequence report extension

🐞 Bug fix ✨ Enhancement

Grey Divider

Walkthroughs

Description
• Fix DFS root-revisit bug in isConnected by marking root as visited
• Add PruferSequence report extension for computing Prüfer sequences
• Prevent StackOverflowError on large dense graphs during connectivity check
• Validate tree structure and handle edge cases in sequence computation
Diagram
flowchart LR
  A["AlgorithmUtils.isConnected"] -->|"set parent[0]=0"| B["Mark root as visited"]
  B -->|"prevents re-entry"| C["Avoid StackOverflow"]
  D["PruferSequence extension"] -->|"validates tree"| E["Compute n-2 sequence"]
  E -->|"remove leaves"| F["Build Prüfer sequence"]
Loading

Grey Divider

File Changes

1. src/graphtea/extensions/AlgorithmUtils.java 🐞 Bug fix +1/-0

Mark DFS root as visited before traversal

• Set parent[0] = 0 before DFS call to mark root as visited
• Prevents root node from being re-entered during recursion
• Fixes potential StackOverflowError on large dense graphs

src/graphtea/extensions/AlgorithmUtils.java


2. src/graphtea/extensions/reports/basicreports/PruferSequence.java ✨ Enhancement +100/-0

Add Prufer sequence computation report extension

• New GraphReportExtension implementation for computing Prüfer sequences
• Validates input graph is a tree (n vertices, n-1 edges, connected)
• Iteratively removes smallest-id leaf and appends neighbor to sequence
• Returns error messages for invalid inputs or disconnected graphs

src/graphtea/extensions/reports/basicreports/PruferSequence.java


Grey Divider

Qodo Logo

@qodo-code-review
Copy link

qodo-code-review bot commented Mar 16, 2026

Code Review by Qodo

🐞 Bugs (3) 📘 Rule violations (1) 📎 Requirement gaps (0)

Grey Divider


Action required

1. Extra blank line in PruferSequence 📘 Rule violation ⛯ Reliability
Description
PruferSequence.java contains multiple consecutive blank lines after the import block, which
violates the repository Checkstyle configuration and can cause ant checkstyle-strict to fail. This
breaks the requirement that newly added Java files comply with Checkstyle 10 rules.
Code

src/graphtea/extensions/reports/basicreports/PruferSequence.java[R12-13]

+
+
Evidence
The Checkstyle configuration disallows multiple blank lines in a row
(allowMultipleEmptyLines=false). The new PruferSequence.java file has two consecutive empty
lines after the imports, which would trigger EmptyLineSeparator in Checkstyle.

CLAUDE.md
config/checkstyle.xml[112-121]
src/graphtea/extensions/reports/basicreports/PruferSequence.java[8-15]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`PruferSequence.java` has multiple consecutive empty lines after the import section, which violates Checkstyle (`EmptyLineSeparator` with `allowMultipleEmptyLines=false`) and may fail `ant checkstyle-strict`.

## Issue Context
This is a newly added Java source file and must comply with the repository Checkstyle configuration.

## Fix Focus Areas
- src/graphtea/extensions/reports/basicreports/PruferSequence.java[8-15]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Subgraph ID crash 🐞 Bug ✓ Correctness
Description
PruferSequence indexes arrays by Vertex.getId(), but in GraphModel subgraphs (subgraphIndex!=0) the
local vertex index is stored separately as a subgraphId, so getId() can be outside 0..n-1 and cause
ArrayIndexOutOfBoundsException or incorrect removals.
Code

src/graphtea/extensions/reports/basicreports/PruferSequence.java[R35-64]

+        // degree[i] mirrors the current degree of vertex with id=i
+        int[] degree = new int[n];
+        Vertex[] vertices = g.getVertexArray();
+        for (Vertex v : vertices) {
+            degree[v.getId()] = g.getDegree(v);
+        }
+
+        int[] sequence = new int[n - 2];
+        boolean[] removed = new boolean[n];
+
+        for (int step = 0; step < n - 2; step++) {
+            // find the leaf with the smallest id
+            int leaf = -1;
+            for (int i = 0; i < n; i++) {
+                if (!removed[i] && degree[i] == 1) {
+                    leaf = i;
+                    break;
+                }
+            }
+            if (leaf == -1) {
+                return "Graph is not a tree (cycle detected)";
+            }
+
+            // find the unique neighbor of this leaf
+            Vertex leafVertex = g.getVertex(leaf);
+            int neighbor = -1;
+            for (Vertex nb : g.getNeighbors(leafVertex)) {
+                if (!removed[nb.getId()]) {
+                    neighbor = nb.getId();
+                    break;
Evidence
Induced subgraphs reuse the original Vertex objects and assign *subgraph IDs* (not BaseVertex.id)
for local indexing; GraphModel/ListGraph APIs (getVertexArray/getEdgeArray/getId wrapper) use these
subgraph IDs, but PruferSequence uses Vertex.getId() as if it were the local index, which can exceed
n-1 and break array indexing.

src/graphtea/extensions/reports/basicreports/PruferSequence.java[35-64]
src/graphtea/extensions/algorithms/subgraphs/InducedSubgraphs.java[20-28]
src/graphtea/library/ListGraph.java[141-148]
src/graphtea/library/BaseGraph.java[402-408]
src/graphtea/graph/graph/GraphModel.java[529-536]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`PruferSequence.calculate` assumes `Vertex.getId()` is a 0..n-1 local index and uses it to index `degree[]`/`removed[]`. In GraphModel subgraphs, local indices are stored as `subgraphId` and `Vertex.getId()` may refer to the supergraph id, leading to `ArrayIndexOutOfBoundsException` and wrong behavior.

### Issue Context
Subgraphs are created via `registerSubgraph()` + `setSubGraphIndex()` and then inserting existing `Vertex` objects. In this mode, `ListGraph.setId` writes `v.setSubgraphId(...)` instead of mutating `v.id`.

### Fix Focus Areas
- src/graphtea/extensions/reports/basicreports/PruferSequence.java[35-75]
- src/graphtea/extensions/algorithms/subgraphs/InducedSubgraphs.java[20-29]
- src/graphtea/library/ListGraph.java[141-148]
- src/graphtea/library/BaseGraph.java[402-408]
- src/graphtea/graph/graph/GraphModel.java[529-536]

### Implementation sketch
- Use `Vertex[] verts = g.getVertexArray();`
- Build `IdentityHashMap&lt;Vertex,Integer&gt; idx` mapping each `verts[i]` to `i`.
- Maintain `degree[i] = g.getDegree(verts[i])` and `removed[i]`.
- When iterating neighbors, translate `nb` to `nbIdx = idx.get(nb)` and use `removed[nbIdx]`/`degree[nbIdx]--`.
- Store output label separately (e.g., `nb.getId()` or `verts[nbIdx].getId()`) rather than array index.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Directed tree accepted 🐞 Bug ✓ Correctness
Description
PruferSequence claims the input must be an undirected tree but never rejects directed graphs; on
directed graphs it mixes total degree (in+out) with neighbors derived from outgoing edges only,
producing incorrect sequences or misleading errors.
Code

src/graphtea/extensions/reports/basicreports/PruferSequence.java[R21-40]

+ * The graph must be an unrooted labeled tree (connected, undirected, n-1 edges).
+ */
+@CommandAttitude(name = "prufer_sequence", abbreviation = "_ps")
+public class PruferSequence implements GraphReportExtension<String> {
+
+    public String calculate(GraphModel g) {
+        int n = g.getVerticesCount();
+        if (n < 2) {
+            return "Graph must have at least 2 vertices";
+        }
+        if (g.getEdgesCount() != n - 1) {
+            return "Graph is not a tree (edge count must equal n-1)";
+        }
+
+        // degree[i] mirrors the current degree of vertex with id=i
+        int[] degree = new int[n];
+        Vertex[] vertices = g.getVertexArray();
+        for (Vertex v : vertices) {
+            degree[v.getId()] = g.getDegree(v);
+        }
Evidence
The report documents an undirected tree requirement, but calculate() only checks edge count. In
directed graphs, BaseGraph.getDegree uses in+out while BaseGraph.getNeighbors relies on
lightEdgeIterator(vertex), which in ListGraph iterates only outgoing edges when directed, so leaf
detection and neighbor selection are inconsistent.

src/graphtea/extensions/reports/basicreports/PruferSequence.java[21-33]
src/graphtea/extensions/reports/basicreports/PruferSequence.java[35-40]
src/graphtea/library/BaseGraph.java[433-435]
src/graphtea/library/BaseGraph.java[442-456]
src/graphtea/library/ListGraph.java[375-399]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`PruferSequence.calculate` is documented for undirected trees but will run on directed graphs. In directed graphs, degree and neighbor iteration semantics do not align with the Prüfer algorithm, so results are wrong.

### Issue Context
- `BaseGraph.getDegree()` counts in+out for directed graphs.
- `getNeighbors()` depends on `lightEdgeIterator(vertex)` which, in `ListGraph`, yields outgoing edges only when `directed==true`.

### Fix Focus Areas
- src/graphtea/extensions/reports/basicreports/PruferSequence.java[26-40]
- src/graphtea/library/BaseGraph.java[433-456]
- src/graphtea/library/ListGraph.java[375-399]

### Suggested change
- Early in `calculate(GraphModel g)` add:
 - `if (g.isDirected()) return &quot;Graph is not a tree (graph must be undirected)&quot;;`
 - (Optional but aligns with the docstring) `if (!AlgorithmUtils.isConnected(g)) return &quot;Graph is not a tree (disconnected)&quot;;`

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Advisory comments

4. 1-based output mismatch 🐞 Bug ✧ Quality
Description
The output formatter comment says it emits 1-based labels, but the code appends raw internal IDs
(0-based), so the displayed Prüfer sequence contradicts its own formatting note.
Code

src/graphtea/extensions/reports/basicreports/PruferSequence.java[R77-84]

+        // format as comma-separated list of 1-based labels for readability
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < sequence.length; i++) {
+            sb.append(sequence[i]);
+            if (i < sequence.length - 1) {
+                sb.append(", ");
+            }
+        }
Evidence
GraphTea assigns internal vertex ids starting from 0, and the report appends those values directly;
the comment stating 1-based output is therefore incorrect unless the code adds +1.

src/graphtea/extensions/reports/basicreports/PruferSequence.java[77-85]
src/graphtea/library/ListGraph.java[590-597]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The comment claims 1-based output, but the implementation prints raw internal ids.

### Issue Context
Graph vertex ids are assigned as `vertices.size() - 1` (0-based).

### Fix Focus Areas
- src/graphtea/extensions/reports/basicreports/PruferSequence.java[77-85]
- src/graphtea/library/ListGraph.java[590-597]

### Suggested change options
- Option A: Fix the comment to say `0-based vertex ids`.
- Option B: Print `sequence[i] + 1` (and consider whether errors/other reports expect 0-based ids).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

@rostam rostam merged commit 064e692 into master Mar 16, 2026
3 checks passed
Comment on lines +12 to +13


Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. Extra blank line in prufersequence 📘 Rule violation ⛯ Reliability

PruferSequence.java contains multiple consecutive blank lines after the import block, which
violates the repository Checkstyle configuration and can cause ant checkstyle-strict to fail. This
breaks the requirement that newly added Java files comply with Checkstyle 10 rules.
Agent Prompt
## Issue description
`PruferSequence.java` has multiple consecutive empty lines after the import section, which violates Checkstyle (`EmptyLineSeparator` with `allowMultipleEmptyLines=false`) and may fail `ant checkstyle-strict`.

## Issue Context
This is a newly added Java source file and must comply with the repository Checkstyle configuration.

## Fix Focus Areas
- src/graphtea/extensions/reports/basicreports/PruferSequence.java[8-15]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +35 to +64
// degree[i] mirrors the current degree of vertex with id=i
int[] degree = new int[n];
Vertex[] vertices = g.getVertexArray();
for (Vertex v : vertices) {
degree[v.getId()] = g.getDegree(v);
}

int[] sequence = new int[n - 2];
boolean[] removed = new boolean[n];

for (int step = 0; step < n - 2; step++) {
// find the leaf with the smallest id
int leaf = -1;
for (int i = 0; i < n; i++) {
if (!removed[i] && degree[i] == 1) {
leaf = i;
break;
}
}
if (leaf == -1) {
return "Graph is not a tree (cycle detected)";
}

// find the unique neighbor of this leaf
Vertex leafVertex = g.getVertex(leaf);
int neighbor = -1;
for (Vertex nb : g.getNeighbors(leafVertex)) {
if (!removed[nb.getId()]) {
neighbor = nb.getId();
break;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

2. Subgraph id crash 🐞 Bug ✓ Correctness

PruferSequence indexes arrays by Vertex.getId(), but in GraphModel subgraphs (subgraphIndex!=0) the
local vertex index is stored separately as a subgraphId, so getId() can be outside 0..n-1 and cause
ArrayIndexOutOfBoundsException or incorrect removals.
Agent Prompt
### Issue description
`PruferSequence.calculate` assumes `Vertex.getId()` is a 0..n-1 local index and uses it to index `degree[]`/`removed[]`. In GraphModel subgraphs, local indices are stored as `subgraphId` and `Vertex.getId()` may refer to the supergraph id, leading to `ArrayIndexOutOfBoundsException` and wrong behavior.

### Issue Context
Subgraphs are created via `registerSubgraph()` + `setSubGraphIndex()` and then inserting existing `Vertex` objects. In this mode, `ListGraph.setId` writes `v.setSubgraphId(...)` instead of mutating `v.id`.

### Fix Focus Areas
- src/graphtea/extensions/reports/basicreports/PruferSequence.java[35-75]
- src/graphtea/extensions/algorithms/subgraphs/InducedSubgraphs.java[20-29]
- src/graphtea/library/ListGraph.java[141-148]
- src/graphtea/library/BaseGraph.java[402-408]
- src/graphtea/graph/graph/GraphModel.java[529-536]

### Implementation sketch
- Use `Vertex[] verts = g.getVertexArray();`
- Build `IdentityHashMap<Vertex,Integer> idx` mapping each `verts[i]` to `i`.
- Maintain `degree[i] = g.getDegree(verts[i])` and `removed[i]`.
- When iterating neighbors, translate `nb` to `nbIdx = idx.get(nb)` and use `removed[nbIdx]`/`degree[nbIdx]--`.
- Store output label separately (e.g., `nb.getId()` or `verts[nbIdx].getId()`) rather than array index.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +21 to +40
* The graph must be an unrooted labeled tree (connected, undirected, n-1 edges).
*/
@CommandAttitude(name = "prufer_sequence", abbreviation = "_ps")
public class PruferSequence implements GraphReportExtension<String> {

public String calculate(GraphModel g) {
int n = g.getVerticesCount();
if (n < 2) {
return "Graph must have at least 2 vertices";
}
if (g.getEdgesCount() != n - 1) {
return "Graph is not a tree (edge count must equal n-1)";
}

// degree[i] mirrors the current degree of vertex with id=i
int[] degree = new int[n];
Vertex[] vertices = g.getVertexArray();
for (Vertex v : vertices) {
degree[v.getId()] = g.getDegree(v);
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

3. Directed tree accepted 🐞 Bug ✓ Correctness

PruferSequence claims the input must be an undirected tree but never rejects directed graphs; on
directed graphs it mixes total degree (in+out) with neighbors derived from outgoing edges only,
producing incorrect sequences or misleading errors.
Agent Prompt
### Issue description
`PruferSequence.calculate` is documented for undirected trees but will run on directed graphs. In directed graphs, degree and neighbor iteration semantics do not align with the Prüfer algorithm, so results are wrong.

### Issue Context
- `BaseGraph.getDegree()` counts in+out for directed graphs.
- `getNeighbors()` depends on `lightEdgeIterator(vertex)` which, in `ListGraph`, yields outgoing edges only when `directed==true`.

### Fix Focus Areas
- src/graphtea/extensions/reports/basicreports/PruferSequence.java[26-40]
- src/graphtea/library/BaseGraph.java[433-456]
- src/graphtea/library/ListGraph.java[375-399]

### Suggested change
- Early in `calculate(GraphModel g)` add:
  - `if (g.isDirected()) return "Graph is not a tree (graph must be undirected)";`
  - (Optional but aligns with the docstring) `if (!AlgorithmUtils.isConnected(g)) return "Graph is not a tree (disconnected)";`

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant