blob: 795a434d6cb694ff4fc3dd493b7411e0c8e20d3c [file]
// Copyright 2026 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package graph
import "slices"
// Postorder returns the sequence of nodes in the spanning DAG of g, in
// postorder.
//
// For rootless subgraphs, it breaks cycles by starting at the lowest numbered
// node.
//
// This algorithm runs in O(V + E) time and O(V + E) space.
func Postorder[NodeID comparable](g Graph[NodeID]) []NodeID {
cg, nodeMap := Compact(g)
numNodes := cg.NumNodes()
if numNodes == 0 {
return nil
}
result := make([]NodeID, 0, numNodes)
visited := newBitset(numNodes)
onStack := newBitset(numNodes)
// visit performs a Depth-First Search.
var visit func(u int)
visit = func(u int) {
if !visited.add(u) {
return
}
onStack.add(u)
for v := range cg.Out(u) {
if onStack.contains(v) {
// Cycle detected (back-edge).
// To resolve, we simply skip processing this edge further in the
// current recursion, effectively "breaking" the cycle at this point.
continue
}
visit(v)
}
onStack.remove(u)
// Post-order: add to result after all descendants are processed.
result = append(result, nodeMap.Value(u))
}
// Visit every node in ascending order to ensure stability.
for u := range numNodes {
visit(u)
}
return result
}
// ReversePostorder returns the nodes of the graph in reverse post-order.
//
// If g is a directed acyclic graph (DAG), the result is a topological sort of
// g.
//
// See [Postorder] for how this handles back-edges and cycles.
//
// This algorithm runs in O(V + E) time and O(V + E) space.
func ReversePostorder[NodeID comparable](g Graph[NodeID]) []NodeID {
result := Postorder(g)
slices.Reverse(result)
return result
}
// bitset is a simple fixed-size bitset used to reduce memory overhead.
type bitset []uint64
func newBitset(n int) bitset {
return make(bitset, (n+63)/64)
}
func (b bitset) add(u int) bool {
if b.contains(u) {
return false
}
b[u/64] |= 1 << (u % 64)
return true
}
func (b bitset) remove(u int) {
b[u/64] &= ^(1 << (u % 64))
}
func (b bitset) contains(u int) bool {
return b[u/64]&(1<<(u%64)) != 0
}