-
-
Notifications
You must be signed in to change notification settings - Fork 98
/
traversal.go
155 lines (135 loc) · 4.21 KB
/
traversal.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
package graph
import "fmt"
// DFS performs a depth-first search on the graph, starting from the given vertex. The visit
// function will be invoked with the hash of the vertex currently visited. If it returns false, DFS
// will continue traversing the graph, and if it returns true, the traversal will be stopped. In
// case the graph is disconnected, only the vertices joined with the starting vertex are visited.
//
// This example prints all vertices of the graph in DFS-order:
//
// g := graph.New(graph.IntHash)
//
// _ = g.AddVertex(1)
// _ = g.AddVertex(2)
// _ = g.AddVertex(3)
//
// _ = g.AddEdge(1, 2)
// _ = g.AddEdge(2, 3)
// _ = g.AddEdge(3, 1)
//
// _ = graph.DFS(g, 1, func(value int) bool {
// fmt.Println(value)
// return false
// })
//
// Similarly, if you have a graph of City vertices and the traversal should stop at London, the
// visit function would look as follows:
//
// func(c City) bool {
// return c.Name == "London"
// }
//
// DFS is non-recursive and maintains a stack instead.
func DFS[K comparable, T any](g Graph[K, T], start K, visit func(K) bool) error {
adjacencyMap, err := g.AdjacencyMap()
if err != nil {
return fmt.Errorf("could not get adjacency map: %w", err)
}
if _, ok := adjacencyMap[start]; !ok {
return fmt.Errorf("could not find start vertex with hash %v", start)
}
stack := newStack[K]()
visited := make(map[K]bool)
stack.push(start)
for !stack.isEmpty() {
currentHash, _ := stack.pop()
if _, ok := visited[currentHash]; !ok {
// Stop traversing the graph if the visit function returns true.
if stop := visit(currentHash); stop {
break
}
visited[currentHash] = true
for adjacency := range adjacencyMap[currentHash] {
stack.push(adjacency)
}
}
}
return nil
}
// BFS performs a breadth-first search on the graph, starting from the given vertex. The visit
// function will be invoked with the hash of the vertex currently visited. If it returns false, BFS
// will continue traversing the graph, and if it returns true, the traversal will be stopped. In
// case the graph is disconnected, only the vertices joined with the starting vertex are visited.
//
// This example prints all vertices of the graph in BFS-order:
//
// g := graph.New(graph.IntHash)
//
// _ = g.AddVertex(1)
// _ = g.AddVertex(2)
// _ = g.AddVertex(3)
//
// _ = g.AddEdge(1, 2)
// _ = g.AddEdge(2, 3)
// _ = g.AddEdge(3, 1)
//
// _ = graph.BFS(g, 1, func(value int) bool {
// fmt.Println(value)
// return false
// })
//
// Similarly, if you have a graph of City vertices and the traversal should stop at London, the
// visit function would look as follows:
//
// func(c City) bool {
// return c.Name == "London"
// }
//
// BFS is non-recursive and maintains a stack instead.
func BFS[K comparable, T any](g Graph[K, T], start K, visit func(K) bool) error {
ignoreDepth := func(vertex K, _ int) bool {
return visit(vertex)
}
return BFSWithDepth(g, start, ignoreDepth)
}
// BFSWithDepth works just as BFS and performs a breadth-first search on the graph, but its
// visit function is passed the current depth level as a second argument. Consequently, the
// current depth can be used for deciding whether or not to proceed past a certain depth.
//
// _ = graph.BFSWithDepth(g, 1, func(value int, depth int) bool {
// fmt.Println(value)
// return depth > 3
// })
//
// With the visit function from the example, the BFS traversal will stop once a depth greater
// than 3 is reached.
func BFSWithDepth[K comparable, T any](g Graph[K, T], start K, visit func(K, int) bool) error {
adjacencyMap, err := g.AdjacencyMap()
if err != nil {
return fmt.Errorf("could not get adjacency map: %w", err)
}
if _, ok := adjacencyMap[start]; !ok {
return fmt.Errorf("could not find start vertex with hash %v", start)
}
queue := make([]K, 0)
visited := make(map[K]bool)
visited[start] = true
queue = append(queue, start)
depth := 0
for len(queue) > 0 {
currentHash := queue[0]
queue = queue[1:]
depth++
// Stop traversing the graph if the visit function returns true.
if stop := visit(currentHash, depth); stop {
break
}
for adjacency := range adjacencyMap[currentHash] {
if _, ok := visited[adjacency]; !ok {
visited[adjacency] = true
queue = append(queue, adjacency)
}
}
}
return nil
}