@@ -0,0 +1,81 @@
|
||||
package graphs
|
||||
|
||||
import (
|
||||
"datastructures/linear"
|
||||
)
|
||||
|
||||
// DFS() -> Depth First traversal, returns ordered slice or nil if start point
|
||||
// is invalid
|
||||
func (graph *Graph[T]) DFS(start T) []T {
|
||||
// sanity check
|
||||
if _, ok := graph.adjList[start]; !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
visited := make(map[T]bool, len(graph.adjList))
|
||||
ordered := make([]T, 0, graph.Order())
|
||||
|
||||
stack := linear.Stack[T]{}
|
||||
|
||||
stack.Push(start)
|
||||
visited[start] = true
|
||||
|
||||
for stack.Size() > 0 {
|
||||
|
||||
current, err := stack.Pop()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
ordered = append(ordered, current)
|
||||
|
||||
for _, neighbor := range graph.adjList[current] {
|
||||
if !visited[neighbor] {
|
||||
visited[neighbor] = true
|
||||
stack.Push(neighbor)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return ordered
|
||||
}
|
||||
|
||||
// BFS() -> Breadth First traversal start, returning the
|
||||
// vertices in visit order. Returns nil if start is not in the graph.
|
||||
func (graph *Graph[T]) BFS(start T) []T {
|
||||
// sanity check
|
||||
if _, ok := graph.adjList[start]; !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
visited := make(map[T]bool, len(graph.adjList))
|
||||
ordered := make([]T, 0, graph.Order())
|
||||
|
||||
queue := linear.Queue[T]{}
|
||||
|
||||
queue.Push(start)
|
||||
visited[start] = true
|
||||
|
||||
for queue.Size() > 0 {
|
||||
|
||||
current, err := queue.Pop()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
ordered = append(ordered, current)
|
||||
|
||||
for _, neighbor := range graph.adjList[current] {
|
||||
if !visited[neighbor] {
|
||||
visited[neighbor] = true
|
||||
queue.Push(neighbor)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return ordered
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"datastructures/graphs"
|
||||
)
|
||||
|
||||
func TestBFSUnknownStartReturnsNil(t *testing.T) {
|
||||
graph := graphs.NewGraph[string]()
|
||||
graph.AddVertex("a", "b")
|
||||
|
||||
if order := graph.BFS("zz"); order != nil {
|
||||
t.Errorf("BFS from unknown vertex = %v, want nil", order)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBFSEmptyGraphReturnsNil(t *testing.T) {
|
||||
graph := graphs.NewGraph[int]()
|
||||
|
||||
if order := graph.BFS(1); order != nil {
|
||||
t.Errorf("BFS on empty graph = %v, want nil", order)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBFSIsolatedVertexVisitsOnlyItself(t *testing.T) {
|
||||
graph := graphs.NewGraph[string]()
|
||||
graph.AddIsolatedVertex("lonely")
|
||||
|
||||
order := graph.BFS("lonely")
|
||||
if !slices.Equal(order, []string{"lonely"}) {
|
||||
t.Errorf("BFS = %v, want [lonely]", order)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBFSPathGraphVisitsInOrder(t *testing.T) {
|
||||
graph := graphs.NewGraph[string]()
|
||||
graph.AddVertex("a", "b")
|
||||
graph.AddVertex("b", "c")
|
||||
graph.AddVertex("c", "d")
|
||||
|
||||
order := graph.BFS("a")
|
||||
if !slices.Equal(order, []string{"a", "b", "c", "d"}) {
|
||||
t.Errorf("BFS = %v, want [a b c d]", order)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBFSVisitsLevelByLevel(t *testing.T) {
|
||||
// a
|
||||
// / \
|
||||
// b c
|
||||
// | |
|
||||
// d e
|
||||
graph := graphs.NewGraph[string]()
|
||||
graph.AddVertex("a", "b")
|
||||
graph.AddVertex("a", "c")
|
||||
graph.AddVertex("b", "d")
|
||||
graph.AddVertex("c", "e")
|
||||
|
||||
order := graph.BFS("a")
|
||||
if !slices.Equal(order, []string{"a", "b", "c", "d", "e"}) {
|
||||
t.Errorf("BFS = %v, want [a b c d e]", order)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBFSFromNonRootStart(t *testing.T) {
|
||||
graph := graphs.NewGraph[string]()
|
||||
graph.AddVertex("a", "b")
|
||||
graph.AddVertex("a", "c")
|
||||
graph.AddVertex("b", "d")
|
||||
|
||||
// b's adjacency is [a d], so a comes before d
|
||||
order := graph.BFS("b")
|
||||
if !slices.Equal(order, []string{"b", "a", "d", "c"}) {
|
||||
t.Errorf("BFS from b = %v, want [b a d c]", order)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBFSCycleTerminatesWithoutRepeats(t *testing.T) {
|
||||
graph := graphs.NewGraph[string]()
|
||||
graph.AddVertex("a", "b")
|
||||
graph.AddVertex("b", "c")
|
||||
graph.AddVertex("c", "a")
|
||||
|
||||
order := graph.BFS("a")
|
||||
if len(order) != 3 {
|
||||
t.Fatalf("BFS on triangle = %v, want 3 vertices", order)
|
||||
}
|
||||
assertNoDuplicates(t, order)
|
||||
}
|
||||
|
||||
func TestBFSDenseGraphVisitsEachVertexOnce(t *testing.T) {
|
||||
// K4: every vertex connected to every other
|
||||
graph := graphs.NewGraph[int]()
|
||||
for first := 1; first <= 4; first++ {
|
||||
for second := first + 1; second <= 4; second++ {
|
||||
graph.AddVertex(first, second)
|
||||
}
|
||||
}
|
||||
|
||||
order := graph.BFS(1)
|
||||
if len(order) != graph.Order() {
|
||||
t.Errorf("BFS visited %d vertices, want %d", len(order), graph.Order())
|
||||
}
|
||||
assertNoDuplicates(t, order)
|
||||
}
|
||||
|
||||
func TestBFSSkipsDisconnectedComponent(t *testing.T) {
|
||||
graph := graphs.NewGraph[string]()
|
||||
graph.AddVertex("a", "b")
|
||||
graph.AddVertex("x", "y")
|
||||
|
||||
order := graph.BFS("a")
|
||||
if !slices.Equal(order, []string{"a", "b"}) {
|
||||
t.Errorf("BFS = %v, want only the component of a: [a b]", order)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBFSReachesEveryVertexInConnectedGraph(t *testing.T) {
|
||||
graph := graphs.NewGraph[int]()
|
||||
graph.AddVertex(1, 2)
|
||||
graph.AddVertex(2, 3)
|
||||
graph.AddVertex(3, 4)
|
||||
graph.AddVertex(4, 1)
|
||||
graph.AddVertex(2, 5)
|
||||
|
||||
order := graph.BFS(1)
|
||||
if len(order) != graph.Order() {
|
||||
t.Errorf("BFS visited %d vertices, want %d", len(order), graph.Order())
|
||||
}
|
||||
for vertex := range graph.Display() {
|
||||
if !slices.Contains(order, vertex) {
|
||||
t.Errorf("vertex %d was never visited, order = %v", vertex, order)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBFSDistancesAreNonDecreasing(t *testing.T) {
|
||||
graph := graphs.NewGraph[string]()
|
||||
graph.AddVertex("a", "b")
|
||||
graph.AddVertex("a", "c")
|
||||
graph.AddVertex("b", "d")
|
||||
graph.AddVertex("c", "d")
|
||||
graph.AddVertex("d", "e")
|
||||
graph.AddVertex("e", "f")
|
||||
|
||||
order := graph.BFS("a")
|
||||
|
||||
// hop count from a, derived from the graph itself
|
||||
distances := map[string]int{"a": 0, "b": 1, "c": 1, "d": 2, "e": 3, "f": 4}
|
||||
previous := 0
|
||||
for _, vertex := range order {
|
||||
current, ok := distances[vertex]
|
||||
if !ok {
|
||||
t.Fatalf("BFS returned unexpected vertex %q", vertex)
|
||||
}
|
||||
if current < previous {
|
||||
t.Errorf("BFS = %v: %q at distance %d visited after distance %d", order, vertex, current, previous)
|
||||
}
|
||||
previous = current
|
||||
}
|
||||
}
|
||||
|
||||
func TestBFSDoesNotMutateGraph(t *testing.T) {
|
||||
graph := graphs.NewGraph[string]()
|
||||
graph.AddVertex("a", "b")
|
||||
graph.AddVertex("b", "c")
|
||||
|
||||
orderBefore, sizeBefore := graph.Order(), graph.Size()
|
||||
graph.BFS("a")
|
||||
|
||||
if graph.Order() != orderBefore || graph.Size() != sizeBefore {
|
||||
t.Errorf("after BFS Order/Size = %d/%d, want %d/%d",
|
||||
graph.Order(), graph.Size(), orderBefore, sizeBefore)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBFSIsRepeatable(t *testing.T) {
|
||||
graph := graphs.NewGraph[string]()
|
||||
graph.AddVertex("a", "b")
|
||||
graph.AddVertex("a", "c")
|
||||
graph.AddVertex("b", "d")
|
||||
|
||||
first := graph.BFS("a")
|
||||
second := graph.BFS("a")
|
||||
|
||||
if !slices.Equal(first, second) {
|
||||
t.Errorf("BFS not repeatable: %v then %v", first, second)
|
||||
}
|
||||
}
|
||||
|
||||
func assertNoDuplicates[T comparable](t *testing.T, order []T) {
|
||||
t.Helper()
|
||||
|
||||
seen := make(map[T]bool, len(order))
|
||||
for _, vertex := range order {
|
||||
if seen[vertex] {
|
||||
t.Errorf("vertex %v visited more than once in %v", vertex, order)
|
||||
}
|
||||
seen[vertex] = true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"datastructures/graphs"
|
||||
)
|
||||
|
||||
// DFS marks a vertex visited when it is pushed, so the neighbours of a vertex
|
||||
// come off the stack in reverse adjacency order: the last neighbour listed is
|
||||
// explored first. The exact-order expectations below follow that rule.
|
||||
|
||||
func TestDFSUnknownStartReturnsNil(t *testing.T) {
|
||||
graph := graphs.NewGraph[string]()
|
||||
graph.AddVertex("a", "b")
|
||||
|
||||
if order := graph.DFS("zz"); order != nil {
|
||||
t.Errorf("DFS from unknown vertex = %v, want nil", order)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDFSEmptyGraphReturnsNil(t *testing.T) {
|
||||
graph := graphs.NewGraph[int]()
|
||||
|
||||
if order := graph.DFS(1); order != nil {
|
||||
t.Errorf("DFS on empty graph = %v, want nil", order)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDFSIsolatedVertexVisitsOnlyItself(t *testing.T) {
|
||||
graph := graphs.NewGraph[string]()
|
||||
graph.AddIsolatedVertex("lonely")
|
||||
|
||||
order := graph.DFS("lonely")
|
||||
if !slices.Equal(order, []string{"lonely"}) {
|
||||
t.Errorf("DFS = %v, want [lonely]", order)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDFSPathGraphVisitsInOrder(t *testing.T) {
|
||||
graph := graphs.NewGraph[string]()
|
||||
graph.AddVertex("a", "b")
|
||||
graph.AddVertex("b", "c")
|
||||
graph.AddVertex("c", "d")
|
||||
|
||||
order := graph.DFS("a")
|
||||
if !slices.Equal(order, []string{"a", "b", "c", "d"}) {
|
||||
t.Errorf("DFS = %v, want [a b c d]", order)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDFSGoesDeepBeforeWide(t *testing.T) {
|
||||
// a
|
||||
// / \
|
||||
// b c
|
||||
// | |
|
||||
// d e
|
||||
graph := graphs.NewGraph[string]()
|
||||
graph.AddVertex("a", "b")
|
||||
graph.AddVertex("a", "c")
|
||||
graph.AddVertex("b", "d")
|
||||
graph.AddVertex("c", "e")
|
||||
|
||||
// c is pushed last, so its branch is exhausted (c, e) before b's
|
||||
order := graph.DFS("a")
|
||||
if !slices.Equal(order, []string{"a", "c", "e", "b", "d"}) {
|
||||
t.Errorf("DFS = %v, want [a c e b d]", order)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDFSFromNonRootStart(t *testing.T) {
|
||||
graph := graphs.NewGraph[string]()
|
||||
graph.AddVertex("a", "b")
|
||||
graph.AddVertex("a", "c")
|
||||
graph.AddVertex("b", "d")
|
||||
|
||||
// b's adjacency is [a d], so d is popped first, then a and its branch
|
||||
order := graph.DFS("b")
|
||||
if !slices.Equal(order, []string{"b", "d", "a", "c"}) {
|
||||
t.Errorf("DFS from b = %v, want [b d a c]", order)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDFSCycleTerminatesWithoutRepeats(t *testing.T) {
|
||||
graph := graphs.NewGraph[string]()
|
||||
graph.AddVertex("a", "b")
|
||||
graph.AddVertex("b", "c")
|
||||
graph.AddVertex("c", "a")
|
||||
|
||||
order := graph.DFS("a")
|
||||
if len(order) != 3 {
|
||||
t.Fatalf("DFS on triangle = %v, want 3 vertices", order)
|
||||
}
|
||||
assertNoDuplicates(t, order)
|
||||
}
|
||||
|
||||
func TestDFSDenseGraphVisitsEachVertexOnce(t *testing.T) {
|
||||
// K4: every vertex connected to every other
|
||||
graph := graphs.NewGraph[int]()
|
||||
for first := 1; first <= 4; first++ {
|
||||
for second := first + 1; second <= 4; second++ {
|
||||
graph.AddVertex(first, second)
|
||||
}
|
||||
}
|
||||
|
||||
order := graph.DFS(1)
|
||||
if len(order) != graph.Order() {
|
||||
t.Errorf("DFS visited %d vertices, want %d", len(order), graph.Order())
|
||||
}
|
||||
assertNoDuplicates(t, order)
|
||||
}
|
||||
|
||||
func TestDFSSkipsDisconnectedComponent(t *testing.T) {
|
||||
graph := graphs.NewGraph[string]()
|
||||
graph.AddVertex("a", "b")
|
||||
graph.AddVertex("x", "y")
|
||||
|
||||
order := graph.DFS("a")
|
||||
if !slices.Equal(order, []string{"a", "b"}) {
|
||||
t.Errorf("DFS = %v, want only the component of a: [a b]", order)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDFSReachesEveryVertexInConnectedGraph(t *testing.T) {
|
||||
graph := graphs.NewGraph[int]()
|
||||
graph.AddVertex(1, 2)
|
||||
graph.AddVertex(2, 3)
|
||||
graph.AddVertex(3, 4)
|
||||
graph.AddVertex(4, 1)
|
||||
graph.AddVertex(2, 5)
|
||||
|
||||
order := graph.DFS(1)
|
||||
if len(order) != graph.Order() {
|
||||
t.Errorf("DFS visited %d vertices, want %d", len(order), graph.Order())
|
||||
}
|
||||
for vertex := range graph.Display() {
|
||||
if !slices.Contains(order, vertex) {
|
||||
t.Errorf("vertex %d was never visited, order = %v", vertex, order)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDFSOrderIsReachableByBacktracking(t *testing.T) {
|
||||
// a graph with cross edges and two cycles, where a naive traversal could
|
||||
// emit a vertex that is not reachable from the current path
|
||||
graph := graphs.NewGraph[string]()
|
||||
graph.AddVertex("a", "b")
|
||||
graph.AddVertex("a", "c")
|
||||
graph.AddVertex("b", "d")
|
||||
graph.AddVertex("c", "d")
|
||||
graph.AddVertex("d", "e")
|
||||
graph.AddVertex("e", "f")
|
||||
graph.AddVertex("f", "a")
|
||||
graph.AddVertex("c", "f")
|
||||
|
||||
order := graph.DFS("a")
|
||||
|
||||
assertNoDuplicates(t, order)
|
||||
if len(order) != graph.Order() {
|
||||
t.Fatalf("DFS = %v, want all %d vertices", order, graph.Order())
|
||||
}
|
||||
assertValidDFSOrder(t, graph.Display(), order)
|
||||
}
|
||||
|
||||
func TestDFSHandlesDeepChainWithoutRecursion(t *testing.T) {
|
||||
// deep enough to blow a recursive implementation's stack budget
|
||||
const length = 10000
|
||||
|
||||
graph := graphs.NewGraph[int]()
|
||||
for vertex := 0; vertex < length-1; vertex++ {
|
||||
graph.AddVertex(vertex, vertex+1)
|
||||
}
|
||||
|
||||
order := graph.DFS(0)
|
||||
if len(order) != length {
|
||||
t.Fatalf("DFS visited %d vertices, want %d", len(order), length)
|
||||
}
|
||||
for index, vertex := range order {
|
||||
if vertex != index {
|
||||
t.Fatalf("DFS[%d] = %d, want %d", index, vertex, index)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDFSDoesNotMutateGraph(t *testing.T) {
|
||||
graph := graphs.NewGraph[string]()
|
||||
graph.AddVertex("a", "b")
|
||||
graph.AddVertex("b", "c")
|
||||
|
||||
orderBefore, sizeBefore := graph.Order(), graph.Size()
|
||||
graph.DFS("a")
|
||||
|
||||
if graph.Order() != orderBefore || graph.Size() != sizeBefore {
|
||||
t.Errorf("after DFS Order/Size = %d/%d, want %d/%d",
|
||||
graph.Order(), graph.Size(), orderBefore, sizeBefore)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDFSIsRepeatable(t *testing.T) {
|
||||
graph := graphs.NewGraph[string]()
|
||||
graph.AddVertex("a", "b")
|
||||
graph.AddVertex("a", "c")
|
||||
graph.AddVertex("b", "d")
|
||||
|
||||
first := graph.DFS("a")
|
||||
second := graph.DFS("a")
|
||||
|
||||
if !slices.Equal(first, second) {
|
||||
t.Errorf("DFS not repeatable: %v then %v", first, second)
|
||||
}
|
||||
}
|
||||
|
||||
// assertValidDFSOrder checks that order is a legal depth-first ordering: each
|
||||
// vertex must attach to the deepest vertex on the current path that is adjacent
|
||||
// to it, which is exactly what backtracking out of a dead end produces.
|
||||
func assertValidDFSOrder[T comparable](t *testing.T, adjacency map[T][]T, order []T) {
|
||||
t.Helper()
|
||||
|
||||
if len(order) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
path := []T{order[0]}
|
||||
for _, vertex := range order[1:] {
|
||||
for len(path) > 0 && !slices.Contains(adjacency[path[len(path)-1]], vertex) {
|
||||
path = path[:len(path)-1]
|
||||
}
|
||||
if len(path) == 0 {
|
||||
t.Fatalf("DFS = %v: %v is not adjacent to anything on the path back to the start", order, vertex)
|
||||
}
|
||||
path = append(path, vertex)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user