From ab8845ef372402b3bef4a94179027ed30891fac4 Mon Sep 17 00:00:00 2001 From: Yinfeng Lu Date: Mon, 2 Sep 2024 23:46:58 -0500 Subject: [PATCH 1/3] feat: implemented kahn's algorithm --- graph/kahn.go | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 graph/kahn.go diff --git a/graph/kahn.go b/graph/kahn.go new file mode 100644 index 000000000..c82522ca1 --- /dev/null +++ b/graph/kahn.go @@ -0,0 +1,40 @@ +package graph + +func Kahn(n int, dependencies [][]int) []int { + g := Graph{vertices: n, Directed: true} + inDegree := make([]int, n) + + for _, d := range dependencies { + if _, ok := g.edges[d[0]][d[1]]; !ok { + g.AddEdge(d[0], d[1]) + inDegree[d[1]]++ + } + } + + queue := make([]int, 0, n) + + for i := 0; i < n; i++ { + if inDegree[i] == 0 { + queue = append(queue, i) + } + } + + order := make([]int, 0, n) + + for len(queue) > 0 { + vtx := queue[0] + queue = queue[1:] + order = append(order, vtx) + for neighbour := range g.edges[vtx] { + inDegree[neighbour]-- + if inDegree[neighbour] == 0 { + queue = append(queue, neighbour) + } + } + } + + if len(order) != n { + return nil + } + return order +} From 5d9df4c60e6cc8f0f85386a52cad568b00ba2525 Mon Sep 17 00:00:00 2001 From: Yinfeng Lu Date: Mon, 2 Sep 2024 23:47:51 -0500 Subject: [PATCH 2/3] doc: added doc for graph/kahn.go --- graph/kahn.go | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/graph/kahn.go b/graph/kahn.go index c82522ca1..6f9d44d71 100644 --- a/graph/kahn.go +++ b/graph/kahn.go @@ -1,16 +1,33 @@ +// Kahn's algorithm computes a topological ordering of a directed acyclic graph (DAG). +// Time Complexity: O(V + E) +// Space Complexity: O(V + E) +// Reference: https://en.wikipedia.org/wiki/Topological_sorting#Kahn's_algorithm +// see graph.go, topological.go, kahn_test.go + package graph +// Kahn's algorithm computes a topological ordering of a directed acyclic graph (DAG). +// `n` is the number of vertices, +// `dependencies` is a list of directed edges, where each pair [a, b] represents +// a directed edge from a to b (i.e. b depends on a). +// Vertices are assumed to be labelled 0, 1, ..., n-1. +// If the graph is not a DAG, the function returns nil. func Kahn(n int, dependencies [][]int) []int { g := Graph{vertices: n, Directed: true} + // track the in-degree (number of incoming edges) of each vertex inDegree := make([]int, n) + // populate g with edges, increase the in-degree counts accordingly for _, d := range dependencies { + // make sure we don't add the same edge twice if _, ok := g.edges[d[0]][d[1]]; !ok { g.AddEdge(d[0], d[1]) inDegree[d[1]]++ } } + // queue holds all vertices with in-degree 0 + // these vertices have no dependency and thus can be ordered first queue := make([]int, 0, n) for i := 0; i < n; i++ { @@ -19,12 +36,20 @@ func Kahn(n int, dependencies [][]int) []int { } } + // order holds a valid topological order order := make([]int, 0, n) + // process the dependency-free vertices + // every time we process a vertex, we "remove" it from the graph for len(queue) > 0 { + // pop the first vertex from the queue vtx := queue[0] queue = queue[1:] + // add the vertex to the topological order order = append(order, vtx) + // "remove" all the edges coming out of this vertex + // every time we remove an edge, the corresponding in-degree reduces by 1 + // if all dependencies on a vertex is removed, enqueue the vertex for neighbour := range g.edges[vtx] { inDegree[neighbour]-- if inDegree[neighbour] == 0 { @@ -33,6 +58,7 @@ func Kahn(n int, dependencies [][]int) []int { } } + // if the graph is a DAG, order should contain all the certices if len(order) != n { return nil } From 104beaf56e6d6f3c7af5a23ac6edfedc8e72d567 Mon Sep 17 00:00:00 2001 From: Yinfeng Lu Date: Mon, 2 Sep 2024 23:48:17 -0500 Subject: [PATCH 3/3] test: added tests for graph/kahn.go --- graph/kahn_test.go | 115 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 graph/kahn_test.go diff --git a/graph/kahn_test.go b/graph/kahn_test.go new file mode 100644 index 000000000..71536b4f6 --- /dev/null +++ b/graph/kahn_test.go @@ -0,0 +1,115 @@ +package graph + +import ( + "testing" +) + +func TestKahn(t *testing.T) { + testCases := []struct { + name string + n int + dependencies [][]int + wantNil bool + }{ + { + "linear graph", + 3, + [][]int{{0, 1}, {1, 2}}, + false, + }, + { + "diamond graph", + 4, + [][]int{{0, 1}, {0, 2}, {1, 3}, {2, 3}}, + false, + }, + { + "star graph", + 5, + [][]int{{0, 1}, {0, 2}, {0, 3}, {0, 4}}, + false, + }, + { + "disconnected graph", + 5, + [][]int{{0, 1}, {0, 2}, {3, 4}}, + false, + }, + { + "cycle graph 1", + 4, + [][]int{{0, 1}, {1, 2}, {2, 3}, {3, 0}}, + true, + }, + { + "cycle graph 2", + 4, + [][]int{{0, 1}, {1, 2}, {2, 0}, {2, 3}}, + true, + }, + { + "single node graph", + 1, + [][]int{}, + false, + }, + { + "empty graph", + 0, + [][]int{}, + false, + }, + { + "redundant dependencies", + 4, + [][]int{{0, 1}, {1, 2}, {1, 2}, {2, 3}}, + false, + }, + { + "island vertex", + 4, + [][]int{{0, 1}, {0, 2}}, + false, + }, + { + "more complicated graph", + 14, + [][]int{{1, 9}, {2, 0}, {3, 2}, {4, 5}, {4, 6}, {4, 7}, {6, 7}, + {7, 8}, {9, 4}, {10, 0}, {10, 1}, {10, 12}, {11, 13}, + {12, 0}, {12, 11}, {13, 5}}, + false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + actual := Kahn(tc.n, tc.dependencies) + if tc.wantNil { + if actual != nil { + t.Errorf("Kahn(%d, %v) = %v; want nil", tc.n, tc.dependencies, actual) + } + } else { + if actual == nil { + t.Errorf("Kahn(%d, %v) = nil; want valid order", tc.n, tc.dependencies) + } else { + seen := make([]bool, tc.n) + positions := make([]int, tc.n) + for i, v := range actual { + seen[v] = true + positions[v] = i + } + for i, v := range seen { + if !v { + t.Errorf("missing vertex %v", i) + } + } + for _, d := range tc.dependencies { + if positions[d[0]] > positions[d[1]] { + t.Errorf("dependency %v not satisfied", d) + } + } + } + } + }) + } +}