-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
313c2c5
commit a9d57ce
Showing
5 changed files
with
178 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
69 changes: 69 additions & 0 deletions
69
src/commonMain/kotlin/io/github/alexandrepiveteau/graphs/algorithms/TopologicalSort.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
@file:JvmName("Algorithms") | ||
@file:JvmMultifileClass | ||
|
||
package io.github.alexandrepiveteau.graphs.algorithms | ||
|
||
import io.github.alexandrepiveteau.graphs.DirectedGraph | ||
import io.github.alexandrepiveteau.graphs.VertexArray | ||
import io.github.alexandrepiveteau.graphs.asVertexArray | ||
import io.github.alexandrepiveteau.graphs.internal.collections.IntDequeue | ||
import kotlin.jvm.JvmMultifileClass | ||
import kotlin.jvm.JvmName | ||
|
||
/** | ||
* Returns a [VertexArray] containing the vertices of the graph, sorted in topological order. If the | ||
* graph contains a cycle, an [IllegalArgumentException] is thrown. | ||
* | ||
* ## Asymptotic complexity | ||
* - **Time complexity**: O(|N| + |E|), where |N| is the number of vertices in this graph and |E| is | ||
* the number of edges in this graph. | ||
* - **Space complexity**: O(|N|), where n is the number of vertices in this graph. | ||
* | ||
* @return a [VertexArray] containing the vertices of the graph, sorted in topological order. | ||
* @receiver the [DirectedGraph] to sort topologically. | ||
* @throws IllegalArgumentException if the graph contains a cycle. | ||
*/ | ||
public fun DirectedGraph.topologicalSort(): VertexArray { | ||
|
||
// 0. Set up a queue of vertices to add to the topological sort, and a boolean array to keep track | ||
// of the vertices that have already been sorted. | ||
val queue = IntDequeue() | ||
val sorted = BooleanArray(size) | ||
val result = IntDequeue() | ||
|
||
// 1. Compute the number of incoming edges for each vertex. | ||
val edges = IntArray(size) | ||
forEachArc { (_, to) -> edges[get(to)]++ } | ||
|
||
// 2. Add all the vertices with no incoming edges to the queue. | ||
for (i in edges.indices) { | ||
if (edges[i] == 0) { | ||
queue.addLast(i) | ||
sorted[i] = true | ||
} | ||
} | ||
|
||
// 3. While there are some vertices to add to the topological sort, add them and remove their | ||
// outgoing edges. If a vertex has no more outgoing edges and has not been sorted yet, add it to | ||
// the queue. | ||
while (queue.size > 0) { | ||
val vertex = queue.removeFirst() | ||
result.addLast(vertex) | ||
|
||
forEachNeighbor(get(vertex)) { | ||
val to = get(it) | ||
edges[to]-- | ||
if (edges[to] == 0 && !sorted[to]) { | ||
queue.addLast(to) | ||
sorted[to] = true | ||
} | ||
} | ||
} | ||
|
||
// 4. If there are still some vertices that have not been sorted, then there is a cycle in the | ||
// graph. Throw an exception. | ||
if (result.size != size) throw IllegalArgumentException("The graph contains a cycle.") | ||
|
||
// 5. Return the result. | ||
return result.toIntArray().asVertexArray() | ||
} |
85 changes: 85 additions & 0 deletions
85
src/commonTest/kotlin/io/github/alexandrepiveteau/graphs/algorithms/TopologicalSortTests.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
package io.github.alexandrepiveteau.graphs.algorithms | ||
|
||
import io.github.alexandrepiveteau.graphs.Vertex | ||
import io.github.alexandrepiveteau.graphs.VertexArray | ||
import io.github.alexandrepiveteau.graphs.arcTo | ||
import io.github.alexandrepiveteau.graphs.builder.buildDirectedGraph | ||
import io.github.alexandrepiveteau.graphs.toVertexArray | ||
import io.github.alexandrepiveteau.graphs.util.Repeats | ||
import io.github.alexandrepiveteau.graphs.util.assertContentEquals | ||
import kotlin.test.Test | ||
import kotlin.test.assertEquals | ||
import kotlin.test.assertFailsWith | ||
|
||
class TopologicalSortTests { | ||
|
||
@Test | ||
fun emptyGraphHasEmptyTopologicalSort() { | ||
val graph = buildDirectedGraph {} | ||
val order = graph.topologicalSort() | ||
assertContentEquals(VertexArray(0), order) | ||
} | ||
|
||
@Test | ||
fun singletonGraphHasSingletonTopologicalSort() { | ||
val graph = buildDirectedGraph { addVertex() } | ||
val order = graph.topologicalSort() | ||
assertContentEquals(VertexArray(1) { Vertex(0) }, order) | ||
} | ||
|
||
@Test | ||
fun loopHasNoTopologicalSort() { | ||
val graph = buildDirectedGraph { | ||
val a = addVertex() | ||
addArc(a arcTo a) | ||
} | ||
assertFailsWith<IllegalArgumentException> { graph.topologicalSort() } | ||
} | ||
|
||
@Test | ||
fun bidirectionalGraphHasNoTopologicalSort() { | ||
val graph = buildDirectedGraph { | ||
val (a, b) = addVertices() | ||
addArc(a arcTo b) | ||
addArc(b arcTo a) | ||
} | ||
assertFailsWith<IllegalArgumentException> { graph.topologicalSort() } | ||
} | ||
|
||
@Test | ||
fun disjointGraphHasTopologicalSort() { | ||
for (count in 0 until Repeats) { | ||
val graph = buildDirectedGraph { repeat(count) { addVertex() } } | ||
val order = graph.topologicalSort() | ||
assertEquals(graph.size, order.size) | ||
} | ||
} | ||
|
||
@Test | ||
fun lineGraphHasIdenticalTopologicalSort() { | ||
for (count in 2 until Repeats) { | ||
val graph = buildDirectedGraph { | ||
val vertices = VertexArray(count) { addVertex() } | ||
repeat(count - 1) { addArc(vertices[it] arcTo vertices[it + 1]) } | ||
} | ||
val order = graph.topologicalSort() | ||
assertContentEquals(VertexArray(graph.size) { graph[it] }, order) | ||
} | ||
} | ||
|
||
@Test | ||
fun geeksForGeeks() { | ||
val graph = buildDirectedGraph { | ||
val (v0, v1, v2, v3, v4, v5) = addVertices() | ||
addArc(v5 arcTo v2) | ||
addArc(v5 arcTo v0) | ||
addArc(v4 arcTo v0) | ||
addArc(v4 arcTo v1) | ||
addArc(v2 arcTo v3) | ||
addArc(v3 arcTo v1) | ||
} | ||
val order = graph.topologicalSort() | ||
val expected = arrayOf(graph[4], graph[5], graph[0], graph[2], graph[3], graph[1]) | ||
assertContentEquals(expected.toVertexArray(), order) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters