order.go (2198B)
1 // Copyright 2026 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package graph 6 7 import "slices" 8 9 // Postorder returns the sequence of nodes in the spanning DAG of g, in 10 // postorder. 11 // 12 // For rootless subgraphs, it breaks cycles by starting at the lowest numbered 13 // node. 14 // 15 // This algorithm runs in O(V + E) time and O(V + E) space. 16 func Postorder[NodeID comparable](g Graph[NodeID]) []NodeID { 17 cg, nodeMap := Compact(g) 18 19 numNodes := cg.NumNodes() 20 if numNodes == 0 { 21 return nil 22 } 23 24 result := make([]NodeID, 0, numNodes) 25 visited := newBitset(numNodes) 26 onStack := newBitset(numNodes) 27 28 // visit performs a Depth-First Search. 29 var visit func(u int) 30 visit = func(u int) { 31 if !visited.add(u) { 32 return 33 } 34 onStack.add(u) 35 36 for v := range cg.Out(u) { 37 if onStack.contains(v) { 38 // Cycle detected (back-edge). 39 // To resolve, we simply skip processing this edge further in the 40 // current recursion, effectively "breaking" the cycle at this point. 41 continue 42 } 43 visit(v) 44 } 45 46 onStack.remove(u) 47 // Post-order: add to result after all descendants are processed. 48 result = append(result, nodeMap.Value(u)) 49 } 50 51 // Visit every node in ascending order to ensure stability. 52 for u := range numNodes { 53 visit(u) 54 } 55 56 return result 57 } 58 59 // ReversePostorder returns the nodes of the graph in reverse post-order. 60 // 61 // If g is a directed acyclic graph (DAG), the result is a topological sort of 62 // g. 63 // 64 // See [Postorder] for how this handles back-edges and cycles. 65 // 66 // This algorithm runs in O(V + E) time and O(V + E) space. 67 func ReversePostorder[NodeID comparable](g Graph[NodeID]) []NodeID { 68 result := Postorder(g) 69 slices.Reverse(result) 70 return result 71 } 72 73 // bitset is a simple fixed-size bitset used to reduce memory overhead. 74 type bitset []uint64 75 76 func newBitset(n int) bitset { 77 return make(bitset, (n+63)/64) 78 } 79 80 func (b bitset) add(u int) bool { 81 if b.contains(u) { 82 return false 83 } 84 b[u/64] |= 1 << (u % 64) 85 return true 86 } 87 88 func (b bitset) remove(u int) { 89 b[u/64] &= ^(1 << (u % 64)) 90 } 91 92 func (b bitset) contains(u int) bool { 93 return b[u/64]&(1<<(u%64)) != 0 94 }