-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgraph.go
155 lines (128 loc) · 3.97 KB
/
graph.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
// © 2025 Rolf van de Krol <[email protected]>
// Package graph provides a directed graph data structure and topological sort
// algorithm (O(n) for n = [number of nodes] + [number of edges], using Kahn's
// algorithm).
//
// g := graph.New[string]()
// g.Add("b", []string{"c"})
// g.Add("c", []string{"a"})
//
// s, err := g.Sort() // []string{"b", "c", "a"}
package graph
import "fmt"
// Graph represents a directed graph.
type Graph[Key comparable] struct {
nodes map[Key]Edges[Key]
}
// Edges represents the edges of a node in a directed graph.
type Edges[Key comparable] map[Key]bool
// New returns a new graph.
func New[Key comparable]() *Graph[Key] {
return &Graph[Key]{
nodes: make(map[Key]Edges[Key]),
}
}
// Node returns the edges for a node. It creates the node if it does not exist.
func (g *Graph[Key]) Node(key Key) Edges[Key] {
n, ok := g.nodes[key]
if !ok {
n = make(Edges[Key])
g.nodes[key] = n
}
return n
}
// Edge adds an edge to the graph. It creates the nodes if they do not exist.
func (g *Graph[Key]) Edge(from Key, to Key) {
f := g.Node(from)
g.Node(to)
f.add(to)
}
// Add adds a node and its outgoing edges to the graph.
func (g *Graph[Key]) Add(node Key, edges []Key) {
n := g.Node(node)
for _, e := range edges {
g.Node(e)
n.add(e)
}
}
// Reverse returns a new graph with all edges reversed.
func (g *Graph[Key]) Reverse() *Graph[Key] {
r := New[Key]()
for from, e := range g.nodes {
r.Node(from)
for to := range e {
r.Edge(to, from)
}
}
return r
}
// Copy returns a new graph with the same nodes and edges.
func (g *Graph[Key]) Copy() *Graph[Key] {
c := New[Key]()
for from, e := range g.nodes {
c.Node(from)
for to := range e {
c.Edge(from, to)
}
}
return c
}
// Sort returns a topological sorted list of the graph nodes. It returns an
// error if the graph has a cycle. It is an implementation of Kahn's algorithm.
// Sort's time complexity is O(n) for n = [number of nodes] + [number of edges].
func (g *Graph[Key]) Sort() ([]Key, error) {
// https://en.wikipedia.org/wiki/Topological_sorting#Kahn's_algorithm
// We need to make a copy of the graph, so we can modify it.
gg := g.Copy()
// The original graph's edges are actually outgoing edges. We need to
// reverse the graph to detect nodes with no incoming edges in an efficient
// way. Without reversing the graph, we would need to iterate over all
// nodes and their edges to find nodes with no incoming edges.
r := g.Reverse()
// The sorted list of keys, which we will return
var sorted []Key
// The list of keys with no incoming edges. We need this to start the
// algorithm. We construct it using the reversed graph and finding nodes
// with no outgoing edges.
var next []Key
for k, e := range r.nodes {
if len(e) == 0 {
next = append(next, k)
}
}
// We iterate over the list of nodes with no incoming edges. This list will
// be empty when the graph is empty or when the graph has a cycle.
for len(next) > 0 {
n := next[0]
next = next[1:]
// We add the node n to the sorted list.
sorted = append(sorted, n)
// We iterate over the nodes that are connected to the current node n.
// We only consider outgoing edges, because the node we are visiting
// has no incoming edges.
for m := range gg.nodes[n] {
// We remove the edge from n to m from the graph.
delete(gg.nodes[n], m)
delete(r.nodes[m], n)
// If the node m has no incoming edges left after we removed the
// edge from n to m, we add it to the list of nodes with no
// incoming edges, so we can consider it in the next iteration.
if len(r.nodes[m]) == 0 {
next = append(next, m)
}
}
// We remove the node n from the graph. This is necessary to detect
// cycles.
delete(gg.nodes, n)
delete(r.nodes, n)
}
// If the graph is not empty, it means that there is a cycle in the graph.
if len(gg.nodes) > 0 {
return nil, fmt.Errorf("cycle detected")
}
return sorted, nil
}
// add adds a key to the edges.
func (n Edges[Key]) add(key Key) {
n[key] = true
}