From 811971ca13726696f9e928fb504fa20cdcb60d82 Mon Sep 17 00:00:00 2001 From: Ali Rostami Date: Mon, 16 Mar 2026 12:17:55 +0100 Subject: [PATCH] Fix DFS root-revisit bug in isConnected; add Prufer sequence report 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 --- src/graphtea/extensions/AlgorithmUtils.java | 1 + .../reports/basicreports/PruferSequence.java | 100 ++++++++++++++++++ 2 files changed, 101 insertions(+) create mode 100644 src/graphtea/extensions/reports/basicreports/PruferSequence.java diff --git a/src/graphtea/extensions/AlgorithmUtils.java b/src/graphtea/extensions/AlgorithmUtils.java index ffb0605a..614add81 100755 --- a/src/graphtea/extensions/AlgorithmUtils.java +++ b/src/graphtea/extensions/AlgorithmUtils.java @@ -53,6 +53,7 @@ public static boolean isConnected(GraphModel g) { for (int i = 0; i < g.getVerticesCount(); i++) { parent[i] = -1; } + parent[0] = 0; // mark root as visited so it is not re-entered during recursion dfs(g, 0, vs, parent); return vs.stream().distinct().count() == g.getVerticesCount(); } diff --git a/src/graphtea/extensions/reports/basicreports/PruferSequence.java b/src/graphtea/extensions/reports/basicreports/PruferSequence.java new file mode 100644 index 00000000..453211bb --- /dev/null +++ b/src/graphtea/extensions/reports/basicreports/PruferSequence.java @@ -0,0 +1,100 @@ +// GraphTea Project: http://github.com/graphtheorysoftware/GraphTea +// Copyright (C) 2012 Graph Theory Software Foundation: http://GraphTheorySoftware.com +// Copyright (C) 2008 Mathematical Science Department of Sharif University of Technology +// Distributed under the terms of the GNU General Public License (GPL): http://www.gnu.org/licenses/ + +package graphtea.extensions.reports.basicreports; + +import graphtea.graph.graph.GraphModel; +import graphtea.graph.graph.Vertex; +import graphtea.platform.lang.CommandAttitude; +import graphtea.plugins.reports.extension.GraphReportExtension; + + +/** + * Computes the Prüfer sequence of a labeled tree. + * + * The Prüfer sequence is a unique sequence of n-2 vertex labels that encodes + * a labeled tree on n vertices. It is produced by repeatedly removing the leaf + * with the smallest label and appending its neighbor's label to the sequence. + * + * The graph must be an unrooted labeled tree (connected, undirected, n-1 edges). + */ +@CommandAttitude(name = "prufer_sequence", abbreviation = "_ps") +public class PruferSequence implements GraphReportExtension { + + 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); + } + + 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; + } + } + if (neighbor == -1) { + return "Graph is not a tree (disconnected)"; + } + + sequence[step] = neighbor; + removed[leaf] = true; + degree[leaf] = 0; + degree[neighbor]--; + } + + // 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(", "); + } + } + return sb.toString(); + } + + public String getName() { + return "Prufer Sequence"; + } + + public String getDescription() { + return "Prufer sequence of a labeled tree"; + } + + @Override + public String getCategory() { + return "General"; + } +}