divide: un problema di dimensione viene ripartito in a sottoproblemi, ciascuno di dimensione , tra loro indipendenti (↔ ricorsione su ogni sottoproblema);
impera: quando si arriva alla soluzione elementare, avviene la risoluzione diretta (↔ condizione di terminazione);
combina: le soluzioni parziali (elementari) vengono ricombinate per ottenere la soluzione del problema di partenza.
L'equazione alle ricorrenze permette di analizzare la complessità di ogni passo del procedimento divide et impera:
divide: (costo della divisione)
impera: (costo della soluzione elementare)
combina: (costo della ricombinazione)
= numero di sottoproblemi che risulta dalla fase di divide
= dimensione di ciascun sottoproblema
Eseguire l'unfolding significa sviluppare alcuni passi di un'equazione alle ricorrenze per trovare una forma analitica chiusa della ricorrenza stessa sotto forma di sommatoria. Serve per analizzare la complessità di una funzione ricorsiva.
Ho k = 3 pioli verticali, con n = 3 dischi forati in ordine decrescente di diametro sul primo piolo. Si vuole portarli tutti e 3 sul terzo piolo. Condizioni per lo spostamento:
si può spostare un solo piolo per volta;
sopra ogni disco solo dischi più piccoli.
Il problema si suddivide in 3 sottoproblemi:
problema da n − 1: 000 → 011 (2 deposito) → si suddivide a sua volta in 3 sottoproblemi elementari
problema da 1: 011 → 211 → sottoproblema elementare
problema da n − 1: 211 → 222 (0 deposito) → si suddivide a sua volta in 3 sottoproblemi elementari
Disegnare le tacche di un righello in maniera ricorsiva, di differenti altezze, fino a quando si arriva all'altezza 0. Si disegna ricorsivamente a metà del sottointervallo la tacca, quindi si considerano i due sottointervalli sinistro e destro e si disegna la tacca di una unità di altezza inferiore.
Backtrack: Si esplora una scelta per volta, e se la scelta non va bene si ritorna indietro. (vd. filo di Arianna) Si termina quando tutte le scelte sono esaurite. Tramite il backtrack si può esplorare tutto lo spazio delle soluzioni.
divide: si suddivide il vettore in due sottovettori
ricorsione:
si applica il merge sort sul sottovettore sinistro
si applica il merge sort sul sottovettore destro
condizione di terminazione: il sottovettore ha 1 cella
combina: si uniscono tutti i sottovettori ordinati, scandendo in parallelo i due sottovettori da sinistra verso destra e confrontando di volta in volta i valori scegliendo quello minore:
È quadratico () → secondo la teoria degli algoritmi non dovrebbe essere ottimo, però: sperimentalmente si dimostra che il caso peggiore ricorre molto raramente, a differenza del caso medio.
Partizione: La divisione non avviene a metà, ma secondo una certa proprietà: sceglie arbitrariamente un elemento separatore (= pivot), quindi separa gli altri valori in sottovettore sinistro e destro a seconda se sono minori o maggiori del pivot.
Individua (tramite un ciclo ascendente su i e un ciclo discendente su j) una coppia di elementi di indici i e j che siano entrambi fuori posto (ovvero l'i-esimo è maggiore del pivot, e il j-esimo è minore del pivot), quindi li scambia.
Condizione di terminazione: i == j (si ferma quando tutte le coppie sono già a posto).
Costo della partizione: (cioè la scansione di tutti gli n elementi).
Al termine della ricorsione è inutile una fase di ricombinazione, perché i dati si trovano già nella posizione corretta.
Il quicksort non segue la formula generale del divide et impera.
Caso peggiore
Il vettore si trova in ordine inverso → la partizione genera un sottovettore da n ‒ 1 elementi e l'altro da 1 elemento.
Equazione alle ricorrenze:
= costo per il sottovettore da 1 elemento
= costo per il sottovettore da n ‒ 1 elementi
= costo della partizione
= costo unitario della risoluzione (riguarda gli elementi al termine della ricorsione)
Unfolding:
Caso migliore
Ogni sottovettore è esattamente la metà → ricorda il merge sort (in questo caso è linearitmico).
Caso medio
Partizione fortemente sbilanciata, senza arrivare al caso peggiore → il quicksort è linearitmico.
È meglio cercare un pivot in modo da dividere sempre in maniera conveniente il vettore → la complessità non cambia (neanche prendendo un pivot che separa sempre il vettore a metà), ma si possono ridurre le costanti.