| // 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 | |
| } |