From 6b1b80bae6983473690f058fd60d8249b828b7d1 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 7 Feb 2026 14:30:47 +0000 Subject: [PATCH] Fix two bugs in k_shortest_paths: off-by-one prefix comparison and PredDAG fill order Bug 1 (off-by-one in Yen's root path comparison): In Yen's k-shortest-paths algorithm, when determining which spur edges to exclude, the code compared only j prefix nodes instead of j+1. This meant the spur node itself was not included in the comparison, causing edges from paths with different root paths to be incorrectly excluded. This resulted in valid paths being missed during enumeration. Bug 2 (PredDAG CSR fill order): When converting discovered paths to PredDAG output, entries were filled sequentially using a linear index. However, the CSR-like parent_offsets structure requires entries to be placed at the correct offset for each node. For paths visiting nodes out of numerical order (e.g., [0,2,1,3]), this produced malformed PredDAGs with parent/edge entries assigned to wrong nodes. Fixed by using node-specific offsets instead of linear index. Both bugs affected the same function and were interconnected: Bug 1 hid Bug 2 because the missing paths were exactly those visiting nodes out of numerical order. https://claude.ai/code/session_011Wz1nQQWEnVTaLwfgz9LDA --- src/k_shortest_paths.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/k_shortest_paths.cpp b/src/k_shortest_paths.cpp index 8227350..fcb96d1 100644 --- a/src/k_shortest_paths.cpp +++ b/src/k_shortest_paths.cpp @@ -203,9 +203,10 @@ std::vector, PredDAG>> k_shortest_paths( for (std::size_t v = 1; v < dag.parent_offsets.size(); ++v) dag.parent_offsets[v] += dag.parent_offsets[v-1]; dag.parents.resize(static_cast(dag.parent_offsets.back())); dag.via_edges.resize(static_cast(dag.parent_offsets.back())); - std::size_t idx = 0; for (std::size_t i = 1; i < P.nodes.size(); ++i) { - dag.parents[idx] = P.nodes[i-1]; dag.via_edges[idx] = P.edges[i-1]; ++idx; + auto v = P.nodes[i]; + auto base = static_cast(dag.parent_offsets[static_cast(v)]); + dag.parents[base] = P.nodes[i-1]; dag.via_edges[base] = P.edges[i-1]; } } items.emplace_back(std::move(dist), std::move(dag)); @@ -249,7 +250,7 @@ std::vector, PredDAG>> k_shortest_paths( std::vector edge_mask_local; edge_mask_local.assign(static_cast(g.num_edges()), static_cast(1)); for (auto const& P : paths) { - if (P.nodes.size() > j && std::equal(P.nodes.begin(), P.nodes.begin() + j, last.nodes.begin())) { + if (P.nodes.size() > j && std::equal(P.nodes.begin(), P.nodes.begin() + static_cast(j + 1), last.nodes.begin())) { // Exclude edge at position j for this path if (P.edges.size() > j) { edge_mask_local[static_cast(P.edges[j])] = 0; @@ -337,9 +338,10 @@ std::vector, PredDAG>> k_shortest_paths( for (std::size_t v = 1; v < dag.parent_offsets.size(); ++v) dag.parent_offsets[v] += dag.parent_offsets[v-1]; dag.parents.resize(static_cast(dag.parent_offsets.back())); dag.via_edges.resize(static_cast(dag.parent_offsets.back())); - std::size_t idx = 0; for (std::size_t i = 1; i < P.nodes.size(); ++i) { - dag.parents[idx] = P.nodes[i-1]; dag.via_edges[idx] = P.edges[i-1]; ++idx; + auto v = P.nodes[i]; + auto base = static_cast(dag.parent_offsets[static_cast(v)]); + dag.parents[base] = P.nodes[i-1]; dag.via_edges[base] = P.edges[i-1]; } } items.emplace_back(std::move(dist), std::move(dag));