add MinimumSpanningTree
continuous-integration/drone/push Build is passing Details

- add Prim's mst
- add Kruskal's mst
- edit weight interface
- add exercise directed cycle
This commit is contained in:
Raffaele Mignone 2019-05-20 22:28:07 +02:00
parent cb60157dd1
commit 33710b1391
Signed by: norangebit
GPG Key ID: F5255658CB220573
12 changed files with 478 additions and 2 deletions

View File

@ -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<Int> {
val cycle = Stack<Int>()
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)

View File

@ -43,6 +43,7 @@ interface WeightUndirectedGraph {
fun edgeNumber(): Int
fun addEdge(edge: EdgeI)
fun adjacentVertex(vertex: Int): Collection<EdgeI>
fun edges(): Collection<EdgeI>
}
interface WeightDirectedGraph : WeightUndirectedGraph {

View File

@ -52,4 +52,15 @@ class WeightDiGraph(
return reverse
}
override fun edges(): Collection<EdgeI> {
val edges = mutableListOf<EdgeI>()
(0 until data.vertexNumber())
.forEach {
edges.addAll(adjacentVertex(it))
}
return edges
}
}

View File

@ -42,4 +42,15 @@ class WeightGraph(
}
override fun adjacentVertex(vertex: Int): Collection<EdgeI> = data.adjacent(vertex)
override fun edges(): Collection<EdgeI> {
val edges = mutableListOf<EdgeI>()
(0 until data.vertexNumber())
.forEach {
edges.addAll(adjacentVertex(it))
}
return edges
}
}

View File

@ -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)
}

View File

@ -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<EdgeI>()
init {
val minQueue = BinaryHeap.createMinPriorityQueue<EdgeI>()
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<EdgeI>, 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<EdgeI> = mst
override fun weight(): Double = mst.map { it.weight() }.sum()
}

View File

@ -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<EdgeI>
fun weight(): Double
}

View File

@ -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<EdgeI>()
private val visited = Array(graph.vertexNumber()) { false }
init {
val queue = BinaryHeap.createMinPriorityQueue<EdgeI>()
visitAdjacent(graph, 0, queue)
while (!queue.isEmpty() && mst.size < graph.vertexNumber() - 1)
tryEdge(graph, queue)
}
private fun tryEdge(graph: WeightUndirectedGraph, heap: PriorityQueue<EdgeI>) {
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<EdgeI>
) {
visited[vertex] = true
graph.adjacentVertex(vertex)
.forEach {
if (!visited[it.other(vertex)])
heap.insert(it)
}
}
override fun edges(): Collection<EdgeI> = mst
override fun weight(): Double = mst.map { it.weight() }.sum()
}

View File

@ -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") {

View File

@ -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)
)
}
}
}
})

View File

@ -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)
)
}
}
}
})

View File

@ -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)
)
}
}
}
})