feat: add Kahn's algorithm for topological sorting - #403
Open
Vir-007 wants to merge 1 commit into
Open
Conversation
Graaf only implemented DFS-based topological sorting. This adds Kahn's algorithm, the BFS/in-degree based alternative, alongside it. The implementation emits vertices whose in-degree has dropped to zero, relaxing outgoing edges as it goes. Cycles fall out of the algorithm: if not every vertex could be emitted, the remaining ones lie on or downstream of a cycle, and std::invalid_argument is thrown. This mirrors how bellman_ford_shortest_paths reports a negative cycle. Self-loops are covered, since a self-loop contributes one to its own vertex's in-degree. In-degrees are counted in a single sweep over the adjacency lists rather than via properties::vertex_indegree, which scans the whole graph on each call and would make the sort O(|V| * (|V| + |E|)) instead of O(|V| + |E|). Docs cover complexity and compare the two approaches side by side. The topological-sort section gains a README so both pages nest under it; the existing page keeps its URL. Tests cover DAGs (chain, rhombus, multi-path), cyclic graphs (cycle, self-loop, cycle in one component of several), and disconnected graphs (two components, isolated vertices), plus empty and single-vertex graphs.
Contributor
|
Hi there! Thank you for creating your first pull-request on the Graaf library :) |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #403 +/- ##
==========================================
- Coverage 99.71% 99.56% -0.16%
==========================================
Files 60 62 +2
Lines 2823 2980 +157
Branches 151 167 +16
==========================================
+ Hits 2815 2967 +152
- Misses 8 13 +5 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #334
What
Adds
kahn_topological_sort, the BFS/in-degree based topological sort, alongside the existing DFS-based one underinclude/graaflib/algorithm/topological_sorting/. Follows the same header/.tppsplit as its sibling.Why
Kahn's algorithm is the standard alternative to the DFS approach and has properties the DFS version doesn't: it detects cycles as a side effect rather than needing a separate pass, it's iterative so it has no recursion-depth limit on deep graphs, and its queue contents form dependency "levels" that suit layered/parallel scheduling.
How
Vertices whose in-degree has dropped to zero are emitted from a queue, relaxing their outgoing edges as they go.
Cycle reporting — if not every vertex could be emitted, the remaining vertices lie on or downstream of a cycle and
std::invalid_argumentis thrown, mirroring howbellman_ford_shortest_pathsreports a negative cycle (as the issue suggested). Self-loops are covered by the same check, since a self-loop contributes one to its own vertex's in-degree.Worth flagging for review: this means the two topological sorts in the same directory report cycles differently — DFS returns
std::nullopt, this one throws. I followed the issue's suggestion, but happy to switch tostd::optionalfor symmetry with the sibling function if you'd prefer that.In-degree computation — the issue suggested reusing
properties::vertex_indegree"where convenient". It turned out not to be: that helper scans every vertex's neighbor set on each call, so calling it per vertex would make the sortO(|V| * (|V| + |E|))and cost Kahn's algorithm its main selling point. In-degrees are instead counted in a singleO(|V| + |E|)sweep over the adjacency lists. There's a comment in the code explaining this.Docs
docs/src/algorithms/topological-sort/kahn.mdcovers the algorithm, complexity (O(|V| + |E|)time,O(|V|)memory), and a side-by-side comparison with the DFS-based version.The section previously held a single flat page, so I added a
README.mdand nested both pages under it inSUMMARY.md, matching howcycle-detection,shortest-pathetc. are laid out. The existingtopological-sort.mdkeeps its path, so published links to it still work. Also updated the rootREADME.mdalgorithm list andllms.txt.Note: the issue referenced
docs/docs/algorithms/topological-sort/, but docs now live underdocs/src/, so I used the current path.How tested
test/graaflib/algorithm/topological_sorting/kahn_topological_sorting_test.cpp, mirroring the GoogleTest style ofdfs_topological_sorting_test.cpp:clang-formatclean against the repo's.clang-format.One note on local verification:
cmakeisn't installed on my machine, so rather thancmake -B build && ctestI compiled the suite directly withg++ -std=c++20against a system GoogleTest. The systemfmtis 12.x while the build pins 9.1.0, and fmt 12 splitfmt::formatout offmt/core.h, so the existing fmt-using tests needed-include fmt/format.hto compile. That's purely a local toolchain artifact — it doesn't touch this change, and nothing in this PR uses fmt. CI will exercise the real CMake path.