Ripetizioni di segmenti di codice
• Spesso è necessario ripetere più volte uno stesso segmento
dell'algoritmo (e.g. I/O, elaborazioni dati)
• Esistono due possibilità:
– Macro
– Procedure
Le macro
• Una macro è definita come un insieme di istruzioni (o blocco)
• Queste vengono sostituite all'interno del codice
• Servono per rendere più leggibile il codice e per facilitarne la
modifica coerente
• #define MAX(a,b) (a>b ? a : b)
Le procedure
• Una procedura è definita come un insieme di istruzioni (o
blocco)
• Il controllo del flusso passa alla procedura ogni volta che viene
invocata
• E' necessario un meccanismo per ritornare al punto chiamante
• Utilizzo dello stack (Stack Pointer)
Procedure e funzioni
• La ripetizione di un segmento di codice può avere due obiettivi:
– ripetere la stessa sequenza di operazioni
– calcolare diversi valori, partendo da parametri diversi
• La differenza principale è:
– la procedura non ritorna nessun valore
– la funzione ritorna un valore (ad es void)
Passaggio dei parametri
• Ogni procedura/funzione generalmente richiede un insieme di
dati
• I dati devono essere accessibili:
– in variabili globali
– oppure sotto forma di parametri
• I parametri sono dati che vengono utilizzati dalla procedura; ad
ogni chiamata possono assumere valori diversi
Passaggio dei parametri
• I parametri possono essere passati in modi diversi:
– per valore
vengono effettuate delle copie dei valori;
le copie sono usate dalla procedura che può anche modificarne il
valore
– per riferimento
si passa il puntatore alle variabili; i valori possono essere modificati
dalla procedura
Side effects
• Se il passaggio dei parametri avviene per valore, l'effetto della
procedura è limitato al valore che ritorna
• Se i parametri sono passati per riferimento, la procedura può
avere effetti collaterali (side effects)
– è un modo per far in modo che la procedura ritorni più di un valore
Le procedure
• Annidamento:
in una procedura può essere contenuta un'altra chiamata a
procedura
• Limite sulla dimensione dello stack
Le procedure
• Ricorsione:
all'interno di una procedura avviene una chiamata alla
procedura stessa
• Limite sulla dimensione dello stack
• Molti algoritmi sono basati sulla ricorsione
La ricorsione
• Esempi:
– somma di N dati
– calcolo del fattoriale
– calcolo dei primi N numeri primi
– visita di un albero
Algoritmi di ordinamento
• Basilare in molteplici applicazioni
• Esempio importante di algoritmi diversi per risolvere lo stesso
problema
• Metodi di ordinamento interni: i dati risiedono nella memoria
centrale (in un vettore; campo chiave)
• Confronto basato sull'efficienza
Selectionsort
• Ordinamento per selezione
• Si sceglie il minimo del vettore e lo si scambia con la posizione
corretta
• Complessità: O(n2)
• I due cicli vengono sempre eseguiti, indipendentemente dalla
configurazione dei dati
Bubblesort
•
•
•
•
•
Basato sulla considerazione che:
ei  ei+1
Confronta coppie adiacenti e eventualmente le scambia
Dopo una scansione completa l'elemento minore è nella posizione
corretta
Termina quando sono terminati gli scambi (se è già ordinato serve solo
un passo per la verifica)
Complessità: O(n2)
Mergesort
• Basato sulla fusione (merge) di due vettori ordinati:
– la fusione ha complessità O(n)
• E' intrisecamente ricorsivo
• Vettore iterativamente diviso in 2 parti:
– ordinamento e fusione
• Complessità O(n log2 n)
Mergesort
• Indipendente dalla configurazione in ingersso
• La ricorsione incrementa la complessità della gestione:
– è consigliabile usare un programma non-ricorsivo
• Per ogni programma ricorsivo esiste un programma
non-ricorsivo più efficiente
Quicksort
• Si sceglie un elemento pivot e si suddivide il vettore in due: uno
con el. minori, l'altro con el. maggiori o uguali del pivot
• Si procede iterativamente fino a vettori di 1 solo elemento
• La suddivisione ha complessità O(n)
• La complessità della parte ricorsiva dipende dalla scelta del
pivot
Quicksort
• Scegliendo come pivot l'elemento min o max, la complessità
diventa O(n2)
• Scegliendo come pivot l'elemento centrale, la complessità è
O(n log2 n)
• Il caso peggiore succede raramente
• E' più probabile il caso migliore
• E' usato per la semplicità e l'efficienza
Limite inferiore della complessità
• Si considerano algoritmi la cui operazione fondamentale è il
confronto tra elementi
• Problema:
– è ricondotto alla ricerca di una specifica permutazione di n oggetti
– esistono n! permutazioni diverse
– tutte le permutazioni sono candidate
– ogni passo dell'algoritmo serve per eliminare dei candidati
Esempio
• La ricerca è rappresentabile graficamente con un albero binario:
– la radice rappresenta tutte le permutazioni
– ogni confronto suddivide le permutazioni
• La profondità dell'albero determina il numero massimo di
confronti nel caso peggiore
Esempio
• Ordinare {a1, a2, a3}
• Albero di risoluzione
– 3! = 6 permutazioni (6 foglie)
– albero con profondità 3
• Programma in C
Limite inferiore della complessità
• L'albero deve avere n! foglie
• In generale un albero binario con profondità i ha al più 2i foglie
• Quindi per avere n! foglie, l'albero deve avere profondità
p=
log(n!)
p = log(n!) ~ log(n/e)n =n log(n) - n log(e) = O(n log(n))
• Il limite inferiore alla complessità è O(n log(n))
Binsort
• Fin'ora gli algoritmi erano basati su operazioni di confronto
• Il binsort utilizza operazioni di indirizzamento con indici
• Sfrutta la conoscenza dell'intervallo di variabilità delle chiavi
Binsort
• Si suppone che gli n elementi del vettore abbiano chiavi [1..n]
• Si scandisce un vettore e si spostano gli elementi in un altro
• Ha complessità O(n)
Binsort
• Caso di più chiavi uguali:
– utilizzo di una lista
– al termine le liste vengono concatenate
• Complessità:
– inserimento O(n)
– concatenazione O(n)
– totale O(n)
Binsort
• E' il più efficiente se:
– si utilizzano chiavi numeriche
– l'intervallo di variabilità delle chiavi è noto
– è possibile effettuare indirizzamenti con indici
Considerazioni
sulla scelta di un algoritmo
• Le caratteristiche di un algoritmo sono:
– semplice, per facilitarne la comprensione, programmazione, e
correzione
– efficiente, cioè richiede una quantità limitata di risorse per
l'esecuzione
• Le due caratteristiche si riferiscono a:
– costo umano
– costo di esecuzione
Considerazioni
sulla scelta di un algoritmo
• Non esistono regole per la scelta ottima
• Generalmente però:
– si sceglie la prima regola quando si deve eseguire poche volte su
insiemi ridotti di dati
– si sceglie la seconda se il programma viene eseguito un grande
numero di volte su insiemi estesi di dati
Considerazioni
• Implementazione efficiente dell'algoritmo
– sono state considerate solo le complessità
– sono state eliminate le costanti moltiplicative
• Per scegliere l'implementazione migliore è necessario
considerare tutto
– ad esempio, gli algoritmi ricorsivi sono generalmente molto pesanti