diff --git a/doc/exercises/directed_cycle.md b/doc/exercises/directed_cycle.md new file mode 100644 index 0000000..1460ec1 --- /dev/null +++ b/doc/exercises/directed_cycle.md @@ -0,0 +1,88 @@ +--- +author: Raffaele Mignone +title: Caratterizzazione della complessità di un algoritmo per la ricerca di cicli orientati in un grafo +keywords: + - Complessità + - Grafo + - Ciclo +subject: Caratterizzazione della complessità +papersize: a4 +lang: it-IT +--- + +# Ricerca di cicli orientati + +## Traccia + +Scrivere un programma per la ricerca di directed cycles in un grafo orientato. + +## Soluzione + +Il problema può essere risolto prendendo come base la ricerca in profondità classica. +Ad essa deve essere aggiunto un modo per tenere memoria dei nodi presenti sul percorso che si sta esplorando. +Ciò può essere fatto tramite l'uso di un array così come si è fatto per tenere traccia dei nodi visitati[^vertex-info]. + +[^vertex-info]: Nel caso specifico si è utilizzata una data class che conserva le informazioni `isVisited`, `isOnPath` e `previously`. + +```{#lst:cycle .kotlin caption="Versione modificata della ricerca in profondità"} +private fun dfs(graph: UndirectedGraph, vertex: Int) { + graphInfo[vertex].isVisited = true + graphInfo[vertex].isOnPath = true + + graph.adjacentVertex(vertex) + .forEach { + when { + hasCycle() -> return@forEach + !graphInfo[it].isVisited -> exploreChild(graph, vertex, it) + graphInfo[it].isOnStack -> cycle = makeCycle(vertex, it) + } + } + + graphInfo[vertex].isOnPath = false +} +``` + +Quando tra i vertici adiacenti a quello che si sta esplorando si trova un nodo già presente sul percorso vuol dire che è stato trovato un ciclo. +La costruzione del ciclo viene mostrata nel @lst:makeCycle + +```{#lst:makeCycle .kotlin caption="Memorizzazione dei vertici che danno origine ad un ciclo"} +private fun makeCycle( + start: Int, + end: Int +): MutableCollection { + val cycle = Stack() + + var currentVertex = start + + while (currentVertex != end) { + cycle.add(currentVertex) + currentVertex = graphInfo[currentVertex].previously + } + + cycle.add(end) + cycle.add(start) + + return cycle +} +``` + +Infine la funzione `exploreChild` (@lst:exploreChild) semplicemente si occupa di settare il predecessore e di eseguire la ricerca in profondità sul nuovo vertice. + +```{#lst:exploreChild .kotlin caption="Funzione per eseguire la ricerca in profondità su un vertice adiacente"} +private fun exploreChild( + graph: UndirectedGraph, + parent: Int, + child: Int +) { + graphInfo[child].previously = parent + dfs(graph, child) +} +``` + +## Complessità + +L'algoritmo per l'individuazione dei grafi mostrato precedentemente è sostanzialmente una versione modificata della ricerca in profondità e nel caso peggiore deve visitare tutti i nodi percorrendo tutti gli archi, per cui ha una complessità pari a $E + V$. + +## Source code + +- [DirectedCycle](https://git.norangeb.it/norangebit-unisannio-computer-science/lm-tecniche-di-programmazione/src/branch/master/src/main/kotlin/it/norangeb/algorithms/graph/operations/DirectedCycle.kt) diff --git a/src/main/kotlin/it/norangeb/algorithms/graph/GraphInterface.kt b/src/main/kotlin/it/norangeb/algorithms/graph/GraphInterface.kt index 653ea19..0e5b96c 100644 --- a/src/main/kotlin/it/norangeb/algorithms/graph/GraphInterface.kt +++ b/src/main/kotlin/it/norangeb/algorithms/graph/GraphInterface.kt @@ -43,6 +43,7 @@ interface WeightUndirectedGraph { fun edgeNumber(): Int fun addEdge(edge: EdgeI) fun adjacentVertex(vertex: Int): Collection + fun edges(): Collection } interface WeightDirectedGraph : WeightUndirectedGraph { diff --git a/src/main/kotlin/it/norangeb/algorithms/graph/WeightDiGraph.kt b/src/main/kotlin/it/norangeb/algorithms/graph/WeightDiGraph.kt index b6b4766..28894ed 100644 --- a/src/main/kotlin/it/norangeb/algorithms/graph/WeightDiGraph.kt +++ b/src/main/kotlin/it/norangeb/algorithms/graph/WeightDiGraph.kt @@ -52,4 +52,15 @@ class WeightDiGraph( return reverse } + + override fun edges(): Collection { + val edges = mutableListOf() + + (0 until data.vertexNumber()) + .forEach { + edges.addAll(adjacentVertex(it)) + } + + return edges + } } \ No newline at end of file diff --git a/src/main/kotlin/it/norangeb/algorithms/graph/WeightGraph.kt b/src/main/kotlin/it/norangeb/algorithms/graph/WeightGraph.kt index d40eaad..b090720 100644 --- a/src/main/kotlin/it/norangeb/algorithms/graph/WeightGraph.kt +++ b/src/main/kotlin/it/norangeb/algorithms/graph/WeightGraph.kt @@ -42,4 +42,15 @@ class WeightGraph( } override fun adjacentVertex(vertex: Int): Collection = data.adjacent(vertex) + + override fun edges(): Collection { + val edges = mutableListOf() + + (0 until data.vertexNumber()) + .forEach { + edges.addAll(adjacentVertex(it)) + } + + return edges + } } \ No newline at end of file diff --git a/src/main/kotlin/it/norangeb/algorithms/graph/data/Edge.kt b/src/main/kotlin/it/norangeb/algorithms/graph/data/Edge.kt index 1bf84cd..f089e28 100644 --- a/src/main/kotlin/it/norangeb/algorithms/graph/data/Edge.kt +++ b/src/main/kotlin/it/norangeb/algorithms/graph/data/Edge.kt @@ -35,8 +35,15 @@ data class Edge(val from: Int, val to: Int, val weight: Double) : EdgeI { else -> from } - override fun compareTo(other: EdgeI): Int = - this.weight.compareTo(other.weight()) + override fun compareTo(other: EdgeI): Int { + var result = weight().compareTo(other.weight()) + if (result == 0) + result = either().compareTo(other.either()) + if (result == 0) + result = other(either()).compareTo(other.other(other.either())) + + return result + } override fun reversed(): EdgeI = Edge(to, from, weight) } \ No newline at end of file diff --git a/src/main/kotlin/it/norangeb/algorithms/graph/operations/Kruskal.kt b/src/main/kotlin/it/norangeb/algorithms/graph/operations/Kruskal.kt new file mode 100644 index 0000000..20fa831 --- /dev/null +++ b/src/main/kotlin/it/norangeb/algorithms/graph/operations/Kruskal.kt @@ -0,0 +1,63 @@ +/* + * MIT License + * + * Copyright (c) 2019 norangebit + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package it.norangeb.algorithms.graph.operations + +import it.norangeb.algorithms.datastructures.queue.priority.BinaryHeap +import it.norangeb.algorithms.datastructures.queue.priority.PriorityQueue +import it.norangeb.algorithms.datastructures.unionfind.QuickFind +import it.norangeb.algorithms.datastructures.unionfind.UnionFind +import it.norangeb.algorithms.graph.WeightUndirectedGraph +import it.norangeb.algorithms.graph.data.EdgeI + +class Kruskal(graph: WeightUndirectedGraph) : MinimumSpanningTree { + val mst = mutableListOf() + + init { + val minQueue = BinaryHeap.createMinPriorityQueue() + val unionFindGraph = QuickFind(graph.vertexNumber()) + + graph.edges().forEach { minQueue.insert(it) } + + while (!minQueue.isEmpty() && mst.size < graph.vertexNumber() - 1) + tryEdge(minQueue, unionFindGraph) + } + + private fun tryEdge(heap: PriorityQueue, unionFindGraph: UnionFind) { + heap.pop().map { + val from = it.either() + val to = it.other(from) + + if (!unionFindGraph.connected(from, to)) { + unionFindGraph.union(from, to) + mst.add(it) + } + } + } + + override fun edges(): Collection = mst + + override fun weight(): Double = mst.map { it.weight() }.sum() +} \ No newline at end of file diff --git a/src/main/kotlin/it/norangeb/algorithms/graph/operations/MinimumSpanningTree.kt b/src/main/kotlin/it/norangeb/algorithms/graph/operations/MinimumSpanningTree.kt new file mode 100644 index 0000000..16b4ac2 --- /dev/null +++ b/src/main/kotlin/it/norangeb/algorithms/graph/operations/MinimumSpanningTree.kt @@ -0,0 +1,33 @@ +/* + * MIT License + * + * Copyright (c) 2019 norangebit + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package it.norangeb.algorithms.graph.operations + +import it.norangeb.algorithms.graph.data.EdgeI + +interface MinimumSpanningTree { + fun edges(): Collection + fun weight(): Double +} \ No newline at end of file diff --git a/src/main/kotlin/it/norangeb/algorithms/graph/operations/Prim.kt b/src/main/kotlin/it/norangeb/algorithms/graph/operations/Prim.kt new file mode 100644 index 0000000..8a75188 --- /dev/null +++ b/src/main/kotlin/it/norangeb/algorithms/graph/operations/Prim.kt @@ -0,0 +1,79 @@ +/* + * MIT License + * + * Copyright (c) 2019 norangebit + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package it.norangeb.algorithms.graph.operations + +import it.norangeb.algorithms.datastructures.queue.priority.BinaryHeap +import it.norangeb.algorithms.datastructures.queue.priority.PriorityQueue +import it.norangeb.algorithms.graph.WeightUndirectedGraph +import it.norangeb.algorithms.graph.data.EdgeI + +class Prim(graph: WeightUndirectedGraph) : MinimumSpanningTree { + private val mst = mutableListOf() + private val visited = Array(graph.vertexNumber()) { false } + + init { + val queue = BinaryHeap.createMinPriorityQueue() + + visitAdjacent(graph, 0, queue) + + while (!queue.isEmpty() && mst.size < graph.vertexNumber() - 1) + tryEdge(graph, queue) + } + + private fun tryEdge(graph: WeightUndirectedGraph, heap: PriorityQueue) { + heap.pop().map { + val from = it.either() + val to = it.other(from) + + if (visited[from] && visited[to]) + return@map + if (!visited[from]) + visitAdjacent(graph, from, heap) + if (!visited[to]) + visitAdjacent(graph, to, heap) + + mst.add(it) + } + } + + private fun visitAdjacent( + graph: WeightUndirectedGraph, + vertex: Int, + heap: PriorityQueue + ) { + visited[vertex] = true + + graph.adjacentVertex(vertex) + .forEach { + if (!visited[it.other(vertex)]) + heap.insert(it) + } + } + + override fun edges(): Collection = mst + + override fun weight(): Double = mst.map { it.weight() }.sum() +} \ No newline at end of file diff --git a/src/test/kotlin/it/norangeb/algorithms/graph/WeightDiGraphTest.kt b/src/test/kotlin/it/norangeb/algorithms/graph/WeightDiGraphTest.kt index f72a97a..0f28404 100644 --- a/src/test/kotlin/it/norangeb/algorithms/graph/WeightDiGraphTest.kt +++ b/src/test/kotlin/it/norangeb/algorithms/graph/WeightDiGraphTest.kt @@ -73,6 +73,15 @@ object WeightDiGraphTest : Spek({ Then("adjacent of 3 should equal listOf(3->4:3.4)") { graph.adjacentVertex(3) `should equal` listOf(Edge(3, 4, 3.4)) } + + Then("edges should equals listOf(1->3:1.3, 1->4:1.4, 1->5:1.5, 3->4:3.4") { + graph.edges().sorted() `should equal` listOf( + Edge(1, 3, 1.3), + Edge(1, 4, 1.4), + Edge(1, 5, 1.5), + Edge(3, 4, 3.4) + ) + } } Scenario("reverse test") { diff --git a/src/test/kotlin/it/norangeb/algorithms/graph/WeightGraphTest.kt b/src/test/kotlin/it/norangeb/algorithms/graph/WeightGraphTest.kt index 7f9439d..0abd2dc 100644 --- a/src/test/kotlin/it/norangeb/algorithms/graph/WeightGraphTest.kt +++ b/src/test/kotlin/it/norangeb/algorithms/graph/WeightGraphTest.kt @@ -76,6 +76,20 @@ object WeightGraphTest : Spek({ Edge(3, 4, 3.4) ) } + + Then("edges should equals listOf(1->3:1.3, 1->5:1.5, 3->4:3.4, 1->4:5.0,") { + graph.edges().sorted() `should equal` listOf( + Edge(1, 3, 1.1), + Edge(3, 1, 1.1), + Edge(1, 5, 1.5), + Edge(5, 1, 1.5), + Edge(3, 4, 3.4), + Edge(4, 3, 3.4), + Edge(1, 4, 5.0), + Edge(4, 1, 5.0) + + ) + } } } }) \ No newline at end of file diff --git a/src/test/kotlin/it/norangeb/algorithms/graph/operations/KruskalTest.kt b/src/test/kotlin/it/norangeb/algorithms/graph/operations/KruskalTest.kt new file mode 100644 index 0000000..e85b2ce --- /dev/null +++ b/src/test/kotlin/it/norangeb/algorithms/graph/operations/KruskalTest.kt @@ -0,0 +1,80 @@ +/* + * MIT License + * + * Copyright (c) 2019 norangebit + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package it.norangeb.algorithms.graph.operations + +import it.norangeb.algorithms.graph.WeightGraph +import it.norangeb.algorithms.graph.data.Edge +import org.amshove.kluent.`should be equal to` +import org.amshove.kluent.`should equal` +import org.spekframework.spek2.Spek +import org.spekframework.spek2.style.gherkin.Feature + +object KruskalTest : Spek({ + Feature("kruskal") { + Scenario("compute mst") { + val graph by memoized { WeightGraph() } + + Given("a graph") { + graph.addEdge(Edge(0, 2, 0.26)) + graph.addEdge(Edge(0, 4, 0.38)) + graph.addEdge(Edge(0, 7, 0.16)) + graph.addEdge(Edge(1, 2, 0.36)) + graph.addEdge(Edge(1, 3, 0.29)) + graph.addEdge(Edge(1, 5, 0.32)) + graph.addEdge(Edge(1, 7, 0.19)) + graph.addEdge(Edge(2, 3, 0.17)) + graph.addEdge(Edge(2, 7, 0.34)) + graph.addEdge(Edge(3, 6, 0.52)) + graph.addEdge(Edge(4, 5, 0.35)) + graph.addEdge(Edge(4, 7, 0.37)) + graph.addEdge(Edge(5, 7, 0.28)) + graph.addEdge(Edge(6, 2, 0.40)) + } + + lateinit var result: MinimumSpanningTree + + When("compute the mst") { + result = Kruskal(graph) + } + + Then("weight should be 1.81") { + result.weight() `should be equal to` 1.81 + } + + Then("edge should be") { + result.edges() `should equal` listOf( + Edge(0, 7, 0.16), + Edge(2, 3, 0.17), + Edge(1, 7, 0.19), + Edge(0, 2, 0.26), + Edge(5, 7, 0.28), + Edge(4, 5, 0.35), + Edge(2, 6, 0.40) + ) + } + } + } +}) \ No newline at end of file diff --git a/src/test/kotlin/it/norangeb/algorithms/graph/operations/PrimTest.kt b/src/test/kotlin/it/norangeb/algorithms/graph/operations/PrimTest.kt new file mode 100644 index 0000000..999256a --- /dev/null +++ b/src/test/kotlin/it/norangeb/algorithms/graph/operations/PrimTest.kt @@ -0,0 +1,80 @@ +/* + * MIT License + * + * Copyright (c) 2019 norangebit + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package it.norangeb.algorithms.graph.operations + +import it.norangeb.algorithms.graph.WeightGraph +import it.norangeb.algorithms.graph.data.Edge +import org.amshove.kluent.`should be equal to` +import org.amshove.kluent.`should equal` +import org.spekframework.spek2.Spek +import org.spekframework.spek2.style.gherkin.Feature + +object PrimTest : Spek({ + Feature("prim") { + Scenario("compute mst") { + val graph by memoized { WeightGraph() } + + Given("a graph") { + graph.addEdge(Edge(0, 2, 0.26)) + graph.addEdge(Edge(0, 4, 0.38)) + graph.addEdge(Edge(0, 7, 0.16)) + graph.addEdge(Edge(1, 2, 0.36)) + graph.addEdge(Edge(1, 3, 0.29)) + graph.addEdge(Edge(1, 5, 0.32)) + graph.addEdge(Edge(1, 7, 0.19)) + graph.addEdge(Edge(2, 3, 0.17)) + graph.addEdge(Edge(2, 7, 0.34)) + graph.addEdge(Edge(3, 6, 0.52)) + graph.addEdge(Edge(4, 5, 0.35)) + graph.addEdge(Edge(4, 7, 0.37)) + graph.addEdge(Edge(5, 7, 0.28)) + graph.addEdge(Edge(6, 2, 0.40)) + } + + lateinit var result: MinimumSpanningTree + + When("compute the mst") { + result = Prim(graph) + } + + Then("weight should be 1.81") { + result.weight() `should be equal to` 1.81 + } + + Then("edge should be") { + result.edges().sorted() `should equal` listOf( + Edge(0, 7, 0.16), + Edge(2, 3, 0.17), + Edge(7, 1, 0.19), + Edge(0, 2, 0.26), + Edge(7, 5, 0.28), + Edge(5, 4, 0.35), + Edge(2, 6, 0.40) + ) + } + } + } +}) \ No newline at end of file