diff --git a/doc/exercises/kmin.md b/doc/exercises/kmin.md new file mode 100644 index 0000000..90baa6f --- /dev/null +++ b/doc/exercises/kmin.md @@ -0,0 +1,79 @@ +--- +author: Raffaele Mignone +title: Caratterizzazione della complessità di un algoritmo per la ricerca del k minimo in uno stream +keywords: + - Complessità + - Coda a priorità + - k minimo +subject: Caratterizzazione della complessità +papersize: a4 +lang: it-IT +--- + +# Ricerca del k minimo + +## Traccia + +Scrivere un programma che, dato uno stream di interi in ingresso, restituisce in ogni momento il k-esimo elemento più piccolo. + +## Soluzione + +La risoluzione del problema è avvenuta attraverso un cambiamento di prospettiva. +Infatti la ricerca del $k$-esimo elemento più piccolo è equivalente alla ricerca dell'elemento più grande all'interno di una collezione di $k$ elementi. +Una volta strutturato il problema come ricerca del massimo può essere velocemente risolto attraverso una coda a priorità massima come mostrato nel @lst:ignoreInput. + +```{#lst:ignoreInput .kotlin caption="Algoritmo di risoluzione non sensibile l'input"} +fun insert(elem: T): Option { + heap.insert(elem) + + if (heap.size() > k) + heap.pop() + + if (heap.size() < k) + return None + + return heap.peek() +} +``` + +## Caratterizzazione della complessità + +La complessità della funzione `insert` del @lst:ignoreInput è legata alla complessità delle funzioni della coda. +Sia la funzione per inserire un elemento nel binary heap che quella per rimuoverlo hanno una complessità logaritmica legata al numero di elementi nella coda. +Dato che una delle condizioni fondamenta per il corretto funzionamento dell'algoritmo è l'avere una coda sempre lunga $k$ possiamo affermare che la complessità della funzione `insert` è pari a $logk$. +Visto che la funzione `insert` sarà invocata per ogni elemento dello stream si ha una complessità totale di $nlogk$. + +La soluzione mostrata nel @lst:ignoreInput non è sensibile all'input del problema quindi rappresenta un upper bound che può essere migliorato come mostrato nel @lst:smart. + +```{#lst:smart .kotlin caption="Algoritmo di risoluzione sensibile all'input"} +fun insert(elem: T): Option { + heap.peek().fold( + { heap.insert(elem) }, + { + if (elem < it || heap.size() < k) + heap.insert(elem) + }) + + if (heap.size() > k) + heap.pop() + + if (heap.size() < k) + return None + + return heap.peek() +} +``` + +In questa nuova versione, attraverso la funzione `fold`[^fold], si evita di aggiungere elementi più grandi dell'attuale massimo, che quindi verrano eliminati immediatamente dalla lista per portarla di nuovo ad una lunghezza $k$. +In questo modo l'algoritmo diventa sensibile all'input e nel caso ideale (il più grande elemento tra i primi $k$ elementi dello stream è più piccolo di tutti gli elementi che vengono dopo $k$) si attiene una complessità di $klogk$ e quindi una soluzione che è costante rispetto alla dimensione del problema. + +[^fold]: La funzione `fold` ha come parametri due lambda expression, la prima viene eseguita quando l'oggetto è vuoto, mentre la seconda quando l'oggetto esiste. + +| best case | average | worst case | +| :-: | :-: | :-: | +| $klogk$ | $nlogk$ | $nlogk$ | + +## Source code + +- [BinaryHeap](https://git.norangeb.it/norangebit-unisannio-computer-science/lm-tecniche-di-programmazione/src/branch/master/src/main/kotlin/it/norangeb/algorithms/datastructures/queue/priority/BinaryHeap.kt) +- [KMin](https://git.norangeb.it/norangebit-unisannio-computer-science/lm-tecniche-di-programmazione/src/branch/master/src/main/kotlin/it/norangeb/algorithms/exercises/KMin.kt) diff --git a/src/main/kotlin/it/norangeb/algorithms/datastructures/dictionary/HashTable.kt b/src/main/kotlin/it/norangeb/algorithms/datastructures/dictionary/HashTable.kt index ba42e62..e897243 100644 --- a/src/main/kotlin/it/norangeb/algorithms/datastructures/dictionary/HashTable.kt +++ b/src/main/kotlin/it/norangeb/algorithms/datastructures/dictionary/HashTable.kt @@ -102,7 +102,7 @@ class HashTable : Dictionary { override fun size(): Int = size private fun getIndex(key: K): Int { - return key.hashCode() % HASHTABLE_SIZE + return key.hashCode().and(0x7fffffff) % HASHTABLE_SIZE } data class Node( diff --git a/src/test/kotlin/it/norangeb/algorithms/exercises/KMinTest.kt b/src/test/kotlin/it/norangeb/algorithms/exercises/KMinTest.kt index b274de1..f57eb18 100644 --- a/src/test/kotlin/it/norangeb/algorithms/exercises/KMinTest.kt +++ b/src/test/kotlin/it/norangeb/algorithms/exercises/KMinTest.kt @@ -27,7 +27,6 @@ package it.norangeb.algorithms.exercises import arrow.core.None import arrow.core.Some -import org.amshove.kluent.`should be` import org.amshove.kluent.shouldEqual import org.junit.jupiter.api.Test @@ -53,4 +52,25 @@ class KMinTest { Some(9) ) } + + @Test + fun test5() { + val input = listOf(15, 17, 9, 12, 22, 4, 73, 87, 12, 5) + + val kmin = KMin(5) + val result = input.map { kmin.insert(it) } + + result shouldEqual listOf( + None, + None, + None, + None, + Some(22), + Some(17), + Some(17), + Some(17), + Some(15), + Some(12) + ) + } } \ No newline at end of file