Premessa:
- In questo post creerò la confusione comune tra
O(n)
eTheta(n)
come notazioni di complessità. - Scriverò pseudo-codice per parlare di algoritmi, usando qualsiasi notazione che trovo di mio gradimento.
Lo so, ancora un'altra domanda su cicli annidati e complessità.
Recentemente ho scritto un algoritmo annidato, che funzionerebbe come collo di bottiglia di un'applicazione più complessa. In qualche modo sembrava questo:
while(firstLoopRunning) {
foreach(element in myArray) {
doSomeAction(element);
}
}
Il mio acuto occhio mi ha portato alla conclusione che, come doSomeAction è in O(1)
, l'algoritmo doveva essere in O(n²)
.
So comunque che i cicli annidati possono avere una complessità diversa. Ad esempio quanto segue è in O(n.log(n))
:
for(i in 0..n) // n is constant in this loop
for(j in 0..i) // j goes up to i and not to a constant
print(j);
Poi ho pensato, hey, so che myArray ha una lunghezza massima. In questo caso specifico, conosco in base alla progettazione che non ci saranno mai più di 8 elementi in myArray, quindi l'algoritmo potrebbe assomigliare a questo:
while(firstLoopRunning) {
// Let's do this one in Java, myArray would actually be an ArrayList
try {
doSomeAction(myArray.get(0));
doSomeAction(myArray.get(1));
doSomeAction(myArray.get(2));
doSomeAction(myArray.get(3));
doSomeAction(myArray.get(4));
doSomeAction(myArray.get(5));
doSomeAction(myArray.get(6));
doSomeAction(myArray.get(7));
} catch(ArrayOutOfBoundsException ex) {
// Just a lazy way for me to avoid checking if the index exists
}
}
E voilà! Eccolo in O(n)
!
Inoltre, so per certo che i compilatori di solito trasformano i loop falso come questo:
for(i in 0..8) // not really a loop
someCall(i);
in una sequenza di chiamate.
Che mi ha portato a una prima conclusione:
A iteration over an array which length will have a known finite upper-bound is in O(1).
Penso di aver ragione su questo (aka: correggimi se sbaglio)
D'altro canto, stiamo lavorando con dati finiti e l'intero punto della teoria della complessità è di lavorare su dati teoricamente infiniti.
Quindi usiamo un altro esempio classico: stiamo iterando sui quadrati di una griglia (= ~ array bidimensionale), quindi chiaramente un algoritmo O(n²)
. E se invece dovessimo mettere tutti i quadrati in una singola lista e cambiare questo:
// O(n²) algorithm over myGrid[][]
for(i in myGrid)
for(j in myGrid[i])
yieldAction(myGrid[i][j])
in questo
// O(n) algorithm over myFlatGrid[]
for(i in myFlatGrid)
yieldAction(myFlatGrid[i mod rowLength][j])
Fondamentalmente la stessa cosa, ma diversa complessità, ma non è stato ancora ottenuto alcun ciclo. Premesso che la griglia può crescere in entrambe le dimensioni, quindi la variabile è davvero quadratica, ma in un certo senso può essere trattata in modo lineare (anche se non ne vale la pena).
Ma mi manca qualcosa. Che senso ha che se si distorcono i dati in una finestra, posso modificare la complessità di un algoritmo da un punto di vista teorico anche se viene eseguito esattamente lo stesso numero di operazioni?