You are on page 1of 128

A.

Laurentini-Analisi degli algoritmi

Dean Karnopp, Donald L. Margolis, Ronald C.


Rosenberg
System dynamics: modeling and simulation of
mechatronic systems

Lezioni: Aldo Laurentini, Dip. Automatica ed Informatica


aldo.laurentini@polito.it

Esercitazioni: Alberto Tonda (in aula Giovedì 8.30-10)


alberto.tonda@polito.it

Testi
Teoria: Dispense di A.Laurentini, reperibili sul sito del corso
Testo Supplementare :Introduzione agli algoritmi e strutture dati, di T.
Cormen, C. Leiserson, R. Rivest, C. Stein, Edizioni McGraw Hill. .
Programmazione C: A.Bottino, A.Cappadonia, Introduzione alla
programmazione in C , CLUT

Esame: Prova di Teoria + Prova di Programmazione . Voto : media dei


due voti

1
A.Laurentini-Analisi degli algoritmi

ANALISI DEGLI ALGORITMI


1 - INTRODUZIONE

• Algoritmo:= Insieme di regole per ottenere dati di OUTPUT da dati di INPUT.

I O
A
L’algoritmo è una soluzione ad un problema. Per un problema ci possono essere più
algoritmi. L’algoritmo ideale, o astratto è un insieme di regole, usualmente espresse
in forma matematica. Ogni algoritmo può a sua volta avere differenti realizzazioni
(diversi linguaggi di programmazione, etc.)

Problema

Alg.1 Alg.2 ………….. Alg.n

Real. 1 Real. 2

Esempio:

2
A.Laurentini-Analisi degli algoritmi

Problema: dato n, è primo?

…………..
Alg.2: riduci le divisioni: se hai già
Alg.1: dividi n per
2, 3, 4…… n/2 diviso per 2 e 3, non dividere per
6…..(Crivello di Eratostene)

Si noti che sia in ingresso che in uscita un algoritmo può avere pochi o molti dati.

n Verifica se si/no
n è primo

Topologia instradamento
rete,…….. Calcola l’instradamento
ottimo su Internet di un
pacchetto

• Descrizione di algoritmi.
- Informali (con parole, schemi, formule….)
- Pseudo-codice: lo scheletro di un programma. Contiene: istruzioni di controllo;
istruzioni imperative; descrizioni di operazioni ad alto livello. Non contiene
dettagli ovvi (dichiarative,etc.). Può essere simile ad un PASCAL semplificato, o
ad altri linguaggi (C) semplificati

• Computability theory (CT)


Studio teorico degli algoritmi, iniziato negli anni ’30, prima dei calcolatori. Uno
strumento è un calcolatore ideale, detto MACCHINA DI TOURING (Alan Touring).
La macchina ha una memoria infinita, costituita da caselle adiacenti. Ogni casella
contiene un simbolo. Una testa di lettura /scrittura lo legge, ed, in base ad uno stato
precedente:
-produce un nuovo stato
-scrive un nuovo simbolo nella casella
-si sposta su un’altra casella

3
A.Laurentini-Analisi degli algoritmi

Memoria

Testa di lettura/scrittura

La CT si è occupata principalmente di comprendere quali problemi possono essere


risolti in modo algoritmico.
Ad es., un risultato è la non resolubilità dell’Halting Problem ( Esiste un algoritmo
generale(o programma) che, dati: a)un algoritmo A; b) dei dati di input ; è in grado di
dire se l’algoritmo A si fermerà in un numero finito di passi?)

Conoscere l’esistenza teorica di un algoritmo per risolvere un problema non è


sufficiente a dirci come fare in pratica. Per esempio, è facile dare un algoritmo
(scrivere un programma) per giocare in modo ottimo a filetto, che esamina tutte le
possibilità. E’ possibile teoricamente costruire allo stesso modo un programma ottimo
per giocare a scacchi. In pratica la cosa non è fattibile (si stima che ci siano 1050
diverse combinazioni di pezzi sulla scacchiera!)

E’ quindi importante avere algoritmi praticamente realizzabili, sia per quanto


riguarda il tempo di elaborazione che lo spazio (memoria) occupata. Di questi
problemi si occupa la branca della computer science detta computational complexity.

• Esempi di problemi comuni trattati nella teoria degli algoritmi


- calcolare se un numero è primo (importante nella teoria dei codici segreti, basati su
numeri prodotto di primi molto grandi)
- moltiplicare due matrici (operazione comunissima, ad es. nella computer graphics)
- trovare il percorso più breve tra due nodi di un grafo pesato (modella numerosi
problemi pratici)
- dato un grafo, trovare il percorso (se c’è) che mi fa passare da tutti i lati una volta
sola ( modella numerosi problemi pratici, dall’ispezione di reti tecnologiche alla
raccolta rifiuti)
- ordinare una lista di nomi o numeri (problema comunissimo, dalle applicazioni
gestionali alla computer graphics)
- dati : un insieme di lavorazioni da effettuare, un ordinamento parziale, un insieme di
centri di lavoro, trovare lo scheduling ottimo (si pensi alla costruzione di un’auto)
- dato un volume (contenitore), ed un insieme di forme 3D, ciascuna con un
coefficiente di merito, inserire le forme 3D nel contenitore in modo da massimizzare
la somma dei coefficienti di merito

4
A.Laurentini-Analisi degli algoritmi

- data la pianta polygonale di un ambiente, qual’ è il numero minimo di telecamere di


sorveglianza richieste per coprire l’ambiente e dove devo installarle? (Il problema
detto ART GALLERY Problem). Si può estendere in 3D.

• Un esempio competo di problema e di algoritmo: L’accoppiamento stabile


(Stable Matching Problem)
Problema: Sono dati due insiemi M=(m1, m2, ……mn) e W= (w1, w2, …..wn) di
uomini e donne. Ciascun uomo ha una lista di preferenze per le donne, e viceversa.
Vogliamo trovare un accoppiamento stabile (se esiste), cioè tale che:
- l’accoppiamento sia perfetto (ogni uomo ed ogni donna sono sistemati in una ed
una sola coppia)
- non ci siano instabilità. Un’instabilità si ha se m↔w e m’↔w’, ma m preferisce w’
al suo partner corrente w e w’ preferisce m al suo partner corrente m’.
Evidentemente in tal caso due coppie si disfano e se ne riforma una
sola.

m m'

w w'

Algoritmo di Gale-Shapley per determinare un accoppiamento stabile.

Descrizione informale:
All’inizio tutti sono disaccoppiati. Ad un certo punto, un maschio m disaccoppiato si
propone alla donna w disaccoppiata che è in testa alle sue preferenze. La coppia viene
formata come provvisoria (un uomo m’ che è più in alto nella lista di w potrebbe
proporsi).
Successivamente, si saranno formate varie coppie provvisorie. Un uomo libero
qualunque si propone alla donna w più in alto nella sua lista. Se w è libera, si forma
una coppia provvisoria. Se w è accoppiata con m’, verifica chi è più in alto nella sua
lista, e si accoppia con lui, lasciando l’altro libero.
L’algoritmo termina quando non c’è più nessuno libero.

Descrizione in pseudocodice

function stableMatching {
Initialize all m M and w W to free
while free man m who still has a woman w to propose to {
w = m's highest ranked such woman
5
A.Laurentini-Analisi degli algoritmi

if w is free
(m, w) become engaged
else some pair (m', w) already exists
if w prefers m to m'
(m, w) become engaged
m' becomes free
else
(m', w) remain engaged
}
}

( http://en.wikipedia.org/wiki/Stable_marriage_problem
http://www.dcs.gla.ac.uk/research/algorithms/stable/)
Si dimostra che:
- L’algoritmo termina dopo al massimo n2 esecuzioni del loop while.
- L’accoppiamento finale è stabile
- Qualunque esecuzione dell’algoritmo da lo stesso accoppiamento (l’ordine degli
insiemi non ha importanza)
- L’algoritmo non trova tutti gli accoppiamenti stabili: ne esistono altri. Uno si
ottiene con le donne che si propongono!
- L’algoritmo come descritto non è simmetrico e favorisce gli uomini!! Diciamo che
una donna w è un partner valido di un uomo m se esiste un accoppiamento stabile
con la coppia (m,w ). Diciamo che w è il miglior partner valido di m se nessuna
donna più alta nella lista delle preferenze di m è un partner valido. L’algoritmo dà
agli uomini il miglior partner valido, ed alle donne il peggior partner valido!!

L’algoritmo si applica a numerosi problemi pratici

2 – ANALISI DEI PROBLEMI E DEGLI ALGORITMI

Gli scopi sono: dato un problema, scegliere tra diversi algoritmi, migliorarli,
comprendere che risorse di calcolo richiedono (tempo, spazio).
Un algoritmo può essere valutato secondo vari criteri:
- correttezza
- lavoro fatto(tempo impiegato)
- spazio(memoria)usata
- semplicità, chiarezza
- ottimalità
- …
-

2.1 - CORRETTEZZA

Due possibili significati:

6
A.Laurentini-Analisi degli algoritmi

a) correttezza dell’algoritmo ideale


b) aderenza all’algoritmo ideale della sua realizzazione(programma)

Per a) non ci sono regole generali. L’idea può essere intuitiva, o richiedere
dimostrazioni più o meno complesse.

Per b) esistono tecniche empiriche, informali (ad es. i casi prova), e tecniche formali.
Ad es., la tecnica dei loop invariants permette di verificare la correttezza di pezzi di
programma. Non sono tecniche pratiche: il controllo è molto complesso anche per
programmi semplici.

2.2 - LAVORO FATTO (TEMPO IMPIEGATO)

E’ ovviamente importantissimo. Molti programmi sono totalmente inutili se


impiegano più di un certo tempo (controllo di processi, previsioni meteo,….).
Il tempo di esecuzione di un programma dipende da molti complessi fattori :
- algoritmo ideale
- sua implementazione
- software usato (linguaggio di programmazione, compilatore, sistema operativo….)
- hardware (memoria, unità aritmetica,….)

Non è in pratica possibile tenere conto di tutto a priori.


Si cerca allora una misura del lavoro fatto che:
- sia semplice e generale
- dipenda solo dall’algoritmo ideale

Il tempo effettivo di elaborazione sarà circa proporzionale a questa misura. La


costante di proporzionalità potrà essere misurata su di un certo sistema di
elaborazione.

Sono necessarie varie approssimazioni. Un programma è fatto di:


- istruzioni di inizializzazioni
- uno o più loop

Si trascurano le inizializzazioni, si considerano solo i loop.


Si può:
- contare quante volte si esegue il loop più interno
oppure
- individuare un’operazione fondamentale e contare quante volte si esegue.

Le due scelte sono in pratica coincidenti.

7
A.Laurentini-Analisi degli algoritmi

Esempi

Problema Operazione fondamentale


-Trovare x in una lista di elementi xi -Confronto tra x e xi
-moltiplicare due matrici -prodotto di due numeri
-ordinare una lista -confronto tra due elementi della lista
-attraversare un albero binario -attraversare un lato
-trovare se un numero è primo -divisione

Vantaggi di una scelta corretta dell’operazione fondamentale (OF):


- l’OF potrebbe essere la più onerosa (es. op. floating point in confronto a op. fixed
point)
- il numero complessivo di operazioni è circa proporzionale al numero di OF
- contare OF risponde alla domanda: come cresce il lavoro fatto con il crescere
dell’imput
- è una scelta flessibile, può essere più o meno dettagliata

La scelta dell’operazione fondamentale determina una classe di algoritmi.

Definiamo:

COMPLESSITA’=LAVORO FATTO=N0 OPERAZIONI FONDAMENTALI

2.2.1 - CASO MEDIO E CASO PESSIMO

Il lavoro fatto W dipende da: a) algoritmo b) INPUT. Evidentemente:

- ordinare 100 nomi è più lungo che ordinarne 10 (richiede più OF)
- cercare un elemento in una lista di 100 nomi è più lungo che in una lista di 10
- etc.

A parità d’INPUT, dipende dal tipo d’INPUT (ad es., se una lista è già ordinata…)

Esempi di dimensione dell’INPUT

Problema Dim. INPUT


- Trovare x in una lista - n0 elementi n della lista
- Moltiplicare due matrici - dim. Matrici
- ordinare una lista - n0 elementi n della lista
- traversare albero binario - n0 nodi
- risolvere sist. di equaz. lineari - n0 equazioni
- risolvere un problema relativo ad un grafo - n0 nodi; n0 lati; ambedue
8
A.Laurentini-Analisi degli algoritmi

• Complessità nel caso pessimo (worst case)

W(n):=Max{w(I)|I∈Dn}

w(I):= num. OF per un INPUT I


Dn := insieme degli INPUT di dimensione n

N.B.
- la complessita’ nel caso pessimo è utile o indispensabile in molti casi pratici (es.
applicazioni REAL TIME)
- è spesso facile da calcolare
- senza altre precisazioni, con complessità si intende sempre complessità nel caso
pessimo

• Complessità nel caso medio (average behavior)

A(n) = ∑ p( I )w( I )
I ∈Dn

Richiede per tutti gli I:


-calcolo di w(I)
-conoscenza delle probabilità p(I)

Esempio 1

- Problema: ricerca di x in una lista di n elementi


- Algoritmo: scansione della lista dall’inizio
- OF: confronto (=, ≠ )

Complessità nel caso pessimo

Il caso pessimo si ha se : a) x e’ l’ultimo; b) non c’è.


W(n)=n

Complessità nel caso medio

Occorre formulare delle ipotesi:

a) Ipotesi: x presente nella lista(una sola volta), posizioni equiprobabili.

9
A.Laurentini-Analisi degli algoritmi

w( I ) = i
n
1 1 n(n + 1) n + 1 n
n
A(n) = ∑ p ( I i )w( I i ) = ∑ i = = ≅
i =1 i =1 n n 2 2 2
b) Altra ipotesi: x presente nella lista una sola volta con probabilità q; se presente,
posizioni equiprobabili
n
q q n(n + 1) q(n + 1)
A(n) = ∑ i + (1 − q)n = + (1 − q)n = + (1 − q)n
i =1 n n 2 2
n +1
q = 1 ⇒ A(n) =
2
1 n +1 n 3
q = ⇒ A(n) = + ≅ n
2 4 2 4
N.B.

- Il worst case non dipende dal particolare algoritmo di scansione (e’ lo stesso
qualunque ordine di scansione si segua)

- l’analisi del caso medio può complicarsi ulteriormente (ad es., x può essere presente
più volte……)

Esempio 2

- Problema: prodotto di due matrici quadrate AB=C


n

- Algoritmo:
cij = ∑ aik bkj
k =1
- OF= moltiplicazione

Complessità nel caso pessimo=complessita’ nel caso medio


W(n)=A(n)=n3

2.3- SPAZIO USATO (MEMORIA)

La teoria è analoga a quella del lavoro fatto. Lo spazio usato si può definire, in
funzione dell’INPUT, sia nel caso pessimo che nel caso medio.

Nell’analisi si trascura:

10
A.Laurentini-Analisi degli algoritmi

- lo spazio usato dal programma


- lo spazio usato dai dati di INPUT
Si considera solo lo spazio aggiuntivo (extra space). Se l’algoritmo non ne richiede,
si dice che funziona in space.

2.4 - SEMPLICITÀ

Caratteristica importante: permette una scrittura più agevole del programma, debug e
modifiche più semplici. Tuttavia è difficile da quantificare.

2.5 - OTTIMALITÀ

Ogni problema ha una sua complessità intrinseca, cioè un lavoro minimo al di sotto
del quale non si può andare. Il confronto tra algoritmi va fatto a parità di condizioni,
quindi entro classi in cui è ammessa la stessa operazione fondamentale.

2.5.1- CASO PESSIMO(WORST CASE)

• Definizione: Un algoritmo è ottimo (nel worst case) se, nella sua classe di algoritmi
non esiste un altro algoritmo che (nel worst case) faccia meno OF.

• Come dimostrare che un algoritmo A è ottimo (in una classe)?


1) Si trova un LOWER BOUND F(n), lavoro minimo (minimo n0 di OF) che
debbono fare tutti gli algoritmi della classe)
2) Si trova la complessità WA(n) di A e si confronta con il LOWER BOUND:

- se F(n) =WA(n), A e ottimo


F(n) WA(n)

- se F(n) <WA(n), non si può dire niente. Potrebbe esistere un LOWER BOUND
F’(n)> F(n).
WA(n)

F(n)

11
A.Laurentini-Analisi degli algoritmi

Esempio 1 di analisi ottimalità nel caso pessimo

- Problema: trovare il max di una lista


- Classe algoritmi: che usano il confronto a tre vie tra due elementi(<=>)
- Algoritmo: confronto il primo col secondo, il maggiore con il terzo, il maggiore
con il quarto….

Max:=L[1]
For index:=2 to n do
If Max<L[index] then
Max:=L[index]
end{if}
end{for}

(pseudo-codice simile al PASCAL)

- Lavoro fatto dall’algoritmo nel caso pessimo: W(n)=n-1 confronti (è uguale anche
ad A(n))
- LOWER BOUND- Se un elemento è max, n-1 elementi non sono max. Per
decidere che uno di questi non è max, ci vuole almeno un confronto. In totale,
almeno F(n)= n-1 confronti.

Conclusione: W(n)= F(n) ⇒ L’algoritmo è ottimo.

Esempio 2 di analisi ottimalità nel caso pessimo


- Problema: prodotto di due matrici quadrate AB=C
- Classe di algoritmi: quelli che ammettono le 4 operazioni aritmetiche
- OF:= moltiplicazione
n

- Algoritmo: applica la definizione: cij = ∑ aik bkj


k =1

Complessità dall’algoritmo: W(n)=n3 ( numero addizioni: (n-1) n2≅ n3 )


LOWER BOUND: n2 (si omette la dimostrazione)

Conclusione: non possiamo dire se l’algoritmo sia o no ottimo.

N.B.
L’algoritmo non è ottimo: sono stati trovati altri algoritmi migliori nella stessa classe.
Es:
- Alg. di Winograd (1968) W(n)=(n3/2) + n2
- Alg. di Strassen(1969) W(n)=n2.81
- Alg. di Coppersmith e Winograd W(n)=n2.376
12
A.Laurentini-Analisi degli algoritmi

Non sappiamo però se quest’ultimo algoritmo è ottimo (non sono stati trovati altri
LOWER BOUNDS)

2.5.2 - OTTIMALITÀ NEL CASO MEDIO

Approccio identico a quello del caso pessimo.

Ricordiamo ancora che, quando non si dice esplicitamente, si tratta di ottimalità nel
caso pessimo.

2.6 - OTTIMALITÀ NELL’USO DELLO SPAZIO

Approccio identico a quello del lavoro fatto.

13
A.Laurentini-Analisi degli algoritmi

3. CLASSIFICAZIONE ASINTOTICA

• Motivazioni

Si supponga di avere due algoritmi:

- Alg. A’ , complessità W’(n)=2n


- Alg. A” , complessità W”(n)=5n

Qual è il migliore? I tempi di esecuzione saranno T’≅ c’2n, T”≅ c”5n. Non sappiamo
dirlo.
E’ ragionevole allora classificarli in una stessa classe di complessità(asintotica).
Possiamo anche dire che sono dello stesso ordine di complessita’.

Supponiamo invece:

- Alg. A’ , complessità W’(n)=c’n3


- Alg. A” , complessità W”(n)=c”n2 c’<c”

Per n piccoli, A’ è migliore, ma per n>n0=c”/c’, A” è sempre migliore.


Inoltre:
W ' ( n)
lim =∞
n →∞ W " ( n)
Dobbiamo quindi considerare A’ ed A” in classi di complessità asintotiche diverse.

c’n3

c”n2

n0=c”/c’

14
A.Laurentini-Analisi degli algoritmi

3.1-NOTAZIONI Ο, Ω, Θ, ο, ω

• Notazione O( o grande, big ho)

Siano f(n), g(n) funzioni di interi positivi n.

Definizione: O(f(n))= O(f):= insieme delle funzioni g tali che esiste una costante
positiva c ed un n0 tali che g(n)≤ cf(n) per n≥ n0 .

- Equivale a dire che: g è asintoticamente limitata da un multiplo di f


g
- Una definizione equivalente è : g∈ O(f) se lim = c con c costante reale (anche 0).
n →∞ f
- Correntemente si dice che O(f) è l’insieme delle funzioni che crescono al massimo
come f. Se g appartiene a questo insieme, si dice che è O(f).

Esempio: c, n, n/1000, n2/2 sono tutte O(n2).

N.B. Se lim f = lim g = ∞ , si può usare il teorema di L’Hopital:


n →∞ n →∞

f f'
lim = lim se f’ e g’ esistono
n →∞ g n → ∞ g'

Esempio 1
f=n3/3 g=37 n2+120n+17
si ha
g∈ O(f) (ma f∉ O(g)
Infatti
a) per n≥78 g< f
oppure
g 37n 2 + 120n + 17 74 240 34
b) lim = lim 3
= lim( + 2 + 3 ) = 0
n →∞ f n →∞ n n→ ∞ n n n
2
Esempio 2

f= n2 g=nlgn

15
A.Laurentini-Analisi degli algoritmi

lg e
g n lg n lg n n =0
lim = lim 2 = lim = L → = lim
' Hopital
n →∞ f n →∞ n n →∞ n n→∞ 1

da cui
g∈ O(f) ; f∉ O(g)

• Notazione Ω

Ω(f):= insieme delle funzioni g tali che esiste un c ed un n0 tali che g(n)≥cf(n) per
n ≥n0
oppure
g g
g∈ Ω (f) se lim = c ≠0 oppure lim =∞
n →∞ f n →∞ f
E’ l’insieme delle funzioni che crescono almeno quanto f.

• Notazione Θ

Θ(f):=O(f)∩Ω(f)
oppure
g
g∈ Θ (f) se lim = c ≠0
n →∞ f
E’ l’insieme delle funzioni che crescono come f.

• Notazione o

o(f):= O(f)-Θ(f)
E’ l’insieme delle funzioni che crescono meno di f.

• Notazione ω

ω(f):= Ω(f)-Θ(f)
E’ l’insieme delle funzioni che crescono più di f.

Ω(f) ω(f)

Θ(f)

16
A.Laurentini-Analisi degli algoritmi

O(f) o(f)

Esempi di complessità’ asintotica di algoritmi

- Ricerca di un elemento in una lista Θ(n)


- Ricerca del max. in una lista Θ(n)
- Prodotto di matrici (algoritmo normale) Θ(n3)

3.2 - PROPRIETÀ DI Ο, Ω, Θ, ο, ω

- Transitività: se f∈O(g) e g∈O(h) , ⇒ f∈O(h)


- f∈O(g) se e solo se g∈Ω(f)
- se f∈Θ (g), allora g∈Θ(f)
- Θ stabilisce un’equivalenza tra funzioni. Ogni insieme Θ e’ una classe di
equivalenza che chiamiamo classe di complessità(asintotica)
- O(f+g)=O(Max(f,g)) ; la proprietà vale anche per Θ e Ω ed è utile per analizzare
algoritmi fatti da più parti

Θ(10n3+3n3+5nlgn+57)= Θ( n3)=……………….

Per descrivere questa classe di complessità usiamo la funzione più semplice della
classe: n3.
Se f∈Θ (n) diciamo che f e’ lineare, se f∈Θ (n2) diciamo che f e’ quadratica, etc.
O(1) indica funzioni limitate da una costante.

N.B. Si dimostra che:


1) lnn∈ο(nα) per α>0. Il logaritmo cresce meno di qualunque potenza (anche di
n1/k)
2) nk∈ο(cn) per k>0 , c>1. Qualunque potenza di n cresce meno
dell’esponenziale

N.B. Talvolta, invece di ∈ si usa =. Ad es., si scrive f=Θ (n)

17
A.Laurentini-Analisi degli algoritmi

4 - RICHIAMI MATEMATICI

• Notazioni “floor” x e “ceiling” x . Floor di x è il maggior intero che non supera
x, ceiling di x è il minor intero maggiore o uguale di x.

Es. 2  = 2.5 =  2.99 = 2 ; 3  = 2.5 =  2.1 = 3

• Logaritmi. Sono largamente usati nell’analisi degli algoritmi, in particolare quelli


in base 2
- logbba=a
- funzione strettamente crescente e univoca
- logb x1 x2 =logb x1 + logb x2
- logb x1/ x2 =logb x1 - logb x2
- logb1=0
- logbxa=alogbx
- conversione di basi :logb x =logc x logb c
- N.B. Nell’analisi degli algoritmi spesso non si considerano le costanti
moltiplicative, quindi i logaritmi in basi diverse sono equivalenti
- notazioni usuali: log2x = lg x; logex = ln x
- lg e ≅ 1.44; ln 2 ≅ 0.7; lg 10 ≅ 3.32
- n ≤ 2lgn < 2n
- n/2 < 2 lgn ≤ n
- d(lnx)/dx =1/x ⇒ d(lgx)/dx =lge /x

• Fattoriali
Ovviamente n!<nn . Una approssimazione piu’ stretta e’ data dalla formula di Stirling:
n
n   1 
n!= 2πn   1 + Θ  
e   n 
da cui si ottiene:
n!∈o(nn)
n!∈ω(2n)
lg(n!) ∈ Θ(n lgn)

18
A.Laurentini-Analisi degli algoritmi

ed anche:

 1 
n n+ 
n n  12 n 
2πn   ≤ n!≤ 2πn  
e e

• Sommatorie

- Interi consecutivi:
n
n(n + 1)
∑i =
i =1 2

- Quadrati consecutivi:
n
2n3 + 3n 2 + n n3
∑i = 2

6

3
i =1

- Potenze di 2:
k

∑2
i =0
i
= 2 k +1 − 1

- Somme geometriche
k
1 1 a k +1 − 1 k
∑ i
= 2− k ∑a = a −1 i

i =0 2 2 i =0

• Approssimazione di somme mediante integrali

- Funzioni decrescenti

b +1 b
f ( x) dx ≤ ∑ f (i ) ≤ ∫
b
∫a
i=a
a −1
f ( x )dx

a b b+1 a-1 a b

- Funzioni crescenti
b b b +1
∫ f ( x) dx ≤ ∑ f (i ) ≤ ∫ f ( x) dx
a −1 a
i=a
Esempio 1
Funzione decrescente.

19
A.Laurentini-Analisi degli algoritmi

n
1 n dx
∑ ≤ ∫ = [ln x ]1 = ln n − ln 1 = ln n
n

i =2 i
1 x

ed anche:
n
1 n +1 dx
∑ = [ln x ]2 = ln(n + 1) − ln 2
n +1

i =2 i
≥ ∫2 x
da cui
n n
1 1

i=2 i
≅ ln n ⇒ ∑
i =1 i
≅ 1 + ln n

Esempio 2

Funzione crescente.
n n n

lg xdx = ∫ lg e ln xdx = lg e[x ln x − x ] = lg e( n ln n − n + 1)


n n
∑ lg i = ∑ lg i ≥ ∫
i =1 i=2
1 1
1

= n lg n − n lg e + lg e ≥ n lg n − 1.44n

20
A.Laurentini-Analisi degli algoritmi

5 - STRUTTURE DATI ELEMENTARI

• Abstract data type(ADT)

Idea generale per svincolare gli algoritmi dai dati.

ADT:= insieme di elementi di informazione con operatori associati.

Es. : numeri interi + operazioni aritmetiche


Matrici +somme, prodotti, inversioni,trasposizioni

Utilità degli ADT. Se sono già forniti gli operatori necessari per gestire i dati, devo
occuparmi solo dell’algoritmo. Di conseguenza:
- il codice è più compatto
- il codice è portabile
- posso cambiare le routine che gestiscono i dati senza cambiare l’algoritmo

5.1-
5.1 LISTE

LISTA: Insieme ordinato di elementi dello stesso tipo (detti talora record):
a1, a2, …………..an,
- n=0, lista vuota
- a1 := testa (head) della lista
- an := coda (tail)
- esiste una posizione data dall’indice:
ai precede ai+1 ∀i, 1≤i<n
e segue ai-1 ∀i, 1<i≤n
- può essere utile avere eol(L), posizione (o indice ) successivo all’ultimo elemento

• Operazioni più comuni sulle liste

- insert(x, p, L): inserisci l’elemento x in posizione p nella lista L


- delete(x,L): cancella l’elemento x dalla lista L
- delete(p,L): cancella l’elemento in posizione p dalla lista L
- locate(x,p,L): se x∈L, ritorna la sua posizione, altrimenti eol(L)
- retrieve(x,p,L): ritorna il valore x dell’elemento in posizione p della lista L

21
A.Laurentini-Analisi degli algoritmi

- etc. (inserisci, togli, in fondo o in testa)

• Realizzazione delle liste

- Mediante vettori: gli elementi sono memorizzati in locazioni contigue. E’ la cosa


più semplice per elementi di lunghezza uguale. Necessari puntatori al primo ed
ultimo elemento (o lunghezza lista). Varie operazioni sono O(n) (insert, delete)
per la necessità di spostare gli elementi successivi. O(1) se si inserisce o toglie
all’inizio o alla fine. Per elementi di lunghezza eguale (caso più comune), il
posizionamento su di un elemento della lista dato il suo indice è O(1).

- Mediante puntatori (liste concatenate). Ogni elemento d’informazione ha un


campo aggiuntivo, un puntatore all’elemento successivo. E’ necessario avere un
puntatore al primo elemento della lista (header).

NIL

Inserzione:

Cancellazione:

Sono O(1)(non bisogna spostare gli elementi successivi) se sono già posizionato nel
punto giusto. Posizionarsi è (senza altri ausili) O(n), quindi Locate e Retrieve sono
O(n).
L’uso dello spazio disponibile è efficiente, a parte l’overhead dei puntatori.
Di solito si usano elementi aggiuntivi

• Altri tipi di liste concatenate:

Liste doppiamente concatenate.

22
A.Laurentini-Analisi degli algoritmi

Liste circolari.

Liste multiple (ogni elemento può avere più puntatori). Esempio:

• STACK (PILA, LIFO: Last In First Out)

E’ una lista particolare. Le operazioni di inserimento(push) e cancellazione(pop) si


svolgono solo ad una estremità della lista (TOS: Top Of Stack). Può essere realizzato
con vettori o liste concatenate.
Nelle CPU particolari istruzioni gestiscono lo STACK come un vettore. Un registro
(Stack Pointer) contiene l’indirizzo di TOS.

• QUEUE (CODA, FIFO: First In First Out)

E’ un particolare tipo di lista. Si può inserire solo da una parte, e prelevare dall’altra.
23
A.Laurentini-Analisi degli algoritmi

Può essere realizzata mediante puntatori o vettori. Nel caso di un vettore, questo è
considerato richiuso su se stesso (buffer circolare). Sono necessari due puntatori.

5.2– ALBERI

Sono un particolare tipo di grafi (si veda più avanti), precisamente grafi aciclici
(senza cammini chiusi).

5.2.1 – ALBERI BINARI

• Costituiti da elementi detti nodi e da collegamenti tra nodi detti lati.

Albero binario:=
Radice
L R 1) vuoto
oppure
2) nodo radice più due lati L(left) e
Albero Albero R(right) collegati a due alberi
binario binario binari. La radice è detta anche
Albero nodo padre (parent) e le radici dei
binario sotto-aberi di sinistra e destra nodi
figli (child)
• Definizioni
-grado di un nodo: numero di sotto-alberi non vuoti collegati (0, 1, 2)
-foglia: nodo di grado 0
-nodo interno: ha grado ≠0
-livello di un nodo: è 0 quello della radice, è quello del padre +1 per gli altri
-albero completo: tutti i nodi interni hanno grado 2, tutte le foglie sono al max. livello
-profondità p: massimo livello
N.B. Su alcuni testi, livello della radice =1 (tutti i livelli aumentano di 1)

Esempi.
Liv.0 A A

Liv.1 B C B C

Nodi interni D E F G
D E F 24

Albero completo
A.Laurentini-Analisi degli algoritmi

Liv.2

Liv.3

Liv.4
• Le informazioni sono di solito collegate ai nodi. Qualche volta vi sono informazioni
collegate anche ai lati.

• L’albero binario come ADT. Sono possibili numerose operazioni associate. Ad es.,
dato un nodo: trovare il padre, trovare il figlio sinistro ed il figlio destro….

• Rappresentazioni

Ne sono possibili diverse:


-liste multiple

-vettori.
Particolarmente semplice e compatta se l’albero binario è completo: si può
memorizzare per livelli, da sinistra a destra. Ogni nodo ha una posizione (indice)
predefinita, non sono necessari puntatori.
E’ facile vedere che: k
- la radice ha indice 1
- i figli di un nodo con indice k hanno indici 2k (L) e 2k+1 (R).
- il nodo con indice k si trova al livello lgk 2k 2k+1
l l+1
- i nodi del livello l hanno indici da 2 a 2 –1
-…..
Queste proprietà rendono semplici molte operazioni, come la ricerca del padre o dei
figli.

• Proprietà
- al livello l ci sono al massimo 2l nodi

25
A.Laurentini-Analisi degli algoritmi

- n0 nodi n ≤ 2p+1 –1 (= per albero completo)


- p ≥ lg(n+1)-1⇒ p ≥ lgn

• Visita dell’albero(attraversamento, traversal)


Esame di tutti i nodi in un certo ordine.
Esistono tre tipi di ordinamento e quindi di attraversamento. v è la radice dell’albero
o sotto-albero da visitare.

Definizioni recursive dei tre tipi di visita:

- preorder(v) =: Visita v; preorder( figlio sinistro di v); preorder( figlio destro di


v);
- inorder(v) =: inorder( figlio sinistro di v); Visita v; inorder( figlio destro di v);
- postorder(v) =: postorder( figlio sinistro di v); postorder( figlio destro di v);
Visita v;

Dette anche visite in ordine anticipato, misto, differito.

Esempi.
Applicando i tre tipi di visite all’albero A della pagina precedente, si ottengono i
seguenti ordinamenti dei nodi:

- preorder(v)⇒ ABDECFGHI
- inorder(v) ⇒DBEAFHGIC
- postorder(v)⇒DEBHIGFCA

5.2.2 – ALBERI (in generale)

Sono come gli alberi binari, tranne che i figli di un nodo possono essere più di 2.

I termini: padre, figli, radice, livello, profondità, foglia, nodo interno, hanno lo
stesso significato che per gli alberi binari.

Se un albero ha nodi con fino a k figli:


- il grado (degree) del nodo può variare da 0 a k
- il grado dell’albero è k

Se un albero ha grado k ,:
- al livello l ci sono al massimo kl nodi
- se la profondità è p, il max. numero di nodi è 1+k + k2 +…… kp =(kp+1–1)/(k-1)

26
A.Laurentini-Analisi degli algoritmi

Possono essere rappresentati con liste multiple (un puntatore per ciascuno dei figli)

ESERCIZI E PROBLEMI
2.5 2
• Un algoritmo richiede f(n)= 15 n +17 n lgn + 136n operazioni fondamentali.
Qual è la sua complessità asintotica? Quali delle asserzioni seguenti è vera?
f(n) ∈ Ω( n2.5) f(n) ∈ω( n2) f(n) ∈o( n2) f(n) ∈O( n3 )

• Dati due numeri reali a e b 0 < a < b, dimostrare che na ∈ O( nb ) , ma


nb ∉ O( na )

• Ordinare le funzioni seguenti in ordine di complessità asintotica crescente.


Verificare quali appartengono alla stessa classe

n 2n nlgn n-n3+7 n5 lgn n1/2 en

n2 + lgn n2 2n-1 lglgn n3 (lgn)2 n!

• Dati tre numeri reali a, b, c diversi tra loro, scrivere un algoritmo che determini il
numero mediano. Si determini il numero di confronti nel caso pessimo ed in quello
medio.

• Scrivere un algoritmo che determini il secondo più grande elemento in una lista di
n elementi
( si potrebbe usare due volte l’algoritmo che determina il massimo in una lista,
facendo così in totale sempre (n-1)+(n-2)= 2n -3 confronti. Si può fare di meglio nel
caso pessimo? E nel caso medio?)

• Scrivere un algoritmo che determini il massimo ed il minimo di una lista (Come


nell’esercizio precedente, si potrebbe usare due volte lo stesso algoritmo (determina il
max. o il minimo a seconda di quale elemento viene scelto dopo ogni confronto), e
quindi 2n -3 confronti. Si può fare di meglio? )

• Problema delle torri di Hanoi


http://en.wikipedia.org/wiki/Tower_of_Hanoi#Origins

27
A.Laurentini-Analisi degli algoritmi

Il puzzle fu inventato dal matematico francese Édouard Lucas nel 1883. Si tratta di
spostare la pila di dischi dal primo perno al terzo , mantenendo la pila ordinata e
facendo solo mosse lecite, cioè:
- spostare un disco per volta
- mettere un disco solo su un perno vuoto, o sopra un disco più grande
Il problema viene usato per illustrare algoritmi recursivi. La soluzione recursiva è:
- applica recursivamente l’algoritmo per spostare n-1 dischi sul perno centrale
- muovi il disco rimasto sul terzo perno
- applica di nuovo l’algoritmo per spostare sul terzo perno gli n-1 dischi del
perno centrale
In pseudocodice (Src, Aux, Dst sono i tre assi)
HanoiTowers(N, Src, Aux, Dst)
if N is 0
exit
else
HanoiTowers(N-1, Src, Dst, Aux)
Move from Src to Dst
HanoiTowers(N-1, Aux, Src, Dst)
Determinare la complessità dell’algoritmo (si scriva la relazione ricorrente che la
determina) .
Se, come nella leggenda, i monaci buddisti lavorano con 64 dischi d’oro spostando un
disco al secondo, ed il mondo finirà quando avranno finito di spostare la pila,
dobbiamo preoccuparci?

• Visitare con le tecniche pre-order, in-order, post-order, l’ albero binario:

28
A.Laurentini-Analisi degli algoritmi

a
b c
d f
e g h
i l m n

• Quanti nodi ha un albero binario completo di profondità 6? E quanti un albero


completo ternario di profondità 5?
• Sia dato un albero binario con 100 nodi. Quanto vale la sua profondità minima?
Quante foglie ha al minimo ed al massimo?
• Risolvere lo stesso esercizio per albero ternario
• Gli alberi possono essere memorizzati in posizioni consecutive in un vettore. Se
l’albero è binario, se il nodo padre ha indice 100, quali sono gli indici dei nodi figli?
E se l’albero è ternario?

29
A.Laurentini-Analisi degli algoritmi

6 – ANALISI DI ALGORITMI DI RICERCA

6 .1– RICERCA IN LISTE ORDINATE

E’ un caso frequente in pratica. Supponiamo ordinamento non decrescente:

xi ≤ xi+1 i=1,…..n

Problema: dato x, trovare la sua posizione i nella lista (se non c’è, si fornisce 0).
In realtà di solito si cerca un elemento di informazione (record) di cui x è un campo
(chiave di ricerca):

x Informazione associata

Operazione fondamentale: confronto

6.1.1– ALGORITMO 1

• Se non sfruttiamo l’ordinamento, la ricerca sequenziale è Θ(n)( vedi esempio in


Par. 3.2.1).
L’idea dell’ALG. 1 è quella di non fare confronti inutili: se x > xi, possiamo arrestare
la ricerca. Si richiede un confronto a 3 vie (=, > , < )

• Caso pessimo. W(n)=n-1 , come nella ricerca sequenziale, poiché x può essere
uguale o maggiore dell’ultimo elemento xn.

• Caso medio. Per la ricerca sequenziale era A(n)≅3n/4 per p=0.5 ( prob. di elemento
presente nella lista).

30
A.Laurentini-Analisi degli algoritmi

Ipotesi:
- p=0.5
- se x∈lista, posizioni equiprobabili (prob. per ciascuna posizione= 1/2n)
- se x∉lista, gli intervalli tra gli elementi sono equiprobabili (prob. =1/2(n+1))

x1 x2 x3 xn

Int.1 Int.2 Int.3 Int.n+1

n n
1 1 1 n
A( n) = ∑ i+∑ i+ n≅
i =1 2n i =1 2(n + 1) 2( n + 1) 2

x∈lista x∉lista, e’ in uno x∉lista, e’ nell’ultimo


dei primi intervalli intervallo

Quindi la complessità nel caso medio e’ migliore, ma non si cambia la classe di


complessità, che rimane Θ(n).

6.1.2 - ALGORITMO 2: RICERCA BINARIA (DICOTOMICA)

• Idea Generale (recursiva).


- Confronto x con l’elemento centrale xc della lista
- Se uguali, STOP
- Se x< xc, applica la stessa tecnica alla metà superiore, se , x> xc, applica la stessa
tecnica alla metà inferiore

• Analisi dettagliata Worst Case


La lista può avere un numero n pari o dispari di elementi.

n=5

n=6
31
A.Laurentini-Analisi degli algoritmi

In ogni caso, confronto con (n+1)/2 (vedi esempi sopra)

Se n è dispari, ho 2 sottoliste uguali da (n-1)/2 elementi ciascuna.


Se n è pari, ho 2 sottoliste da n/2 e da (n/2 )–1 elementi ciascuna.

In ogni caso, la lista esaminata nel passo successivo è lunga, nel worst case, n/2.

Si ha quindi, nel worst case, la seguente relazione ricorrente (recurrence relation):

W(n)= 1+W(n/2)

che, insieme alla condizione al contorno

W(1)=1

dà la soluzione

W(n)= 1+ lgn

Si può giungere a questo risultato in modo approssimato, senza conoscere la teoria


esatta della equazioni alle differenze.

W(n)= 1+W(n/2) = 1+1+W(n/4) = 1+1+1+W(n/8) = …………..

Se n è una potenza di 2, si ottiene:

W(n)= 1+ lgn

Conclusione:l’ ALGORITMO 2 (dicotomico) è Θ(lgn).

• Analisi caso medio n lgn


Con le ipotesi: 1000 10
- x non ripetuto 106 20
- posizioni equiprobabili 109 30
- intervalli equiprobabili
si dimostra che:

A(n)≅1/2 + lgn∈Θ(lgn).

32
A.Laurentini-Analisi degli algoritmi

6.1.3- OTTIMALITA’ DELLA RICERCA BINARIA (DICOTOMICA)

• Classe di algoritmi: quelli che usano confronti a 3 vie

• Determinazione Lower bound


Qualunque algoritmo che faccia confronti può essere descritto con un’albero di
decisione.
Albero di decisione:=albero binario che rappresenta, per un dato algoritmo, e una data
dimensione n dell’INPUT, l’ordine in cui vengono fatti i confronti. La radice
contiene il primo elemento con cui si fa il confronto.

i
x<xi x>xi
Nodo generico:

Esempio: albero di decisione per la ricerca binaria su una lista di 10 elementi.

1 2 3 4 5 6 7 8 9 10
5

2 8

1 3 6 9

4 7 10
Si ha che:
numero dei confronti fatti nel worst case = profondità dell’albero +1

Il lower bound per la profondità dell’albero binario è (vedi Par. 5.2.1):

p≥ lg N N= numero nodi

Dimostriamo che N≥n.


Per assurdo: supponiamo che nell’albero non ci sia il nodo i. Evidentemente, se x è in
posizione i non può essere trovato. Quindi deve esserci almeno un nodo per ogni
posizione. Ne consegue che:

p ≥ lg N ≥ lg n

33
A.Laurentini-Analisi degli algoritmi

e quindi

LB(n)=1+ lg n = W(n) ⇒ l’algoritmo dicotomico è ottimo

Problema: mantenimento lista ordinata (se è un vettore, l’inserzione è O(n)

7 - ALGORITMI DI ORDINAMENTO (SORT)

• Gli algoritmi di SORT sono:


- molto usati in ogni tipo di applicazioni tecnica e gestionale
- utili didatticamente
- molto studiati, di analisi relativamente semplice

• Ipotesi:
- lista di n elementi con campo chiave (key) o sole chiavi di ordinamento
- OF: confronto a 3 vie (=, <, >). Gli spostamenti non contano.
- ordinamento voluto: non decrescente

7. 1- INSERTION SORT

• E’ in place (non richiede spazio aggiuntivo)

• Descrizione algoritmo

Situazione iniziale Situazione ad un passo generico

Chiavi da ordinare Chiavi ordinate Chiavi da ordinare

Opero per scambi successivi. Ad ogni passo inserisco nel modo seguente una nuova
chiave nella lista ordinata :

34
A.Laurentini-Analisi degli algoritmi

- considero la prima chiave della lista da ordinare; la confronto con l’ultima delle
chiavi ordinate
- se è ≥, è già al posto giusto e considero la prossima chiave da ordinare
- se è <, la scambio con questa, la confronto con la penultima delle chiavi da
ordinare. Procedo per scambi finché non è inserita al posto giusto tra le chiavi già
ordinate.

Sono 2 loop :
- uno esterno per ognuna delle chiavi da inserire (n-1)
- uno interno per l’inserimento tra le chiavi già ordinate.

• Analisi worst case


E’ il caso in cui le chiavi sono ordinate al contrario.

Chiavi all’inizio del SORT Chiavi durante il SORT

Per ciascuna delle n-1 chiavi da inserire si fanno tutti i confronti possibili con quelle
già ordinate.

n
n(n − 1)
W (n) = ∑ (i − 1) = ⇒W(n)∈Θ(n2)
i=2 2

Il caso ottimo si ha per chiavi già ordinate: bastano n-1 confronti.


Possiamo anche dire che l’algoritmo è O(n2)

• Analisi caso medio


Ipotesi:
- chiavi distinte
- permutazioni equiprobabili
- intervalli di inserzione equiprobabili
Situazione al passo di inserzione dell’elemento i. Probabilita’ per ogni intervallo 1/i.

Confronti i-1 i-1 i-2 ………. 2 1

35
A.Laurentini-Analisi degli algoritmi

1 2 3 i-1 i i+1 ……..

Numero medio di confronti per ogni inserzione:

i −1 1 1 1 i −1 1 i +1 1
∑ j =1
i
j + (i − 1) = ∑ j =1 j + 1 − =
i i i 2

i

per tutte le inserzioni:


n  i +1 1  n 2 3n n 1
A(n) = ∑i = 2  − = + − 1 − ∑i = 2 =
 2 i 4 4 i
2
n 3n
≅ + − 1 − ln n
4 4
Trascurando i termini di ordine inferiore:

n2
A(n) ≅ ∈ Θ( n 2 )
4

7. 2- ALTRI SORT O(n2)

• SELECTION SORT (MAXSORT)


Descrizione: costruisco la lista ordinata un elemento per volta, selezionando ogni
volta la chiave più grande della lista delle chiavi non ordinate rimaste.

Il SORT può essere in place se ogni volta la chiave selezionata si scambia con
l’ultima delle chiavi non ordinate.

Un SORT analogo si costruisce scegliendo ogni volta la chiave minima.

Per ogni selezione su di una lista di i chiavi si fanno i-1 confronti. Quindi:
n
n(n − 1)
W (n) = ∑ (i − 1) = = A(n)
i=2 2
Il numero di confronti del caso pessimo e medio sono uguali, ed esattamente uguali a
quelli dell’insertion sort nel caso pessimo.

• BUBBLE SORT
Questo SORT fa diversi passi sulla lista, confrontando chiavi contigue e
scambiandole se fuori ordine. Si inizia confrontando le prime due chiavi,
scambiandole se non sono ordinate. Si confronta poi la seconda con la terza,
scambiandole se necessario, la terza con la quarta, etc.. Alla fine di questo passo, la
chiave maggiore è arrivata in fondo alla lista.

36
A.Laurentini-Analisi degli algoritmi

L’algoritmo si riapplica alla lista dei primi n-1 elementi, poi ai primi n-2, etc.
Non si prosegue se su una di queste sottoliste non si fanno più scambi.

Anche in questo caso, nel caso pessimo ( lista ordinata al contrario), il numero di
confronti è uguale a quello del MAXSORT e a quello del caso pessimo dell’insertion
sort.

Anche il caso medio, con le ipotesi usuali, è Θ(n2).


7. 3- LOWER BOUND PER ALCUNI SORT

• Una lista può essere ordinata in n! modi. Le inversioni sono le coppie di elementi
scambiati. Ad es., nella lista
2, 4, 1, 5, 3
ci sono 4 inversioni: (2,1), (4,1), (4,3), (5,3).

Consideriamo i SORT della categoria seguente:


- OF: confronto a 3 vie
- rimuovono un’inversione (al max.) ad ogni confronto
come bubble SORT, insertion SORT, e tutti i SORT che operano per scambi tra
elementi contigui.

• Worst case
Il numero massimo di inversioni si ha per una lista ordinata inversamente:

no inversioni: n(n-1)/2

Ne consegue che bubble SORT e insertion SORT sono ottimi in questa categoria!

• Caso medio.
Quante sono le inversioni in media? Supponiamo permutazioni equiprobabili.
Consideriamo una permutazione e la sua trasposta (Es. 2 4 1 5 3 ⇔ 3 5 1 4 2).
Per ogni possibile coppia di indici, se c’è un’inversione in una permutazione, non c’è
nella trasposta.
In totale, le coppie di indici sono n(n-1)/2, e quindi mediamente le inversioni sono
n(n-1)/4.
Quindi bubble SORT e insertion SORT sono ottimi anche nel caso medio.

Conclusione: per avere algoritmi migliori ci vogliono SORT che rimuovano più di
una inversione per confronto.

37
A.Laurentini-Analisi degli algoritmi

7. 4- QUICKSORT: APPROCCIO “DIVIDE AND CONQUER”

•Divide and conquer


E’ una tecnica generale per accelerare algoritmi di complessità più che lineare.
E’ basata sul fatto che:

(a+b+ …. q)k >ak + bk+ …. qk se k>1

Se e’ possibile:

- applicare l’algoritmo a parti dell’INPUT


- combinare i risultati con un algoritmo di complessità < k

si riesce a ridurre il lavoro fatto dall’algoritmo.

• Il Quicksort

L’idea generale è di dividere la lista in due sottoliste, una di chiavi più piccole, l’altra
di chiavi più grandi. A questo scopo, si considerare un elemento x della lista, e si
costruiscono due sottoliste, la prima costituita dagli elementi ≤ di x , la seconda da
quelli ≥ di x (procedura Split). Si applica recursivamente il Quicksort alle due
sottoliste.

first last
x
Split
first splitpoint last
≤x x ≥x

Quicksort Quicksort

Pseudocodice (recursivo):

38
A.Laurentini-Analisi degli algoritmi

procedure Quicksort
begin
if first<last then
Split(first, last, splitpoint);
Quicksort(first, splitpoint-1);
Quicksort(splitpoint+1,last);
end{ if }
end{ Quicksort}
La procedura Split:
la lista all’inizio
first last
x ?

splitpoint unknown
a metà
first last
x <x ≥x ?

splitpoint unknown
al termine
first last
x <x ≥x

splitpoint
Pseudocodice:

procedure Split
begin
x:=L(first)
splitpoint:=first
for unknown:=first+1 to last do
if L(unknown)<x then
splitpoint:=splitpoint+1
Interchange(L(splitpoint),L(unknown))
end{ if }
end{ for}
Interchange(L(first), L(splitpoint))
end{ Split}

• Worst case

39
A.Laurentini-Analisi degli algoritmi

Il caso peggiore si ha quando Split non divide in due parti, perché la prima chiave è la
più piccola (o la più grande). Quindi il caso pessimo si ha con lista già ordinata, o
ordinata al contrario (scegliendo x come primo elemento)
In tal caso, il numero totale di confronti è
n
n(n − 1)
W (n) = ∑ (k − 1) =
k =2 2

esattamente come l’insertion, il bubble ed il MAXSORT.


• Caso medio
Ipotesi:
- permutazioni equiprobabili
- chiavi distinte
- ogni splitpoint è ugualmente probabile
Si dimostra che
A(n)≅1.4(n+1)lgn ∈Θ(nlgn)

• Miglioramenti di Split
Il problema è di dividere ogni volta circa a metà. Esistono tecniche migliori per
scegliere la chiave x. Esse rendono estremamente improbabili casi vicini al pessimo.

• Spazio usato.
Il quicksort non è in-place. Ad ogni iterazione di Split occorre memorizzare (in uno
STACK) gli estremi delle sotto-liste. Nel caso peggiore, lo spazio aggiuntivo
richiesto è Θ(n).

7. 5- MERGE (FUSIONE) DI LISTE ORDINATE

E’ un’operazione sulle liste comune quanto il SORT.

A B C=A+B

+ =
a1 an b1 bm

• Algoritmo. Confronto a1 e b1. Trasferisco il più piccolo nella prima posizione della
lista C. Proseguo confrontando ogni volta i due elementi più piccoli di quello che è
rimasto delle liste A e B. La realizzazione è semplice, con tre puntatori, uno alla lista
C che si incrementa ogni volta, ed altri due in A e in B, di cui ogni volta uno solo si
incrementa.

40
A.Laurentini-Analisi degli algoritmi

Se ad un certo punto una lista si è svuotata, il rimanente dell’altra può essere copiato
in C senza ulteriori confronti.

• Variante. A è una lista concatenata, in cui inserisco uno dopo l’altro i bi.

• Caso pessimo. Quando le liste finiscono insieme si fanno tutti i confronti possibili:
W(n)= n+m-1
L’algoritmo è lineare, ed è facile dimostrare che è ottimo.

• Caso ottimo. Si ha quando tutti gli elementi di una lista sono maggiori (o minori)
di tutti quelli dell’altra. In questo caso il numero di confronti è:
Min[n,m]. Si può ridurre il numero dei confronti ad O(1) se si confrontano subito gli
estremi delle liste.

N.B. Un algoritmo lineare, che richieda di considerare tutti gli elementi dell’INPUT,
è sempre ottimo.

• Riduzione dello spazio usato


Sembra che occorra uno spazio aggiuntivo di n+m per la lista C. Si può ridurre lo
spazio aggiuntivo a Min[n,m]:

1 A n n+m 1 B m

7. 6- MERGE -SORT

• Idea generale: divido la lista in due parti uguali, applico a ciascuna recursivamente
il MERGE-SORT, ricombino con MERGE

41
A.Laurentini-Analisi degli algoritmi

first (first+last)/2 last

merge-sort merge-sort

merge

procedure Mergesort(first, last)


begin
if first<last then
Mergesort(first, (first+last)/2)
Mergesort((first+last)/2 +1, last)
Merge(first, (first+last)/2, (first+last)/2 +1, last)
end{ if }
end{ Mergesort}

• Analisi del caso pessimo

Relazione ricorrente e condizione al contorno:


W(n) = W(n/2)+ W(n/2) + n- 1

sort merge
W(1) =0

Soluzione per n potenza di 2.

W(n) = 2W(n/2) + n- 1 = 2( 2W(n/4) + n/2- 1) + n- 1 =


= n- 1+ n- 2+4W(n/4) = n- 1+ n- 2+ n- 4+8W(n/8)=…….
≅ nlgn – n ∈ Θ(nlgn).

• Una visione non recursiva del MERGE-SORT

42
A.Laurentini-Analisi degli algoritmi

Confronti n/2 n/2


n-1
n/4 n/4 n/4 n/4
n-2

……………………………………………………………………..

2 2
n-n/4 …………………………………………..

n-n/2 …………………………………………..

Partendo dal basso, si fanno solo MERGE. Il numero di confronti è ancora


W(n) = n- 1+ n- 2+ n- 4+…….n- n/2= nlgn – n

7. 6.1- OTTIMALITA’ DEL MERGE -SORT

Abbiamo visto algoritmi Θ(n2) e Θ(nlgn). La differenza è grande:


n n2 nlgn n2 /nlgn

10 100 ≅30 ≅3
100 104 ≅600 ≅15
1000 106 ≅104 ≅100
106 1012 ≅2x107 ≅5x104

E’ possibile far meglio di Θ(nlgn)? No, nella classe di algoritmi che usano il
confronto a 3 vie.

• Lower bound.
Ad ogni algoritmo della classe e ad ogni dimensione dell’INPUT possiamo associare
un albero binario di decisione. I nodi interni sono i confronti, le foglie le
permutazioni (i vari ordinamenti possibili). Es.:SORT di x1 x2 x3; n!=3!=6
permutazioni.

x1<x2 x1: x2 x1>x2

x2: x3 x1: x3
x1<x3 x1>x3
x2<x3 x2>x3 43

x1 x2 x3 x:x x2 x1 x3 x2: x3
A.Laurentini-Analisi degli algoritmi

Il numero dei confronti nel caso pessimo è la profondità (3 nell’esempio).

Il numero delle foglie deve essere ≥ n! Se fosse maggiore, ci sarebbero più foglie
uguali. Poiché l’albero è deterministico, solo una delle foglie uguali potrebbe essere
raggiunta, le altre non lo sarebbero mai.
Possiamo quindi sempre ricondurci ad esattamente n! foglie.

Si può dimostrare che la profondità minima (e quindi il numero di confronti minimo)


è:

p = lg (n!) =LB(n)

ma abbiamo anche:

lg (n!) ≥ (n/2)lg(n/2)
LB(n) ∈ Θ(nlgn).

e quindi il merge-sort è ottimo nella sua classe di complessita’ asintotica.

Piu’ in dettaglio(vedi richiami matematici:sommatorie):

lg n! = ∑ j =1 lg j ≥ n lg n − n lg e ≅ n lg n − 1 .44 n
n

44
A.Laurentini-Analisi degli algoritmi

7. 7- ALGORITMI DI SORT LINEARI

Usano altri tipi di operazioni fondamentali, non il semplice confronto a 3 vie.


Tipicamente richiedono maggiori conoscenze sulle chiavi di ordinamento.

7. 7.1 – BUCKET SORT

• Idea: Devo ordinare dei nomi. Li metto in 26 sottoliste (buckets) a seconda della
prima lettera. Ordino le sottoliste e ricombino (applicazione del principio divide and
conquer). Si può fare lo stesso usando le cifre di un numero.

• Analisi
Consideriamo la complessità di uno degli algoritmi di SORT già esaminati applicato
all’intera lista o applicato a k sottoliste uguali:

a) uso per le sottoliste un algoritmo Θ(nlgn).

nlgn> k((n/k)lg(n/k) = nlg(n/k) = n(lgn – lgk)

b) uso per le sottoliste un algoritmo Θ(n2).

n2 > k(n/k)2 = n2/k

45
A.Laurentini-Analisi degli algoritmi

In ambedue i casi, se k=n/a , i SORT diventano lineari !

Il SORT consiste di 3 fasi:


- distribuzione in k buckets: Θ(n)
- Sort di ciascun bucket: circa lineare se k ≅ n/a
- Ricombinazione (se necessaria) O(n)
L’intero algoritmo è tendenzialmente lineare.

Un’alternativa è l’applicazione recursiva del SORT alle sottoliste.

• Problemi
- la suddivisione in sottoliste, se si usano regole semplici, può non essere è
uniforme ( ad. es, se ordino dei nomi, quelli che cominciano per C sono di più di
quelli che cominciano per Z.
- il numero di sottoliste deve aumentare con n. Se uso le prime lettere, l’aumento
non è continuo: 26⇒262⇒263⇒……
- se uso una recursione completa, devo gestire molti puntatori.

7. 7. 2 – RADIX SORT

Algoritmo di SORT usato già prima dell’avvento dei calcolatori da macchine a


schede perforate.
Elimina i problemi realizzativi del BUCKET SORT. Si applica in tutti i casi in cui la
chiave è una stringa di k caratteri che possono assumere m valori.

• Algoritmo.
Si distribuiscono i numeri negli m buckets contigui in base all’ultima cifra (si usano
quindi 10 buckets se le chiavi sono numeri decimali).
Si fanno tanti passi quanti sono i caratteri della stringa, ridistribuendo ogni volta le
chiavi in m buckets secondo la cifra considerata, conservando, a parità di cifra,
l’ordine precedente (vedi esempio).

Al termine dei k passi, le stringhe sono ordinate in ogni bucket, ed i bucket contigui
danno la lista finale ordinata. Infatti, consideriamo due stringhe qualsiasi A e B nella
lista finale con i caratteri seguenti :

c1, c2, ….. ck……….cn, c’1, c’2, ……. c’k, ………. c’n,

se c1< c’1, la stringa A è stato messa in un bucket prima di B nell’ultimo passo, e


quindi è nella posizione corretta rispetto a B. Se fossero uguali i primi k-1 caratteri, e

46
A.Laurentini-Analisi degli algoritmi

ck< c’k, A è stato messo in un bucket prima di B nel passo n-k+1, riguardante il
caratter k-esimo, e quindi è nella posizione corretta rispetto a B. Tutto ciò è vero per
qualunque coppia A e B, e quindi l’intera lista è ordinata.

• Analisi
Se si usa una struttura dati a liste multiple, come quella indicata in figura, con una
lista concatenata per ogni bucket, la collocazione nei bucket delle chiavi richiede, per
ogni chiave un numero costante di operazioni. Così pure ad ogni passo la
ricombinazione dei bucket per formare una nuova lista richiede un numero costante
di operazioni.
Quindi la distribuzione nei buckets è Θ(n), e poiché avviene un numero di volte fisso
k (tante quante sono le cifre), l’intero algoritmo è Θ(n).

• Esempio numerico

329 720 720 329


457 355 329 355
657 436 436 436
839 457 839 457
436 657 355 657
720 329 457 720
355 839 657 839
Lista iniziale terza cifra seconda cifra prima cifra, lista ordinata

Struttura dati: lista multipla


E’ necessaria una nuova lista multipla ad ogni passo dell’algoritmo, costruita
svuotando la vecchia (quella della cifra precedente)

47
A.Laurentini-Analisi degli algoritmi

Puntatore all’elemento
1 corrente

7. 7. 3 – COUNTING SORT

Ordina in tempo lineare numeri interi da 1 a k (o elementi che si possono


rappresentare in questo modo)

• Idea generale. contare quanti sono gli interi con valore 1, 2,….k. Copiare in un
nuovo vettore tanti 1, 2, … k quanti si sono prima contati.

Esempio
Vettore da ordinare (k=6)

3 6 4 1 3 4 1 4

Vettore ausiliario (contiene le occorrenze di 1, 2,….6)

48
A.Laurentini-Analisi degli algoritmi

1 2 3 4 5 6

2 0 2 3 0 1

• Analisi
- costruzione vettore ausiliario: Θ(n),
- lettura vettore ausiliario e costruzione vettore ordinato (sul vettore originale per
risparmiare spazio: Θ(n+k).
In pratica, si usa il counting SORT quando k è O(n), per cui l’intero algoritmo è Θ(n)
(sarebbe errato usare l’algoritmo per pochi interi molto lontani fra loro)

7. 8 – ALTRI SORT

• HEAP-SORT.
Usa una struttura dati detta HEAP, che vedremo più avanti. Appartiene alla classe
degli algoritmi che fanno confronti, ed è ottimo in questa classe (Θ(nlgn) nel caso
pessimo).

• EXTERNAL SORTING
Si suppone che il numero di chiavi sia tale da non poter risiedere
contemporaneamente in memoria centrale. In questi casi predominano i tempi di
trasferimento con i dischi o nastri. Il problema, con le memorie centrali attuali, ha
ridotta importanza.

ESERCIZI E PROBLEMI

• Un algoritmo di sort viene detto stabile se chiavi uguali rimangono nella stessa
posizione relativa nella lista ordinata. Quale dei seguenti algoritmi è stabile? Per tutti
i SORT non stabili, dare un esempio in cui due chiavi uguali cambiano posizione
relativa.
- Insertion Sort
- Maxsort
- Bubblesort
- Radix Sort
- Quicksort
49
A.Laurentini-Analisi degli algoritmi

• Si abbia una lista di migliaia di elementi in cui solo poche elementi non sono in
ordine, spostati di poche posizioni da quella corretta. Quale, o quali degli algoritmi di
Sort studiati è più adatto?

• Studiare degli algoritmi ragionevoli per i seguenti problemi, e determinare la loro


complessità nel caso pessimo:
- Sia data una pila di migliaia di fatture, ed un’altra pila di pagamenti ( si
supponga che su ogni documento di pagamento ci sia il riferimento al numero
fattura). Si individuino le fatture non pagate (algoritmo banale: per ogni fattura
esamino la lista dei pagamenti: complessità: O(numero fatture X numero
pagamenti))
- Sia data una lista di schede di libri di una biblioteca, comprendente tra l’altro la
casa editrice. Sia data anche una lista di 30 case editrici. Si determini quanti
libri della biblioteca sono stati pubblicati da ogni casa editrice
- Siano date le schede dei prestiti di una biblioteca universitaria, ciascuna con la
matricola dell’allievo che ha ricevuto il prestito. Si voglia determinare il
numero di allievi che hanno preso in prestito almeno un libro.

• Studiare un algoritmo efficiente in place che riordini una lista in modo che tutte le
chiavi negative precedano tutte le chiavi positive.

• Si consideri una lista in cui le chiavi hanno valore solo 0 oppure 1.


- Quanti confronti fa l’Insertion Sort nel caso pessimo?
- Quanti confronti fa il Quicksort nel caso pessimo?
- Esiste un algoritmo efficiente (lineare) per questo caso?
-
• Si consideri il caso di una lista con tre soli valori possibili per le chiavi. Se è
ammesso solo il confronto e lo scambio tra chiavi, è possibile realizzare un algoritmo
lineare?

• Si consideri il seguente problema pratico: sono da ordinare in ordine alfabetico 300


compiti d’esame cartacei, ed è a disposizione una grossa tavola. Cosa conviene fare?

50
A.Laurentini-Analisi degli algoritmi

• Si abbia a disposizione un algoritmo efficiente per il sort di chiavi intere. E’ adatto


ad ordinare dei numeri floating point? Se non lo è, cosa si può fare?

8 – ANALISI DELLA TECNICA “DIVIDE AND CONQUER”

E’ usata da molti algoritmi di SORT(QUICKSORT, MERGESORT, etc.) e di altro


genere.

• Approccio generale
- divido un problema di dimensione n in a sottoproblemi ciascuno di dimensioni n/b
- ricombino le soluzioni dei sottoproblemi

• Analisi
In generale è:

W(n) = a W(n/b) +c nk (risoluzione dei sottoproblemi + ricombinazione)

Si dimostra che, se a e b sono interi, a≥1, b≥2, c e k sono positivi, :

51
A.Laurentini-Analisi degli algoritmi

lg a
O(n b ) a>bk

W(n)= O (n k lg n) a=bk

O(n k ) a<bk

Ad esempio, per SORT-MERGE a = b ⇒ W(n)∈O(nlgn)

9- PROBLEMI P, NP, NP-COMPLETI

• Distinguiamo i problemi in due grandi categorie:


- Quelli “trattabili”, che ammettono algoritmi polinomi O(nk) con k qualunque
- Quelli “intrattabili, con algoritmi esponenziali O(bcn) con b, c qualunqui.

• Distinguiamo tra 3 livelli di difficoltà (crescenti ) di un problema:

- Problemi di decisione: c’è una soluzione migliore di un dato valore? (Risposta:


si/no)
- Problemi di valore ottimo: quanto vale la soluzione ottima?
- Problemi di soluzione ottima: qual è la soluzione che produce il valore ottimo?

Esempio

52
A.Laurentini-Analisi degli algoritmi

Problema: colorazione di un grafo G (si colorano i nodi senza che due nodi adiacenti
abbiano lo stesso colore)

- decisione: i G è colorabile con K colori?


- valore ottimo: qual è il numero cromatico χ(G) del grafo? Il numero cromatico è il
minimo numero di colori richiesti per colorare un grafo.
- soluzione ottima: una colorazione che usi il numero minimo di colori.

Ovviamente è facile passare dalla soluzione dei problemi più difficili alla soluzione
di quelli più facili.

La teoria dei problemi NP ed NP-completi si occupa della difficoltà dei problemi di


decisione, (più facili da trattare). Gli altri sono almeno altrettanto difficili.

9.1 - ESEMPI DI PROBLEMI

Tutti (salvo uno) i problemi seguenti hanno grande importanza pratica, o sono
varianti e/o semplificazioni di problemi di grande importanza pratica. Come
vedremo, tutti i problemi seguenti hanno una caratteristica comune.

• Bin Packing
E’ il più semplice problema di impaccamento (monodimensionale). Si hanno dei
recipienti (bin) di capacità unitaria , ed n oggetti di dimensioni s1, s2, …. sn, con
0 < si≤ 1.

Problema di ottimizzazione: determinare il numero minimo di bin che contengono gli


n oggetti.
Problema di decisione: sono sufficienti k bin per contenere gli n oggetti?

• Knapsack
Si suppone di avere uno zaino (knapsack) di capacità C (intero positivo), ed n oggetti
di dimensioni s1, s2, …. sn, ciascuno con associato un “guadagno” g1, g2, …. gn (tutti
interi positivi).

Problema di ottimizzazione: determinare il massimo guadagno per oggetti che


entrano nello zaino, e trovare il sottoinsieme di oggetti che forniscono il max.
guadagno.
Problema di decisione. E’ possibile avere un guadagno ≥k?

N.B. Se si considera l’impaccamento oggetti fisici, il problema è 3D. Così come


proposto, il problema ha senso, ad. es., per un investimento in danaro ( ho una cifra

53
A.Laurentini-Analisi degli algoritmi

massima disponibile C, vari investimenti possibili di costi si, ciascuno con varie
redditività gi)

• Colorazione di un grafo
Il problema è già stato definito. Qui osserviamo che la colorazione di un grafo
schematizza numerosi problemi di assegnazione, in cui vi siano incompatibilità.

• Job scheduling
Si hanno n job ( lavori) da eseguire uno per volta. Abbiamo dei tempi di esecuzione
t1, t2, …. tn, e delle penalità p1, p2, …. pn, se i job non vengono eseguiti entro le
deadlines d1, d2, …. dn.
Una schedule è una permutazione degli indici, che stabilisce l’ordine di esecuzione
dei job. Per ogni schedule c’è una penalità totale, somma delle penalità dei job che
oltrepassano la deadline.
Problema di ottimizzazione: determinare la penalità totale minima, e trovare la
permutazione ottima.
Problema di decisione. E’ possibile avere una penalità totale ≤k?

N.B. Esistono numerose varianti e complicazioni di questo problema. Ad es.,(si pensi


alle operazioni di un’officina, o all’esecuzione di programmi) alcuni job potrebbero
essere condotti in parallelo; l’esecuzione di alcuni job richiede che altri siano stati già
terminati.

• Cammino Hamiltoniano, circuito Hamiltoniano


Un cammino (un ciclo) Hamiltoniano è un percorso (un ciclo) di un grafo che passa
attraverso tutti i vertici una volta sola.

Problema di decisione. Dato un grafo, esiste un cammino (un ciclo) Hamiltoniano?

• Problema del commesso viaggiatore (traveling salesman)

Problema di ottimizzazione. Dato un grafo pesato (ogni lato ha associato un numero


detto peso), trovare un cammino (un ciclo) Hamiltoniano di peso totale minimo.
Problema di ottimizzazione. Dato un grafo pesato, esiste un cammino (un ciclo)
Hamiltoniano di peso totale ≤k?

• CNF-SAT (conjunctive normal form satisfiability)


Si abbia un’espressione booleana formata da prodotti logici(AND) di somme logiche
di variabili booleane. Ad es.:
(a+b+c)(a+e+f)(b+q)…..

54
A.Laurentini-Analisi degli algoritmi

Problema di decisione. Esiste un’assegnazione di TRUE e di FALSE alle variabili


booleane che renda TRUE l’espressione?

9.2 – LA CLASSE P (PROBLEMI DI DECISIONE)

E’ la classe dei problemi di decisione che ammettono nel caso pessimo algoritmi
polinomiali .
Per nessuno dei problemi del paragrafo precedente si conosce un algoritmo
polinomiale.

N.B.
- Gli algoritmi in P rimangono in P se combinati insieme
- La classe P e’ indipendente dal modello computazionale
- La dimensione dell’INPUT e’ la lunghezza minima in caratteri di una stringa che
lo definisce. In alcuni casi si può essere indotti in errore. Esempio:
Problema: un dato intero n è un numero primo? Algoritmo: divido per tutti gli
interi da 2 a n/2. Analisi: si fanno Θ(n) divisioni, ma l’algoritmo non è Θ(n)! La
dimensione dell’ingresso è misurata dal numero di cifre, che sono Θ(logbn)!
Quindi l’algoritmo è in realtà Θ(bn). L’algoritmo descritto è migliorabile, ma tutti
gli algoritmi conosciuti sono esponenziali.

9.3 – LA CLASSE NP

E’ la classe dei problemi di decisione che ammette algoritmi Non deterministici


Polinomiali. Questi algoritmi consistono di due fasi:
a) fase non deterministica. Viene prodotta una stringa di caratteri, che individua
una possibile soluzione del problema
b) fase polinomiale. L’ipotesi di soluzione è verificata con un algoritmo
(deterministico) polinomiale

Esempio
Per determinare se un grafo e’ colorabile con k colori, si produce in qualche modo
(anche a caso) una stringa contenente varie occorrenze di k caratteri lunga V. La
stringa descrive una possibile colorazione. Se la colorazione sia valida o no, può
essere facilmente verificato in tempo polinomiale.

55
A.Laurentini-Analisi degli algoritmi

• Tutti gli algoritmi presentati nel paragrafo 9.2 ∈NP

• P⊆NP ; infatti l’algoritmo deterministico è un caso particolare di algoritmo non


deterministico.

• P=NP ? Questo è un problema aperto. Evidentemente, per qualunque problema in


NP si possono costruire algoritmi del tipo:
a) genero tutte le possibili ipotesi di soluzione
b) applico la verifica polinomiale per ciascuna ipotesi.
Tali algoritmi sono però esponenziali. Un algoritmo di SORT che usa questa idea è il
seguente: genero a caso una delle n! permutazioni degli indici delle chiavi. Verifico
in tempo polinomiale se la permutazione è un ordinamento valido.

La situazione (da diversi anni) è la seguente:


- non è stato trovato un algoritmo polinomiale per nessuno dei problemi visti.
- non è stato dimostrato un lower bound esponenziale per nessuno di questi
problemi (i lower bound trovati sono tutti polinomiali)

9.4 – LA CLASSE DI PROBLEMI NP-COMPLETI

Sono i più difficili della classe NP: se ci fosse un algoritmo polinomiale per uno di
questi problemi, ci sarebbe per tutti i problemi in NP.

•Trasformazione(riduzione) polinomiale T di un problema Π1 in un problema Π2


Si effettua nel modo seguente:

T Algoritmo per Π2 si/no


x (input di Π1) T(x)

Algoritmo per Π1
Esempio.
Π1:= date n variabili booleane xi , almeno una di queste è TRUE?

56
A.Laurentini-Analisi degli algoritmi

Π2:= dati n interi, il loro massimo è positivo?


Una trasformazione polinomiale adatta è la seguente:
T(x1 , x2 ……..xn )={ y1 , y2 ……..yn }
Dove , yi=1 se xi= TRUE, yi=0 se xi= FALSE

• Definizione: Un problema Π è NP-completo se:


a) Π ∈NP
b) ogni altro problema Π‘∈NP è trasformabile polinomialmente in Π

• Importanza della classe NP-completa.


Dalla definizione precedente, se ci fosse un algoritmo polinomiale per un problema
NP-completo, ci sarebbe per tutta la classe NP: sarebbe cioè P=NP.
Che esistano problemi NP-completi è garantito dal teorema seguente.

• Teorema di COOK(1971): Il problema CNF-SAT è NP-completo

Successivamente si è dimostrato che molti altri problemi sono NP- completi.


Sono NP- completi tutti i problemi del paragrafo 9.2.

• Come si dimostra che un problemaΠ è NP-completo.


a) Si parte da un problema Π’ che sia NP-completo. Lo si trasforma
polinomialmente in Π. Questo significa che tutti i problemi in NP sono
trasformabili polinomialmente in Π:

la classe di problemi NP Π’ Π
trasformazione polinomiale

b) Si dimostra che appartiene ad NP


N.B. Per alcuni problemi si riesce a fare solo il passo a). In tal caso si dice che
l’algoritmo è NP-hard: è almeno difficile come un problema NP-completo.
Esempio.
Problema: è possibile con k guardie (telecamere) sorvegliare un ambiente di area
poligonale? Il problema è detto dell’Art Gallery.
E’ stato dimostrato che il problema Art Gallery è NP-hard , trasformando il problema
3-SAT (CNF-SAT con 3 variabili per ogni prodotto) nel problema Art Gallery
(O’Rourke and Supowit (1983), per poligoni con buchi poligonali, Lee and Lin
(1986) per poligoni senza buchi).
Non è stato invece dimostrato che il problema Art Gallery sia in NP.

• La semplificazione può far passare da NP-completo a P


- 3-SAT è NP-completo, 2-SAT è in P
- non è noto se verificare l’isomorfismo di due grafi sia in P o in NP, ma per grafi
planari è in P

57
A.Laurentini-Analisi degli algoritmi

- la 2-colorabilità è in P; la 3-colorabilità è NP-completo

• Esempio di dimostrazione di NP-completezza.


Cook ha dimostrato che CNF-SAT è NP-completo. Dimostriamo che lo è anche 3-
SAT, trasformando una qualunque forma CNF-SAT in una forma 3-SAT che è TRUE
se e solo se lo è la prima.

La forma CNF-SAT contiene termini con la somma di 1, 2, 3…variabili. Per ogni


caso, devo trovare una regola per passare ad uno o più termini con tre variabili.

- caso di una variabile:


x1 ⇒ (x1+ y + z) (x1+ y + z) (x1+ y + z) (x1+ y + z)
con y e z nuove variabili. La sottolineatura indica la complementazione. Il prodotto
dei 4 termini da tre variabili è TRUE se e solo se x1=TRUE
- caso di due variabili:
(x1 + x2)⇒ (x1+ x2 + z) (x1+ x2 + z)
con z nuova variabile. Il prodotto dei 2 termini da tre variabili è TRUE se e solo se
x1=TRUE
- caso di tre variabili: il termine rimane inalterato
- caso di k≥4 variabili. Una sostituzione che soddisfa le condizioni volute è la
seguente:
(x1 + x2 ….. + xk)⇒ (x1+ x2 + y1) (x3+ y1 + y2) (x4+ y2 + y3)…….. (xk-1+ xk + yk-3)
Infatti: se il primo termine è TRUE, almeno una variabile xi è TRUE. Il secondo
termine (quello con i prodotti di 3 variabili) è TRUE con i seguenti valori delle nuove
variabili:
y1= y2= y3=…….. yi-2= TRUE ; yi-1= yi= yi+1=……. yk-3= FALSE
Viceversa, dobbiamo provare che se il secondo termine è TRUE, lo è anche almeno
una variabile xi del primo termine. Per assurdo, supponiamo che tutte le variabili xi
siano FALSE. In tal caso, la seconda espressione diventa:
( y1) ( y1 + y2) ( y2 + y3)…….. ( yk-4 + yk-3) ( yk-3)
Sviluppando in somme(OR) di prodotti(AND), si ottengono tutti termini che
contengono una variabile ed il suo complemento. Poiché yi yi= FALSE, tutti i termini
sono FALSE, e lo è anche la seconda espressione, contraddicendo l’ipotesi.

9.5 – COME AFFRONTARE I PROBLEMI NP-COMPLETI

Molti importanti problemi pratici sono NP-completi (o peggio). Si possono affrontare


con due tipi di algoritmi:

a) Algoritmi polinomiali approssimati. Anche se questi algoritmi non sempre


forniscono l’ottimo, possono essere utili per vari motivi:
- potrebbero essere a prestazioni garantite

58
A.Laurentini-Analisi degli algoritmi

- potrebbero avere un buon comportamento sperimentale

b) Algoritmi esatti, che abbiano un buon comportamento in media, anche


esponenziali in improbabili casi pessimi

9.5.1 – ESEMPI DI ALGORITMI APPROSSIMATI

• Esempio 1

- Problema: Bin packing


- Esempio di algoritmo esatto: suddividere in tutti i modi possibili gli oggetti in k≤n
subset. E’ O(nn).
- Algoritmo approssimato: Fist Fit Decreasing (FFD): 1) ordino le dimensioni si in
modo non crescente ; 2) esamino i bin dall’inizio e sistemo l’oggetto di
dimensioni si nel primo bin che lo contiene.
- Analisi del caso pessimo di FFD: l’ordinamento è O(nlgn); se la sistemazione
dell’oggetto i-esimo è nell’ i-esimo contenitore faccio n(n-1)/2 tentativi. In totale,
l’algoritmo è Θ(n2).

- Esempio:
s=(0.8, 0.5, 0.4, 0.4, 0.3, 0.2, 0.2, 0.2)

0.2
0.2
0.4
0.3
0.8
0.5
0.4
0.2

Non è ottimo(bastano 3 bin: (0.8, 0.2)(0.5, 0.3, 0.2)(0.4, 0.4, 0.2)).

- Analisi della qualità dell’algoritmo.


Definiamo:
soluzione data da FFD con input I FFD(I)
r(I)= =
soluzione ottima con input I OPT(I)
59
A.Laurentini-Analisi degli algoritmi

R(m)=max{r(I)|I tale che OPT(I)=m}

Si può dimostrare che:


R(m)≤(4/3)+1/3m
Definendo:
S(n)=max{r(I)|I di dimensione n}
si dimostra che:
S(n)≤3/2 ; per n→∞, S(n)→3/2

Recentemente per R(m) è stato dimostrato un bound migliore:


R(m)≤(11/9)+4/m
Il bound sembra non migliorabile, poiché ci sono esempi in cui R(m)>11/9

L’algoritmo è stato anche studiato sperimentalmente con ottimi risultati medi.

• Esempio 2
- Problema. Set covering. Dati un set ed alcuni subset: S={x1, x2… xn }; SS1={xp,
… xq }, ..SSm={xl, … xr }; i costi c1, c2… cm; trovare l’insieme di subset che copre
S (ogni elemento di S è in almeno un subset) con somma dei costi minima.

- Algoritmo esatto. Si forma la presence function:


n
Π ( ∑ qi )
j =1 i∈
Ij
qi= variabile logica TRUE se SSi fa parte della copertura
Ij= insieme degli indici dei subset che contengono xj
Si trasforma l’espressione in somme di prodotti. Ogni prodotto rappresenta una
possibile copertura per cui calcolare la somma dei costi. Complessità: i prodotti sono
O(mn), ciascuno con O(m) termini.

Esempio:
Elementi: a, b, c, d
Insiemi: (a,b), (a,b,d), (b,c), (c,d),
Variabili rappresentanti un insieme nella copertura: q1, q2, q3, q4
Presence function: (q1+ q2)( q1+ q2 + q3 ) )( q3 + q4 ) q4 =……= q1 q3 q4+ q1 q4+…
Soluzioni ottime (costi uguali degli insiemi) q1 q4, q2 q4
Soluzione ottima(costi =dimensione insieme) q1 q4

- Algoritmo approssimato. Usa una tecnica greedy (avida). Ad ogni passo si sceglie
il sottoinsieme che ha il minimo costo (a parità di costi, si sceglie quello che copre
più elementi). Si può dimostrare che:
costo della soluzione data dall’alg. approssimato
≤ 1 + lg r
costo della soluzione data dall’algoritmo esatto

60
A.Laurentini-Analisi degli algoritmi

r= massimo numero di elementi in un sottoinsieme


Complessità: O(m2n)

Esempio:
Caso precedente, costi uguali.
Prima scelta: q2 (copre 3 elementi)
Seconda scelta: q4 (copre l’unico elemento scoperto)
Soluzione: q2 q4

9.5.2 – ESEMPI DI TECNICHE PER RIDURRE I CASI ESPLORATI

• Backtracking
E’ una tecnica per esplorare l’albero della possibilità in modo da ridurre i casi da
verificare.
- Esempio.
Problema 3-coloring di un grafo (vedere se è possibile colorare un grafo con 3
colori).
Algoritmo esatto: produco tutte le possibili 3-colorazioni (3V), e verifico se sono
ammissibili.
Algoritmo che tende a ridurre i casi. Iniziamo a colorare un vertice in modo
arbitrario, e procediamo colorando gli altri vertici in tutti i modi compatibili con i
colori già assegnati. La procedura può essere rappresentata con l’attraversamento di
un albero: la radice corrisponde allo stato iniziale, ogni lato corrisponde ad una
decisione per un nuovo vertice. Se si raggiunge un nodo non colorabile, si torna
indietro (backtracking) e si esplora un altro ramo.
1B, 2G
4 3B
3R

3 4G 4B 4B
4R
5
2
5R

La soluzione è: 1B, 2G, 3B, 4G, 5R. (sono possibili altre colorazioni equivalenti).
Per un grafo generico, l’albero ha un numero esponenziale di nodi. Con questa
tecnica posso ridurre di molto quelli esplorati.
• Branch-and-bound
E’ un’idea simile per il caso in cui si cerca il massimo o il minimo di una funzione
obiettivo.
Esempio.
61
A.Laurentini-Analisi degli algoritmi

Problema: ricerca di una colorazione minima.


Algoritmo: Costruiamo, come nell’esempio precedente, un albero di scelte. Ad ogni
nuovo nodo usiamo, se possibile, uno dei colori già usati, altrimenti uno nuovo.
Procediamo in profondità, senza esplorare le varie scelte possibili. Quando abbiamo
colorato tutti i vertici, il numero di colori usati k serve da bound per l’esplorazione
delle altre possibilità: non esploro più soluzioni che richiedano più di k colori ma
effettuo un backtracking. Se trovo una nuova colorazione con k’<k colori, uso k’
come nuovo bound.
Si noti che:
- l’algoritmo potrebbe beneficiare di buone euristiche o tecniche approssimate per
trovare il bound
- si può arrestare l’algoritmo prima dell’esplorazione completa dell’albero,
ottenendo soluzioni sub-ottime

10- ALGORITMI PARALLELI

• Gli algoritmi descritti sinora sono seriali ( 1 processore, un’operazione per volta).
Si stanno diffondendo macchine a più processori, capaci di effettuare più operazioni
per volta, e quindi di eseguire algoritmi paralleli. Per sfruttare al meglio le capacità
delle nuove macchine è necessario:
- parallelizzare algoritmi esistenti
- studiare nuovi algoritmi paralleli

• Numerosi problemi ammettono algoritmi paralleli. Ad esempio:


- prodotti di matrici, ricerche, ordinamenti
- algoritmi di analisi segnali (FFT, convoluzioni, etc.)
- problemi di ottimizzazione
- algoritmi per la Computer Graphics
- problemi relativi a grafi
- problemi NP-completi (esame in parallelo di molti casi)

62
A.Laurentini-Analisi degli algoritmi

• Gli algoritmi paralleli richiedono la soluzione di ulteriori problemi di


comunicazione e sincronizzazione tra i processori.

• Ci sono vari modelli di calcolo parallelo, ad esempio:


-SIMD(Single-Instruction Multiple-Data)
-MIMD(Multiple-Instruction Multiple-Data)
Nel seguito, salvo diverso avviso, considereremo modelli MIMD.

10.1 – DEFINIZIONI GENERALI

T(n,p):= tempo di esecuzione di un algoritmo, dipendente dalla dimensione n


dell’INPUT e dal numero p di processori.

S(p):= T(n,1)/T(n,p) è lo speedup (accellerazione) dovuta ai p processori.


Evidentemente, al massimo per un utilizzo perfetto dei processori, si ha S(p)=p.
E(n,p):= S(p)/p=T(n,1)/pT(n,p) e l’efficienza (al massimo uno)

N.B. Per gli algoritmi paralleli importano le costanti moltiplicative.

Si supponga di aver trovato un certo T(n,p) con p processori. Usandone solo p/k noi
vorremmo la massima efficienza, e quindi:
T(n,p/k)≅kT(n,p)
Questo si può realizzare, in linea di massima, eseguendo su di un solo processore k
passi consecutivi dell’algoritmo (parallelism folding principle, ). L’idea potrebbe non
applicarsi in alcuni casi.

L’idea è efficace solo per efficienza elevata. Ad esempio, supponiamo:


T(n,1)=n ; T(n,n)=lgn; → S(n)=n/lng; E(n,n)=1/lgn
Se n=1024 e abbiamo p=256 processori, secondo il parallelism folding principle si
ha:
T(1024,256)=4T(1024,1024)=4lg1024=40; → S(1024)=1024/40≅25
Se invece n=1024 e abbiamo solo p=16 processori:
T(1024,16)=64T(1024,1024)=64lg1024=640; → S(1024)=1024/640<2

10.2 – ESEMPI DI ALGORITMI PARALLELI SHARED MEMORY

Il modello shared memory (memoria condivisa) assume che tutti i processori


possano accedere, con uguali tempi, ad una memoria comune (in pratica, aumentando
il numero dei processori la cosa può non essere più vera). Il calcolo avviene in passi,
di durata uguale per tutti i processori. Durante il passo, un processore effettua
un’operazione, e legge (o scrive) nella memoria condivisa.

63
A.Laurentini-Analisi degli algoritmi

Si distinguono vari sottocasi di disciplina degli accessi alla stessa locazione di


memoria:
- EREW(Exclusive-Read Exclusive-Write)
- CREW(Concurrent-Read Exclusive-Write)
- CRCW(Concurrent-Read Concurrent-Write)
Nell’ultimo caso, supponiamo che la scrittura funzioni solo se i vari processori
scrivono tutti la stessa cosa (sono possibili anche altri modelli).

• Addizione parallela binaria


- Algoritmo normale (seriale): sommo le cifre meno significative, produco il
riporto, sommo le cifre di una posizione più a destra con il riporto, ….

L’algoritmo è O(n) a causa del riporto (n è il numero delle cifre).


- Un possibile algoritmo parallelo: divido ciascuno degli addendi in due numeri da
n/2 cifre; sommo le parti meno significative e contemporaneamente quelle più
significative con e senza riporto (se le cifre non sono binarie, considero tutti i
possibili riporti); quando giunge il riporto della parte meno significativa, scelgo la
soluzione corretta per la parte più significativa. Si ha quindi, per un impiego
recursivo dell’algoritmo:
T(n,n)=T(n/2, n/2)→ T(n,n)∈O(lgn)

• Determinazione del massimo di n numeri


- Algoritmo seriale: T(n,1)=n-1
- Algoritmo parallelo: metodo del torneo di tennis:

n
n/2
n/4 lgn

2
1

T(n, n/2)=lgn
E=(n-1)/((n/2)lgn)≅2/lgn

Per migliorare l’efficienza, usiamo n/lgn processori, e dividiamo l’INPUT in n/lgn


gruppi, ciascuno con lgn elementi.
Algoritmo modificato:
64
A.Laurentini-Analisi degli algoritmi

- ciascun processore esamina serialmente ciascun gruppo trovandone il massimo;


T≅lgn
- si usa la tecnica del torneo per i vincitori(i processori sono all’inizio il doppio del
necessario); T=lg(n/lgn)≅lgn

In totale:

T(n, n/lgn)≅2lgn → E≅n/((n/lgn)2lgn) =1/2

Si dice statico un algoritmo in cui i processori hanno compiti predefiniti. L’algoritmo


del torneo modificato è statico. L’idea dell’algoritmo per aumentare l’efficienza si
può applicare a tutti gli algoritmi statici EREW.

• Determinazione del massimo di n numeri, algoritmo CRCW


Si può migliorare il tempo lgn per trovare il massimo? Sì, con tecnica CRCW.
All’inizio ci sono n celle condivise contenenti 0.
- n(n-1)/2 processori esaminano contemporaneamente tutte le n(n-1)/2 coppie, e
scrivono 1 nella cella corrispondente al numero vincente. Viene effettivamente
scritto un solo uno nella cella corrispondente al massimo.
- n processori esaminano contemporaneamente le n celle; quello che trova un 1
scrive l’indice del vincitore in una apposita cella.

L’algoritmo funziona in tempo costante, ma con bassa efficienza:


T(n, n(n-1)/2)=2→ E≅n/2n2 =1/2n

E’ possibile migliorare l’efficienza con una tecnica simile a quella del torneo
modificato. Supponiamo di avere n processori (n potenza di 2). Considero gruppi da 2
elementi. Con n/2 processori trovo n/2 massimi. Di questi faccio gruppi da 4 (n/8
gruppi). Per trovare il massimo in due passi, mi bastano 4x3/2 =6 processori per
gruppo (ne ho 8 disponibili per gruppo)………Si dimostra che, agendo su gruppi da
2, 4, 16, 256 …..elementi i processori sono sempre sufficienti, e quindi il tempo e
l’efficienza sono:
T(n,n)= 2lglgn → E≅n/(n2lglgn)

10.3 – INTERCONNECTION NETWORKS

Le interconnection networks sono modellate da grafi dove i nodi sono i processori,


collegati da un lato se c’è una connessione diretta. Ogni processore ha una memoria
locale e può accedere, tramite la rete, alla memoria di tutti gli altri processori.

È importante il diametro del grafo (lunghezza del massimo cammino tra 2 nodi).

65
A.Laurentini-Analisi degli algoritmi

Ad es., per un grafo fatto a griglia nxn, il diametro è 2n. Per un albero di profondità p,
il diametro è 2p.

Una struttura regolare per i collegamenti tra processori è l’ipercubo, un cubo d-


dimensionale con i processori ai vertici. I processori sono n=2d, gli indirizzi o indici
dei processori sono numeri binari con d bit da 0 a 2d – 1. La distanza tra due
processori è il numero di bit diversi (distanza di Hamming), il diametro è d.

• Esempio di algoritmo.
Sort di n numeri. Ci sono n processori Pi, ciascuno collegato ad un predecessore e
successore, e contenente un numero. L’algoritmo dispone il numero più piccolo in P1,
..il più grande in Pn. Ogni processore confronta il proprio numero con il vicino di
sinistra, e lo scambia se fuori ordine, e poi con il suo vicino di destra, scambiandoli se
necessario. È facile vedere che: in al massimo n-1 passi l’algoritmo termina.
Ovviamente non si può fare di meglio se si possono solo fare scambi tra elementi
contigui.

10.4 – ARCHITETTURA SISTOLICA

E’ simile ad una linea di assemblaggio: ciascun processore opera in modo cadenzato


sui dati che gli giungono dal processore precedente. Esempio: prodotto di una matrice
A per un vettore X.

xin b xout = xin +ab

a44

a43 a34

a42 a33 a24

a41 a32 a23 a14

a31 a22 a13

a21 a12

a11
66
A.Laurentini-Analisi degli algoritmi

b1 b2 b3 b4 x4 x3 x2 x1

ESERCIZI E PROBLEMI
• Si trovi un algoritmo per verificare se un grafo G(V,E) è 2-colorabile. L’algoritmo
dovrebbe essere lineare Θ(Max(V,E))
• Mostrare che ciascuno dei seguenti problemi di decisione è in NP. A questo scopo,
indicare per ogni problema quello che può essere un’ipotesi di soluzione, e mostrare
come si può verificare in tempo polinomiale che l’ipotesi è corretta.
- bin packing
- circuito hamiltoniano
- CNF-SAT
• Mostrare che il problema della 3-colorabilità può essere ridotto polinomialmente a
CNF-SAT
• Trovare un algoritmo che determini il numero cromatico di un grafo con grado dei
nodi ≤ 2.(l’algoritmo dovrebbe essere lineare in V)
• Trovare un algoritmo polinomiale per determinare se una forma CNF con
esattamente due lettere per ogni somma ha valore TRUE.
• Trovare un algoritmo polinomiale per determinare se un grafo ha una 4-clique
• Dimostrare che, se un problema di decisione bin-packing potesse essere risolto in
tempo polinomiale, allora anche il problema di ottimizzazione potrebbe essere risolto
in tempo polinomiale
• Costruire un esempio di problema Bin Packing in cui l’algoritmo approssimato usa
3 bin mentre ne bastano 2
• Qual è l’efficienza di un algoritmo parallelo per cui T(n, 1) = 100 e T(n, 10)=30?
• Qual è l’efficienza di un algoritmo per trovare il massimo di una lista di 256
elementi col metodo del torneo?
• Se i processori per il caso precedente sono solo 32, per cui i primi confronti non si
possono svolgere tutti in parallelo, quanto vale l’efficienza?

67
A.Laurentini-Analisi degli algoritmi

11- GRAFI

Servono a modellare molti problemi pratici relativi a:


- trasporti
- reti tecnologiche
- comunicazioni, protocolli
- reti elettriche, di calcolatori
- esecuzione di task, programmazione
- relazioni tra elementi d’informazione, compatibilità
- etc.

11.1 – DEFINIZIONI E PROPRIETA’

• Un grafo (graph) è un’entità astratta, una coppia di insiemi.


G(V,E):= insieme V di elementi detti nodi o vertici + insieme E di coppie di vertici,
dette anche lati.

68
A.Laurentini-Analisi degli algoritmi

Rappresentato spesso graficamente con simboli come pallini, circoletti, per i vertici, e
segmenti rettilinei o curvi che congiungono 2 vertici per i lati.
Vi possono essere informazioni associate ai nodi, ai lati, ad ambedue.

Esempio 1:
B E G

A D

C
H

F
V={A, B, C, D, E, F, G, H}
E={(A,A), (A,B), (A,D), (A,C), (C,D), (C,F), (E,G)}

• Se un lato inizia e termina nello stesso vertice, come (A,A) nell’esempio, viene
detto cappio (self-loop)

• Se vi sono più lati tra gli stessi due nodi, si parla di multigrafo, altrimenti il grafo
viene detto semplice (l’aggettivo viene di solito omesso).
Nel seguito, salvo esplicito diverso avviso, ci occuperemo di grafi semplici e senza
self-loops

• Indicheremo con V e con E il numero di vertici e di lati di un grafo (o anche con


V e con E). Per i grafi di cui ci occupiamo (semplici e senza self-loops):

E ≤V(V-1)/2 ⇒ E∈O(V2)

• Se esistono tutti i possibili lati, il grafo si dice completo, ed è E = V(V-1)/2.


Kn indica il grafo completo con n vertici. Ad es.:

K3 K4

• Grafi orientati (di(rected)-graphs):= grafi in cui i sottoinsiemi di ordine 2 (lati)


sono ordinati.

Esempio 2

69
A.Laurentini-Analisi degli algoritmi

B E G

A D

C
H

F
V={A, B, C, D, E, F, G, H}
E={ 〈A,B〉, 〈A,D〉, 〈A,C〉, 〈C,D〉, 〈C,F〉, 〈E,G〉}

• I lati che hanno un nodo come estremo si dicono incidenti nel nodo. Il numero di
lati incidenti in un nodo si dice grado (degree) del nodo ( nell’Esempio 1, C ha grado
3).
Per un grafo orientato, si distinguono un grado in uscita (out-degree) ed un grado in
ingresso(in-degree) (nell’Esempio 2, C ha out-degree=2, in-degree=1 )
Due nodi si dicono adiacenti (o contigui) se un lato li collega direttamente.

• Un grafo è pesato (weighted) se ci sono dei valori, detti pesi, associati ai lati.

• Un percorso (cammino, path) di lunghezza k è una sequenza di k+1 vertici


adiacenti. La lunghezza è il numero di lati del percorso. In Esempio 1, ACD, ACF,
ADCF, sono percorsi di lunghezze 2,2,3. Un percorso è semplice se un nodo non
figura 2 o più volte. Spesso l’aggettivo si omette.
Se il grafo è orientato, i lati possono essere percorsi solo in una direzione. Un nodo è
raggiungibile da un altro se esiste un cammino che dal primo giunge al secondo.

• La distanza tra due nodi è la lunghezza del percorso più breve tra i due nodi.

• Il diametro di un grafo è la massima distanza tra due nodi del grafo.

• Un circuito (circuit) è un percorso con nodo iniziale uguale al nodo finale. Il


circuito è semplice se non ci sono altri vertici che compaiono più di una volta ed in tal
caso si dice ciclo(cycle). In Esempio 1, ADC e ACD sono cicli. In Esempio 2 non
esistono cicli.

• Un grafo non diretto è connesso se esiste un percorso tra due nodi qualsiasi. Per
grafi diretti, si esamina il grafo trasformato in non diretto. I grafi di Esempio 1 ed
Esempio 2 non sono connessi.

• Un grafo è detto una foresta se è aciclico (senza cicli) nella sua forma non diretta.

70
A.Laurentini-Analisi degli algoritmi

• Un grafo è detto un albero se è una foresta connessa (aciclico e connesso)

B E G

A D

C
H
Esempio di foresta F

A D

Esempio di albero F

• Un albero è radicato ( rooted tree) se si sceglie un suo nodo qualunque come


radice. Gli altri nodi possono essere organizzati in livelli a seconda della distanza
dalla radice. Dall’albero dell’esempio precedente si possono estrarre 5 alberi radicati
a seconda della scelta del nodo radice, ad esempio, scegliendo come radici C e D:

radice C D

livello 1 C
A D F

livello 2 B A F

livello 3 B

• G(V’, E’) è un sottografo di G(V, E) se V’⊆V ed E’⊆E

• G(V’, E’) è un albero ricoprente (spanning tree) se è un sottografo aciclico con


tutti i nodi: V’=V ed E’⊆E. Esempi:

71
A.Laurentini-Analisi degli algoritmi

Grafo Albero ricoprente 1 Albero ricoprente 2

• La foresta ricoprente (spanning forest) è un sottografo con tutti i nodi di un grafo


non connesso.

11.2 – RAPPRESENTAZIONE DEI GRAFI

• Matrice di adiacenza

1 se (Vi Vj)∈E (se grafo orientato, +1 o -1 a seconda della direzione)


aij=
0 se (Vi Vj)∉E
E’ simmetrica per un grafo non orientato.
Lo spazio occupato, per grafi orientati o non, è Θ(V2).

Se il grafo è pesato:
W(Vi Vj) se Vi e Vj sono collegati (positivo o negativo se grafo orientato)
aij=
una costante se non collegati (ed es, 0 se pesi=capacità, ∞ se tempi di
percorrenza
B E G

A D

C
H

A B C D E F G H
A 1 1 1 1 0 0 0 0
B 1 0 0 0 0 0 0 0
C 1 0 0 1 0 1 0 0
D 1 0 1 0 0 0 0 0
E 0 0 0 0 0 0 1 0
F 0 0 1 0 0 0 0 0
G 0 0 0 0 1 0 0 0
H 0 0 0 0 0 0 0 0

72
A.Laurentini-Analisi degli algoritmi

• Liste di adiacenza
Per ogni nodo si memorizza la lista dei nodi adiacenti. Ad esempio, per il grafo
precedente:

A A B C D Nil

B A Nil

C A D Nil

A
D A CD Nil

E G Nil

A
F C Nil

G E Nil

H Nil

Altro es. per grafo pesato:

1 2 25 Nil 2 6
25
14 Nil 14
2 3 10 4
1 10 4
3 1 5 Nil
5 18
3
4
A 2 6 3 18 Nil

Lo spazio occupato, per grafi orientati o non, è Θ(V+E).

11.3 – CHIUSURA TRANSITIVA

Si abbia un grafo G(V, E) orientato o non orientato. Si definisce chiusura transitiva


(transitive closure) del grafo dato il grafo CT(V’, E’) che ha le proprietà seguenti:
- V’=V

73
A.Laurentini-Analisi degli algoritmi

- E’ contiene il lato ViVj se nel grafo G(V, E) esiste un percorso dal nodo Vi al
nodo Vj .
Esempio:

B C B C

A E A E

D D
G(V, E) CT(V’, E’)

Lo stesso nome (chiusura transitiva) prende la matrice di adiacenza di CT(V’, E’)

• Algoritmo 1 per la costruzione di CT


Consideriamo la matrice di adiacenza A di G(V, E) come una matrice Booleana
(1=TRUE, 0=FALSE). Moltiplichiamo la matrice per se stessa usando somma e
prodotto Booleani (OR ed AND) al posto di somma e prodotti normali:

A2=AxA
a2ij=( ai1 AND a1j)OR( ai2 AND a2j)OR……….

a2ij=1 se esiste un percorso di lunghezza 2 tra il nodo Vi e il nodo Vj.

Analogamente si definiscono le matrici

A3=AxA2 ; A4=AxA3 etc.

i cui elementi sono 1 se esistono percorsi di lunghezza 3, 4, etc., tra il nodo Vi e il


nodo Vj. Ad esempio, per il grafo precedente si ha:

A B C D E A B C D E
A 0 0 1 1 0 A 0 0 0 1 1
B 0 0 1 0 0 B 0 0 0 1 1
2
A= C 0 0 0 1 1 A= C 0 0 0 1 1
D 0 0 0 0 1 D 0 0 0 1 0
E 0 0 0 1 0 E 0 0 0 0 1

Per avere la matrice CT, i cui elementi sono 1 se esiste un percorso di lunghezza
qualunque tra due nodi, e’ sufficiente sommare logicamente (OR) tutte le matrici:

CT= A+A2 + A3+……… An

74
A.Laurentini-Analisi degli algoritmi

Per il grafo dell’esempio:

A B C D E
A 0 0 1 1 1
B 0 0 1 1 1
CT= C 0 0 0 1 1
D 0 0 0 1 1
E 0 0 0 1 1

N.B. Se ci sono n nodi, I cammini sono lunghi al max. n-1 se tra due nodi Vi e Vj di
indici i≠j, sono lunghi al max. n se i=j.

L’algoritmo richiede Θ(V3) operazioni per ogni matrice, ed e’ quindi Θ(V4).

● Algoritmo 2 (di Warshall)


Definiamo le matrici Pk :
1 logico (TRUE) se se esiste un cammino tra Vi e Vj con indici ≤k, esclusi i e j
k
p ij:=
0 logico (FALSE) altrimenti Vk+1

Da Pk si puo’ ottenere Pk+1 nel modo seguente:


pkij = 1, oppure
pk+1ij = 1 se Vi Vj
pki,k+1 = 1 e pkk+1,j = 1 ≤k

Si puo’ costruire CT= Pn a partire da P0 = A con un algoritmo che usa:


- un loop esterno con indice k eseguito Θ(V) volte
- un loop interno con indici i e j da eseguire Θ(V2) volte:

pk+1ij = (pkij )OR((pki,k+1)AND(pkk+1,j))

In totale l’algoritmo e’ Θ(V3)

11.4 – VISITA DI UN GRAFO (GRAPH TRAVERSAL)

Molti algoritmi richiedono di visitare tutti i vertici di un grafo (una sola volta)
passando da un vertice all’altro tramite lati del grafo. La visita serve anche a trovare
componenti connessi. Esistono due algoritmi principali:
- visita in profondità( depth first)
- visita in ampiezza (breadth first)
Si tratta di costruire un albero ricoprente.

75
A.Laurentini-Analisi degli algoritmi

• Visita in profondità
Descrizione informale dell’algoritmo. Parto da un nodo qualsiasi, lo contrassegno
come visitato, vado ad uno qualunque dei nodi adiacenti, lo contrassegno….
- mi fermo se tutti i nodi sono contrassegnati
- se non ho finito, e tutti i nodi adiacenti sono contrassegnati, torno al nodo
precedentemente visitato e controllo i nodi adiacenti.

Il risultato (ordine di visita) dipende da scelte arbitrarie (in pratica legate alla struttura
dati). Esempio:
A 1 A
2 8
B C D B 11
C D

E I M 3 7 I
H E 6 M 10
H
F L 4 F L
G 9
5 G
a) Realizzazione recursiva:

procedure DepthFirst(V)
begin
visita e marca V
while c’è un vertice W adiacente non marcato do
DepthFirst(W)
end{while}
end{ DepthFirst }

b) Realizzazione non recursiva(usa uno stack):


procedure DepthFirst(V)
begin
inizializza lo Stack(vuoto)
visita, marca, PUSH V
while Stack non è vuoto do
while c’è un vertice W adiacente a TopofStack non marcato do
visita, marca, PUSH W
end{while}
Pop TopofStack
end{ while }
end{ DepthFirst }

76
A.Laurentini-Analisi degli algoritmi

L’algoritmo tocca tutti i vertici di un grafo connesso. Per assurdo, supponiamo che
uno o più vertici non siano marcati. Poiché il grafo è connesso, almeno uno dei vertici
non marcati è adiacente ad uno marcato: questo però è impossibile.
N.B. Se il grafo non è connesso, bisogna applicare l’algoritmo tante volte quanti sono
i pezzi non connessi

Complessità. Uso la rappresentazione del grafo liste di adiacenza. Per ogni sottolista
dei nodi connessi ad un nodo dato metto un puntatore che mi indica a che punto sono
arrivato nella scansione. Ogni lista è percorsa al massimo una volta, ed ogni lato al
massimo due volte (è presente in due sottoliste). Quindi l’algoritmo è O(V+E).
Se uso invece la matrice di adiacenza, l’algoritmo è O(V2).

• Visita in ampiezza (larghezza)


Descrizione informale dell’algoritmo. Parto da un nodo qualsiasi, e visito prima tutti
quelli a distanza 1, poi quelli a distanza 2, etc. Esempio:
A 1
A
2 4
B C D B 3
C D
5
E I M E 6 I
H M 8
H
11
F L 7
G 9 F L
10 G
Possibile realizzazione (uso una coda).

procedure BreadthFirst(V)
begin
inizializza la Coda(vuota)
visita e marca V, metti V in coda
while la coda non è vuota do
rimuovi il primo elemento X della coda
for ogni vertice W adiacente a X e non marcato, do
visita e marca W, metti W in coda
end{for}
end{ while }
end{ BreadthFirst }

L’ordine di visita è legato alla struttura dati usata.

Complessità. Usando liste di adiacenza, l’algoritmo è O(V+E).


Se uso invece la matrice di adiacenza, l’algoritmo è O(V2).

77
A.Laurentini-Analisi degli algoritmi

A
D C B

E D C

I E D

L M I E

F G H L M I

F G H L M

F G H L

F G H

F G

Evoluzione della coda dell’esempio

11.5 – ALBERO RICOPRENTE MINIMO (MINIMUM SPANNING TREE)

Definizione. Dato un grafo pesato non orientato, MST:= albero ricoprente i cui lati
hanno somma dei pesi minima.
I pesi debbono essere ≥0, altrimenti si può far tendere a -∞ la somma dei pesi
percorrendo più volte un loop. Nei casi pratici, in cui i pesi rappresentano costi, tempi
di percorrenza, etc., sono sempre positivi
N.B. Possono esserci più MST per uno stesso grafo:

2 1 2 1 1

2
2

• Algoritmo di Dijkstra/Prim

78
A.Laurentini-Analisi degli algoritmi

Descrizione. E’ un algoritmo greedy(avido), che ricerca un ottimo locale ed ottiene


l’ottimo globale.

1) parto da un vertice arbitrario, che inserisco nell’albero


2) Ad ogni passo aggiungo all’albero un nuovo vertice. I vertici sono di tre categorie:
- quelli già scelti
- quelli a distanza 1 da quelli già scelti
- tutti gli altri
Scelgo tra i vertici a distanza 1 quello che ha il collegamento con uno già scelto di
costo minimo.

Esempio:
MST
A 2 B 2
A B
7 4 7
3 6 Scelgo il 3
F G 3 nodo A ⇒ F G
5 1 2 C
H
6 I 4 2
8
2
E D
1
MST
A 2 B
Scelgo il Scelgo il
7 4
nodo B ⇒ 3 6 nodo G ⇒
F G
C

MST
A 2 B Scelgo il nodo I ⇒
7 4 …….. 79
3
F G 3
1 C
A.Laurentini-Analisi degli algoritmi

A 2 B
3
F G 2
5 1 C
H
I 2
2
E D
1

L’algoritmo porta all’ottimo globale.


Dimostrazione. Per induzione: supponiamo di avere ad ogni passo un sotto-insieme
connesso di lati del MST. Dimostriamo che, aggiungendo un nuovo lato vw secondo
la regola dell’algoritmo, ottengo ancora un sotto-insieme connesso di lati del MST.
Per assurdo, se aggiungendo vw non ho costruito un MST, deve esistere un
collegamento tra v e w di costo minore . Ma ciò è impossibile, perché qualunque altro
collegamento deve includere un lato come pq, che da solo ha peso maggiore di vw.

w
v

p q

Possibile realizzazione:

procedure MST(Radice, G)
begin
for tutti i nodi V≠Radice, do
dist(W)=peso(V, Radice), oppure dist(v)=∞ se V non collegato a Radice
end{for}
while ci sono nodi da inserire in MST do
scegli il nodo W con dist(W) minima, aggiungilo a MST
for tutti i nodiV restanti do
80
A.Laurentini-Analisi degli algoritmi

dist(V)=min {dist(V), peso(V,W}


end{for}
end{ while }
end{ MST}

Complessità. Il primo loop di inizializzazione deve essere eseguito V-1 volte. Il


secondo loop ha un loop più interno. Quello esterno si esegue V-1 volte, quello
interno richiede nel caso peggiore di esaminare tutti i nodi non ancora aggiunti a
MST. In totale, l’algoritmo è O(V2).

Algoritmo più efficiente. Usa una struttura dati detta coda prioritaria (si veda più
avanti). E’ O(V+ElgV)

11.6 – ALBERO DEI CAMMINI MINIMI (SHORTEST PATH)

Si abbia un grafo pesato, con pesi positivi, eventualmente orientato.


Problema: Trovare il percorso tra due nodi con somma dei pesi minima.
Si parla anche di percorso più breve tra due nodi. In pratica i pesi possono essere
lunghezze, tempi, costi.
Si potrebbe pensare che un MST sia un albero di cammini minimi, ma non è vero in
generale: nell’esempio precedente il cammino AC sull’MST ha costo 9, ma il
percorso ABC sul grafo originale costa 6

• Algoritmo (di Dijkstra)

Descrizione. E’ un algoritmo che aggiunge un nodo per volta, e determina i cammini


minimi tra un nodo V0 e tutti gli altri costruendo un albero SP a partire da V0.

Ad ogni passo ho tre insiemi di nodi : quelli già scelti, quelli adiacenti ad uno di
quelli già scelti, gli altri.

a) Inizializzo l’albero SP inserendo V0. Attribuisco a tutti gli altri una distanza da V0
nel modo seguente:
d(V0Vi) = peso (V0Vi) se Vi è adiacente a V0,
d(V0Vi) =∞ altrimenti
b) scelgo ed aggiungo a SP ad ogni passo il nodo Vx per cui d(V0Vx) è minima.
Aggiorno le distanze per i nodi adiacenti a Vx :
d(V0Vi) =min{ d(V0Vi) ; d(V0Vx)+peso(VxVi) }
Continuo con b) finché ci sono nodi

L’algoritmo produce l’albero dei cammini minimi per il motivo seguente.

81
A.Laurentini-Analisi degli algoritmi

Vy Vx
SP
Vp
V0
Vq

Vr

Per induzione, suppongo che SP sia un albero di cammini minimi, e dimostriamo che
lo resta aggiungendo Vx al percorso minimo V0 ……Vy . Infatti ho scelto Vx perché
d(V0Vx) è minore di quelle da altri nodi adiacenti a SP, come d(V0Vp), d(V0Vq),
d(V0Vr). Il percorso V0 ……VyVx ha costo minimo perché qualunque altro percorso
da V0 a Vx deve includere d(V0Vp) oppure d(V0Vq) oppure d(V0Vr).

Complessità. Un’implementazione banale, del tipo visto per l’albero ricoprente


minimo, è O(V2). Si possono migliorare le cose usando una coda prioritaria,
ottenendo un algoritmo O(V+ElgV).

Esempio: costruzione dell’albero dei cammini minimi a partire dal nodo A.

A 2 B 2
A B
7 4 7
3 6 3
F G 3 F G
5 1 2 C
H
6 I 4 2 d(AB)=2
8
2 d(AG)=3
E D
1 d(AF)=7
scelgo AB

A 2 B
7 d(AG)=Min{d(AG);d(AB)+peso(BG)}=3
3 6 4
d(AC)= Min{∞;d(AB)+peso(BC)}=6
F G d(AF)=7
C
scelgo AG

A 2 B
82
7 4
3
F G 3
C
A.Laurentini-Analisi degli algoritmi

d(AF)=7
d(AC)=6
d(AI)=MIN{∞;d(AG)+peso(GI)}=4
d(AH)= MIN{∞;d(AG)+peso(GH)}=6
scelgo GI

d(AE)= MIN{∞;d(AI)+peso(IE)}=6
d(AF)= MIN{d(AF);d(AI)+peso(IF)}=7
d(AH)= MIN{d(AH);d(AI)+peso(IH)}=6
d(AC)=6
scelgo E…………..

………..ed alla fine:

A 2 B
7 4
3
F G 3 L’albero dei cammini minimi
1 C
H
I
2
E D
1
11.7 – ALCUNE DEFINIZIONI

• Grafo bipartito := i suoi vertici possono essere divisi in due sottoinsiemi tali che
ogni vertice di un sottoinsieme è adiacente ad un vertice dell’altro.

I grafi bipartiti completi (ogni nodo di un sottoinsieme è collegato ad un nodo


dell’altro) si indicano con Knm, dove n e m sono i numeri di nodi nei due insiemi. Ad
esempio, questo è K33:

83
A.Laurentini-Analisi degli algoritmi

• Isomorfismo . Due grafi G(V,E) e G(V’,E’) sono isomorfi se i vertici di V possono


essere messi in corrispondenza biunivoca con i vertici di V’ in modo tale che anche i
lati di E siano in corrispondenza biunivoca con quelli di E’.
In sostanza i due grafi sono uguali, hanno solo nomi diversi per i nodi.
Esempio: B 2

A⇔1
A C 1 3
B⇔2
C⇔3 4
D⇔4 D
Esistono in questo caso altri isomorfismi (4x3x2=24 in tutto).
Non si sa se il problema della verifica dell’isomorfismo sia in P o in NP.
Se il grafo è planare, il problema è in P.

• Automorfismo:= è l’isomorfismo di un grafo con se stesso.


Ad esempio, il grafo precedente (K4) è automorfo in tutti i modi possibili (3x2=6)

• Omeomorfismo. Due grafi sono omeomorfi se isomorfi a meno di nodi di grado 2.


Esempio di grafi omeomorfi:

11.8– GRAFI PLANARI

Un grafo è una struttura astratta, ma talora i lati hanno significato di collegamento


fisico ( strada, percorso, collegamento elettrico….). In questi casi è importante la
planarità.
Definizione. Un grafo è planare se si può disegnare su di un piano (o sulla superficie
topologicamente equivalente di una sfera) senza che due lati si intersechino.
Esempio.

⇒ ⇒

K4

K4 è planare, e può essere disegnato con tutti i lati rettilinei, come tutti i grafi planari.
I grafi completi da K5 in poi non sono planari. E’ facile verificare che K5 non è
planare:
2 2 2
1 3 1 3 1 3 84

5 4 5 4 5 4
A.Laurentini-Analisi degli algoritmi

Qualunque grafo Kn per n>5 contiene K5 come sottografo e quindi non è planare.

• Teorema di Kuratowski(1930). Un grafo è planare se e solo se non contiene alcun


sottografo omeomorfo a K5 o K33.

Questo teorema suggerisce un algoritmo di verifica della planarità: è sufficiente


considerare tutti gli insiemi di 5 vertici per vedere se formano K5 , e tutte le coppie di
insiemi di tre vertici per vedere se formano K33. L’algoritmo è O(n6).

Esistono algoritmi lineari, e quindi ottimi, per la verifica della planarità (ad. es,
Hopcroft e Tarjan, 1974). La loro descrizione è assai complicata.

Esistono anche algoritmi lineari per il planar embedding, cioè per disegnare senza
intersezioni sul piano un grafo planare.

• Faccia di un grafo planare


E’ un insieme di punti connessi sul piano dove ho disegnato il grafo planare.
Esempi:

f3
f1 f2 f2
f4 : faccia f1 f3 85

esterna
infinita
A.Laurentini-Analisi degli algoritmi

• Teorema di Eulero(1752)
Se un grafo planare connesso ha V nodi, E lati e F facce, si ha:

V+F=E+2

Dimostrazione. Per induzione su E. Il teorema è vero per E=0, V=1, F=1.


Supposto vero per un certo valore di E, dimostriamolo per E+1. Ci sono 3 possibilità
per il nuovo lato:
- è un cappio. In tal caso, F+1→F, E+1→E, l’eguaglianza rimane verificata
- congiunge due nodi già esistenti. In tal caso, F+1→F, E+1→E, l’eguaglianza
rimane verificata
- congiunge un nodo vecchio ed uno nuovo. F rimane invariato, V+1→V, E+1→E,
l’eguaglianza rimane verificata

N.B. Il teorema si applica a poliedri convessi o isomorfi a poliedri convessi. Infatti gli
spigoli ed i vertici di un poliedro convesso si possono proiettare su di una superficie
sferica formando un grafo planare.

• Teorema di Eulero per grafi planari non connessi


Se il grafo ha K componenti non connessi, si ha:

V+F=E+K+1

N.B. Per un grafo planare semplice connesso:


se V ≥ 3 ⇒ E ≤ 3V − 6 ⇒ E ∈ O (V )
Dato che si ha anche:
E ≥V −1
abbiamo che, per un grafo planare semplice:
E ∈ Θ(V )

• Grafo duale di un grafo planare


Il grafo duale G* di un grafo planare G ha:
- un vertice per ogni faccia di G
- una faccia per ogni vertice di G
- un lato per ogni lato di G, che collega I due vertici corrispondenti alle 2 facce
separate dal lato di G

86
A.Laurentini-Analisi degli algoritmi

Il duale del grafo duale è il grafo di partenza: G** =G


Esempio:

• Genus di un grafo
Un grafo non planare potrebbe essere disegnato senza lati che si incrociano su
superfici diverse dal piano o dalla sfera.
Ad esempio, K5 e K33 possono essere disegnati senza incroci sulla superficie di un
toro circolare, o di una sfera con manico (vedi figura successiva).
Si dice che K5 e K33 hanno genus G= 1: sono disegnabili senza incroci su di una
superficie con un foro passante. Se sul piano ci fossero almeno 2 incroci, sarebbe
necessaria una superficie con 2 fori passanti.

Il teorema di Eulero si può generalizzare:

V+F=E+(2-2G)

87
A.Laurentini-Analisi degli algoritmi

Due grafi non planari di genus 1 si possono disegnare senza intersezioni su di un toro.

11.9– GRAFI EULERIANI E SEMI-EULERIANI

Il problema fu posto e risolto da Eulero (1707-1783) con riferimento ai ponti di


Koenigsberg sul fiume Pregal( Prussia, 1736).
88
A.Laurentini-Analisi degli algoritmi

E’ possibile trovare un percorso che attraversi i sette ponti una sola volta e torni al
punto di partenza? A destra il grafo che modella il problema.

• CicloEuleriano di un grafo G:= un cammino chiuso che contiene una sola volta tutti
i lati del grafo

• Grafo Euleriano :=un grafo che contiene (almeno) un ciclo Euleriano

• Teorema: Condizione necessaria e sufficiente perché un grafo connesso sia


Euleriano è che i gradi di tutti i nodi siano pari.

Che la condizione sia necessaria è ovvio: da ogni nodo in cui entro, incluso il nodo di
partenza, dovrò uscire. Ne consegue che il problema dei ponti di Koenigsberg non ha
soluzione.
Per dimostrare che la condizione è sufficiente, diamo una prova costruttiva, che
definisce un algoritmo per la costruzione del ciclo Euleriano.
a) Passo 1. Si decompone il grafo in una serie di cicli semplici. A questo scopo uso
alternativamente due algoritmi:
- Algoritmo 1. Determina un ciclo, usando una visita in profondità arrestata non
appena si torna al primo nodo ( si può dimostrare che almeno un ciclo esiste
sempre in un grafo con nodi di grado≥2 ). Elimino i lati visitati ed eventuali nodi
isolati. Tutti i nodi rimasti sono ancora di grado pari.
- Algoritmo 2. Se ci sono ancora lati, determino i componenti connessi del grafo (
eliminando i lati il grafo può diventare non connesso). La determinazione dei
componenti connessi si può fare applicando una visita (in ampiezza o profondità)
tante volte quanti sono i componenti connessi. Si torna quindi ad applicare
l’Algoritmo 1 ai componenti connessi trovati.
b) Passo 2. Si crea il ciclo Euleriano percorrendo i cicli trovati in a) con la regola
seguente. Si inizia a percorrere un ciclo qualunque, eliminando ogni volta lati già
percorsi. Se da un nodo parte un lato di un altro ciclo, si percorre questo. Se nel
percorrere il nuovo ciclo ne incontro un altro nuovo, si inizia a percorrerlo. Se si
incontra un ciclo vecchio (già percorso parzialmente), si continua con il ciclo
corrente.

89
A.Laurentini-Analisi degli algoritmi

Complessità. L’Algoritmo 1 considera una sola volta tutti i lati ed è lineare. Lo stesso
è per l’Algoritmo 2, e quindi per il passo 1. Anche il passo 2 considera ogni lato una
sola volta, ed è lineare. Nel passo 2, i nuovi cicli incontrati vanno messi in uno
STACK, da cui si estraggono ogni volta che il ciclo corrente è terminato.

Esempio
Costruzione dei cicli:
1 3 1
2 3
2

4 5
Ciclo 1

8 7
8
6 6
1 3
2 1
3
Ciclo 2
4 5
4 5
8 7
6
2 2

4 5 4 5 Ciclo 3

8 7
6 6
4 4
Ciclo 4
8 7 7
8
Costruzione del ciclo Euleriano.
Si parta (arbitrariamente) dal nodo 1 del Ciclo 1. Si incontra subito il Ciclo 2 che si
inizia a percorrere in una direzione arbitraria, ad es. 13. In 3 si incontra il Ciclo 1
(vecchio) e si continua percorrendo il lato 35 del Ciclo 2. In 5 si incontra il nuovo
Ciclo 3, e si percorre 56. In 6 non si incontrano cicli nuovi, e si percorre 64. In 4 si
incontra il nuovo Ciclo 4, che si percorre completamente (lati 47,78,84). Completato
il Ciclo 4, si torna a percorrere il Ciclo 3 (lati 42, 25). In 5 si torna sul Ciclo 2(lati
54,41), ed in 1 si torna a percorrere il Ciclo 1 (lati 12, 23, 36, 68, 81).
In conclusione, la sequenza dei lati è :
13, 35, 56, 64, 47, 78, 84, 42, 25, 54, 41, 12, 23, 36, 68, 81
90
A.Laurentini-Analisi degli algoritmi

N. B. Esistono numerosi cicli Euleriani, per le decisioni arbitrarie prese sia nel Passo
1 che nel Passo 2.
N.B. Se il grafo è orientato, la condizione è che per ogni nodo sia: in-degre=out-
degree

• Algoritmo di Fleury. Per trovare un ciclo Euleriano si può anche applicare


l’algoritmo di Fleury:
si parte da un nodo arbitrario, e si visita il grafo in profondità scegliendo ogni volta
un lato non percorso, non percorrendo istmi del grafo costituito dai lati non ancora
percorsi se ci sono altre scelte.

Un istmo è un lato che, se soppresso, rende non connesso un grafo connesso.

• Cammino Euleriano di un grafo G:= un cammino(vertice iniziale e finale diversi)


che contiene una sola volta tutti i lati del grafo

• Grafo Semi-Euleriano :=un grafo che contiene (almeno) un cammino Euleriano

• Teorema. Condizione necessaria e sufficiente perché un grafo connesso sia Semi-


Euleriano è che i gradi di tutti i nodi siano pari, tranne due.

Infatti, aggiungendo un lato tra i due nodi A e B di grado dispari, si ottiene un grafo
Euleriano con almeno un ciclo Euleriano. Se si sopprime questo lato, il ciclo diventa
un cammino che inizia in A e finisce in B (e viceversa, se il grafo non è orientato).

Esempi.

91
A.Laurentini-Analisi degli algoritmi

Grafo non Euleriano(tutti nodi di grado dispari) Grafo Semi-Euleriano (due soli
nodi dispari)

11.10– GRAFI HAMILTONIANI E SEMI-HAMILTONIANI

I percorsi Hamiltoniani riguardano la visita dei vertici, e non dei lati. Il problema fu
introdotto dal matematico irlandese Hamilton(1859).

• Cammino Hamiltoniano:=un percorso (di V-1 lati)che passa per tutti i vertici del
grafo una sola volta

• Ciclo Hamiltoniano:=un ciclo (di V lati)che passa per tutti i vertici del grafo una
sola volta

Si definiscono grafi Hamiltoniani o semi-Hamiltoniani quelli che contengono un


ciclo o un cammino di Hamilton.

Esempi

Grafo non Hamiltoniano Grafo semi-Hamiltoniano Grafo Hamiltoniano

Si consideri il problema di decisione: dato G(V, E), esiste un cammino


Hamiltoniano? E’ stato dimostrato che il problema è NP-completo.

Dirac dimostrò una condizione sufficiente:


-un grafo è Hamiltoniano se V≥3 ed il grado di tutti i nodi è ≥ V/2

• Problema del commesso viaggiatore(traveling salesperson): trovare, per un grafo


pesato, il ciclo Hamiltoniano di costo totale (somma dei pesi) minimo.

Il problema è difficile almeno quanto quello di trovare un ciclo Hamiltoniano.

92
A.Laurentini-Analisi degli algoritmi

Se il grafo è Euclideo (le distanze tra i vertici del grafo disegnato sono uguali ai pesi),
o almeno vale la disuguaglianza tringolare peso(AB)≥ peso(AC)+ peso(CB), esiste un
algoritmo polinomiale ad approssimazione garantita (costo≤1.5costoMin)

11.11– CLIQUE

Si dice clique (cricca)di un grafo un suo sottografo massimo completo. Massimo


significa non contenuto in un altro sottografo completo.
Si dice clique number di un grafo la massima cardinalità (massimo numero di nodi)
di una clique.
Per indicare il numero di vertici si usa anche il termine k-clique

N.B. Su alcuni testi il termine clique indica un qualunque sottografo completo.

Il problema :dato un grafo, trovare se contiene un sottografo completo di k o più


vertici è NP-completo

11.12– COLORABILITA’

Come già accennato, la colorazione di un grafo deve avvenire con colori diversi per
nodi adiacenti, ed il numero cromatico χ(G) è il minimo numero di colori richiesti per
colorare un grafo.
Evidentemente:
χ(G)≥clique number

Il problema della k-colorabilità per k ≥3 e del numero cromatico sono NP-completi .

Esistono vari algoritmi approssimati polinomiali di colorazione, ma nessuno ad


approssimazione garantita, e non è probabile che esistano. E’ stato dimostrato che ,
se esistesse un algoritmo di colorazione approssimato che usasse al massimo il
doppio del numero cromatico, si potrebbe ottenere il numero cromatico in tempo
polinomiale (e quindi sarebbe P=NP).

• Esempio di problema schematizzabile come ricerca di numero cromatico


Problema degli esami: si abbia un periodo destinato ad e esami con un certo numero
t di time slots assegnabili ai vari esami da mettere a calendario (ad es. 3 periodi al
giorno per una settimana, t=15). Alcuni esami non sono compatibili tra di loro, e
devono essere posti in slots diversi. E’ possibile calendarizzare gli esami negli slots
disponibili rispettando le incompatibilità?
Si costruisca un grafo con e vertici, collegati da un lato se incompatibili.
Un’assegnazione degli esami agli slots corrisponde ad una colorazione: esami

93
A.Laurentini-Analisi degli algoritmi

incompatibili devono corrispondere a nodi con colori diversi. L’assegnazione è


possibile se il numero cromatico è ≤ t.

• Colorabilità di grafi planari.Per più di un secolo, nessuno è stato in grado di


dimostrare, o di smentire con un esempio, la congettura dei 4 colori, secondo cui 4
colori bastano a colorare qualunque grafo planare(proposta da Guthrie nel 1852).
E’ chiaro che 3 colori non sono sufficienti (per esempio, a colorare K4 che è planare);
da tempo era stato dimostrato che 5 colori sono sempre sufficienti.
Si noti che il problema della colorazione di un grafo planare è equivalente al
problema della colorazione di una carta geografica. La carta geografica può essere
trasformata in un grafo planare facendo corrispondere ad ogni stato un nodo e ad ogni
confine un lato.

Solo recentemente(Appel e Haken 1976) è stata trovata una dimostrazione. Tuttavia


la dimostrazione è molto complessa, e ha richiesto l’uso di un calcolatore per
esaminare un gran numero di alternative. Sono state proposte varianti sempre assai
complesse.

Manca tuttora una dimostrazione semplice.

11.13– DOMINANZA

Insieme dominante: = insieme di vertici di un grafo G non orientato tale che ogni
altro vertice è adiacente ad un vertice dell’insieme.

Insieme dominante minimale (non riducibile): = insieme di vertici di un grafo G non


orientato tale che:
- ogni altro vertice è adiacente ad un vertice dell’insieme
- nessun suo sottoinsieme è un insieme dominante.

Insieme dominante minimo: = insieme dominante di cardinalità minima di un grafo G.


Il numero di nodi α(G) dell’insieme dominante minimo è detto numero di
dominanza.

a
Esempio.
{adef},{adeb} : insiemi dominanti d
{fde}{abc}: insiemi dominanti minimali f e
c b
94
A.Laurentini-Analisi degli algoritmi

{fb}{fa}: insiemi dominanti minimi

Diversi problemi si possono schematizzare con gli insiemi dominanti. Ad esempio, il


grafo può schematizzare una rete di trasmissione, e l’insieme dominante minimo le
stazioni di trasmissione che coprono tutta la rete.

Un insieme dominante deve contenere, per ogni nodo: o il nodo stesso, o uno dei soi
adiacenti. Per costruire tutti gli insiemi dominanti minimali, tra cui scegliere il
minimo, si può usare il seguente algoritmo (simile a quello per il set covering).
- Si costruisce un’espressione logica formata dal prodotto logico (AND) di tante
somme logiche(OR) quanti sono i nodi. Ogni somma contiene le variabili che
rappresentano la presenza di un nodo e dei suoi adiacenti
- Si minimizza l’espressione logica come somma di prodotti, riducendola agli
implicanti principali. Ognuno di essi rappresenta un insieme dominante minimale.

Il problema è NP-completo

Esempio

(a+b)(b+a+c+d)(d+b+c)(c+d+b)=b+ac+ad a d
b c
Il primo termine indica che, per coprire a, ci vuole a stesso o b,
il secondo che per coprire b ci vogliono a oppure b oppure c oppure d, etc.
In questo caso il numero di dominanza è 1.

Esempii di problemi schematizzabili mediante insiemi dominanti

Problema della scacchiera


Sia data una scacchiera (8X8 caselle). Si vuol disporre il numero minimo di regine
che copre ( tiene sotto scacco) tutte le caselle.

Ad ogni casella si associa il nodo di un grafo. Due nodi sono collegati da un lato se
una regina su uno tiene sotto scacco l’altro (le caselle corrispondenti ai nodi devono
stare sulla stessa linea orizzontale, verticale o diagonale). Il problema è così ridotto al
trovare un insieme dominante minimo del grafo

Una delle soluzioni:

95
A.Laurentini-Analisi degli algoritmi

Problema dei parcheggi


Si supponga di avere la pianta di una rete stradale urbana con parcheggio a
pagamento.
Vogliamo sistemare agli incroci il minimo numero di macchinette distributrici di
biglietti per il parcheggio in modo tale che almeno una sia visibile da ogni punto
delle strade.
Soluzione: Se le strade sono rettilinee tra un incrocio e un altro, la zona si può
schematizzare con un grafo, dove i nodi sono gli incroci e le strade i lati. Gli incroci
dove sistemare il minimo numero di macchinette corrispondono ad un insieme
minimo dominante.

Un teorema sugli insiemi dominanti


Dato un grafo senza nodi isolati, se l’insieme di nodi ID è un insieme dominante
minimo, anche l’insieme complementare V-ID è un insieme dominante minimo.
Ne segue:
- ogni grafo senza nodi isolati ha almeno due insiemi dominanti
- l’insieme dominante minimo contiene V/2 nodi

11.14– INDIPENDENZA

Un insieme di vertici di un grafo G è un insieme indipendente(independent set) se


non comprende vertici adiacenti. Un insieme è dipendente se almeno due nodi sono
adiacenti.

Un insieme indipendente è massimale se diventa dipendente aggiungendo un nodo


qualunque.

96
A.Laurentini-Analisi degli algoritmi

f
e g
c
a h

Esempio. (b,e,g) è un insieme indipendente, ma non massimale (si può aggiungere d)


(b,d,e,g) , (b,h), (e,c) sono massimali (non si può aggiungere nessun nodo).

Si cercano di solito gli insiemi indipendenti massimi, quelli cioè con il massimo
numero di nodi (nell’esempio sono massimi (b,d,e,g) , (a,c,f,h)). Il loro numero di
nodi è il numero di indipendenza β(G).

Il problema è NP-completo

Esempii di problemi schematizzabili come ricerca di insiemi indipendenti massimi

Esempio 1

Si vogliono invitare ad una festa il massimo numero di amici. Qualcuno di loro ha


però litigato con qualcun altro, e non si possono invitare insieme.
Si associa ad ogni amico un nodo, ed un lato ad ogni coppia di amici che ha litigato.
Il problema è ridotto alla ricerca dell’insieme indipendente massimo.

Esempio 2

Sia data una scacchiera (8X8 caselle). Si vuol disporre il numero massimo di regine
che non si danno scacco a vicenda.
Ad ogni casella si associa il nodo di un grafo. Due nodi sono collegati da un lato se
una regina su uno tiene sotto scacco l’altro (le caselle corrispondenti ai nodi devono
stare sulla stessa linea orizzontale, verticale o diagonale). Il problema ridotto al
trovare un insieme indipendente massimo del grafo

Una delle soluzioni:

97
A.Laurentini-Analisi degli algoritmi

ESERCIZI E PROBLEMI

98
A.Laurentini-Analisi degli algoritmi

• Determinare la chiusura transitiva per il grafo

• Scrivere un programma C che determina i componenti connessi di un grafo G(V,E)


• Progettare un algoritmo di visita depth first per un grafo che fornisca una lista dei
nodi nell’ordine in cui sono stati incontrati la prima volta.
• Determinare un albero ricoprente minimo per il grafo pesato
3
5
2
2
3 3
6
4 1
1
5
1 2
2 2

• Progettare e scrivere un programma C per determinare l’albero ricoprente minimo


di un grafo G(V,E)
• Determinare l’albero dei cammini minimi a partire dal nodo più a sinistra per il
grafo del terzo esercizio

• Progettare un algoritmo che verifica se un grafo è un albero. Determinarne la


complessità.

• Progettare un algoritmo per verificare se un grafo è bi-connesso, e determinarne la


complessità (un grafo è bi-connesso se, in qualunque modo si raggruppino i nodi in
due insiemi, esistono sempre almeno due lati che congiungono nodi del primo e del
secondo insieme).

• Progettare un algoritmo per verificare se un grafo è tri-connesso, e determinarne la


complessità

• Progettare un algoritmo per verificare se un grafo è bipartito. Determinarne la


complessità.
• Si voglia trovare il cammino più breve tra un nodo e un altro con lati tutti dello
stesso peso (ad es., il problema è pianificare un viaggio aereo col minor numero di
soste). Si può usare un algoritmo più semplice di quello di Dijkstra, magari lineare?

99
A.Laurentini-Analisi degli algoritmi

• L’algoritmo di Dijkstra per il cammino più breve funziona anche se alcuni pesi
sono negativi? Giustificare la risposta positiva o dare un controesempio se la risposta
è negativa
• Determinare i grafi duali dei grafi seguenti

• Determinare i grafi duali di K4 e K23

• E’ possibile determinare i grafi duali di K6 e K43 ?

• Verificare il teorema di Eulero, o una delle sue varianti, sui due grafi seguenti

G2
G1

• Verificare il teorema di Eulero, o una della sue varianti, sui poliedri seguenti

• Verificare se i grafi seguenti sono Euleriani o Semi-euleriani. Nel caso lo siano,


determinare un ciclo o un cammino Euleriano

100
A.Laurentini-Analisi degli algoritmi

• Progettare un programma C per la determinazione dei cicli o cammini Euleriani.


Analizzarne la complessità

• Dire se sono Euleriani o Semieuleriani i grafi K43 , K6 , K5 .

• Verificare se sono Hamiltoniani o Semi-hamiltoniani i grafi seguenti:

• Trovare una Maxclique dei grafi seguenti

G2
G1

• Determinare Maxclique per i grafi K4,3 , K7 , K2,2

• Quanto vale al max. il clique number di un grafo planare?

• Determinare se il grafo seguente e tre-colorabile, e se lo è trovare una colorazione

• Determinare, per il grafo precedente, un insieme dominante minimo ed un insieme


indipendente massimo

• Per il problema della colorazione di un grafo esistono varie euristiche. Una molto
semplice si chiama sequential coloring. Essa consiste semplicemente, su di un grafo

101
A.Laurentini-Analisi degli algoritmi

con n vertici, nel numerare i colori progressivamente, e nell’assegnare ad ogni vertice


non ancora colorato il colore più basso nella lista dei colori non assegnato a vicini del
vertice.
- Si trovi la complessità dell’algoritmo
- Si dimostri che il numero di colori usato è non superiore a (Max. grado di un
vertice del grafo)+1
Si trovino degli esempi in cui il metodo è inefficiente

• Si determini un ciclo Hamiltoniano sul grafo seguente

12- ALGORITMI DI RICERCA

102
A.Laurentini-Analisi degli algoritmi

La ricerca dell’informazione collegata ad una chiave di ricerca (key) è un’operazione


frequentissima in tutte le applicazioni tecniche e gestionali.

Key Data

Ad esempio:
Chiave Informazione associata
Codice cliente Anagrafica cliente
Codice prodotto Anagrafica prodotto
Vocabolo italiano Vocabolo inglese
Nome Numero di telefono
Nome variabile Indirizzo in memoria
Codice c.c Movimenti

Gli ADT relativi alle ricerche si distinguono in:


- Dizionari, in cui si cerca un elemento qualunque
- Code prioritarie, in cui si cerca l’elemento con chiave massima o minima

• Dizionari: operazioni tipiche


L’argomentodi INPUT è sottolineato.
-Search(Key, Data) operazione fondamentale
-Insert(Key, Data)
-Delete(Key)
Assai meno importanti operazioni come :
-Min(Key,Data)
-Max(Key,Data)
………
• Dizionari statici: ammessa solo Search (Es. dati storici)

• Dizionari dinamici: ammesse anche Insert e Delete (Es.Elenco telefonico corrente)

• Dizionari semi-dinamici: ammesse Search e Insert (Es. movimenti correnti c.c.,


magazzino, tabella dei simboli compilatore o assembler)

• Dizionari: possibili realizzazioni:


- Vettori
- Liste
- Alberi di ricerca
- Hash table

103
A.Laurentini-Analisi degli algoritmi

12.1– VETTORI

Adatti a liste statiche di piccole dimensioni.

• Vettori non ordinati


- Search, Delete, Min, Max: O(n)
- Insert: O(1) in coda
• Vettori ordinati
- Search: O(lgn) con ricerca dicotomica
- Insert, Delete: O(n) (O(lgn) per posizionarsi, O(n) per far posto o compattare)
- Min, Max: O(1)

12.2– LISTE CONCATENATE

Sono adatte a dizionari dinamici o semi-dinamici di piccole dimensioni. Nelle


realizzazioni semplici, tutte le operazioni sono tendenzialmente O(n), tranne le
inserzioni in testa o in fondo.

12.3– ALBERI DI RICERCA

• Alberi binari di ricerca (Binary Search Tree-BST):= alberi binari in cui ogni nodo
corrisponde ad un elemento dell’insieme.
Ogni nodo contiene: chiave, dati associati, puntatori al figlio a sinistra(LEFT), al
figlio a destra(RIGHT), al nodo genitore(PARENT).
Le chiavi di ogni nodo LEFT sono minori della chiave di PARENT.
Le chiavi di ogni nodo RIGHT sono maggiori della chiave di PARENT.

Esempi (nei nodi sono indicate solo le chiavi)


10 15

5 14 5 18

7 12 18 10

7 12
15

I due BST hanno le stesse chiavi 14

104
A.Laurentini-Analisi degli algoritmi

• La visita IN-ORDER del BST fornisce le chiavi in ordine crescente

• Proprietà fondamentale dei BST: tutte le operazioni principali sono O(p). Se


l’albero è bilanciato (le foglie si trovano tutte al livello p o p-1), le operazioni sono
O(lgn). Supponiamo chiavi distinte.

- Search. Confronto la chiave k con quella della radice kr . Se coincide, la ricerca è


terminata. Se k ≤ kr , applico recursivamente la procedura al sotto-albero di
sinistra, altrimenti a quello di destra. Mi arresto se: 1) trovo k ; 2) il sottoalbero è
vuoto. Il numero di confronti max è p+1.

- Insert. Applico Search. Mi arresto ad un certo nodo, perché il sottoalbero


corrispondente a k è vuoto. Inserisco il nuovo nodo con chiave k come figlio
sinistro o destro. Il numero di confronti max è p+1. Esempi:

3 10 10 11

5 14 5 14

3 7 12 18 7 12 18

15 11 15

- Delete. Si trova l’elemento da cancellare con Search. Dopo di che l’algoritmo ha


vari casi.

1) Il nodo non ha figli. Lo si cancella (si cambia il puntatore nel nodo padre).

10 10

5 14 cancello 18 ⇒ 5 14

7 12 3 7 12
3 18

105
A.Laurentini-Analisi degli algoritmi

2) Il nodo ha 1 figlio. Faccio risalire il sottoalbero che ha il figlio per radice (il
puntatore del nodo padre viene fatto puntare al figlio).

10 10

5 14 cancello 14 ⇒ 5 12

3 7 12 3 7

3) Il nodo ha due figli. Ci sono due possibilità. Posso sostituire il nodo da cancellare
con il nodo più a sinistra del sottoalbero a destra, o con il nodo più a destra del
sottoalbero a sinistra. Se ad esempio cancello la radice 10 dell’albero a sinistra,
ottengo uno dei due alberi a destra:

10 7 12

5 14 ⇒ 5 14 oppure 5 14

3 7 12 3 12 3 7

In ogni caso la cancellazione è O(p)

Si può facilmente verificare che sono semplici anche altre operazioni come Min,
Max, Next, Previous.

• Bilanciamento. Affinchè sia O(p)= O(lgn), è necessario che l’albero rimanga


bilanciato. E’ chiaro che le operazioni di cancellazione e di inserzione potrebbero
portare nel caso peggiore a p=n-1.
A questo fine esistono varie tecniche:
- Usare dei particolari alberi detti HBT( Height Balanced Tree), che garantiscono
operazioni O(lgn), sia pure con costanti un po’ peggiori.
- Usare molte altre varianti di alberi semi-bilanciati
- Non fare niente: si dimostra che, con valori equiprobabili di chiavi e intervalli tra
le chiavi, la ricerca media è ≅ 1.4 lgn. Il caso pessimo però è Θ(n)

106
A.Laurentini-Analisi degli algoritmi

12.4– HASH TABLE

E’ la tecnica di gran lunga più usata per grandi valori di n. Le tabelle hash
permettono ricerche in tempo medio circa costante.

• Tabelle ad accesso diretto.


Si definisce tabella ad accesso diretto quella in cui la chiave di accesso è, o fornisce
in tempo costante O(1), l’indirizzo in memoria dell’elemento cercato. Ad esempio, se

Indirizzo=K+HxChiave

Ciò è possibile in alcuni casi pratici, quando se si può assegnare la chiave di ricerca
ad.esempio: codice prodotto di magazzino:= interi successivi.

In molti casi però la chiavi sono assegnate e non modificabili (dizionari, elenchi
telefonici, …). Si potrebbe pensare di trasformare direttamente le chiavi in numeri.
Si consideri un dizionario italiano-inglese: per trasformarlo in una tabella ad accesso
diretto, si potrebbe trasformare le parole italiane in chiavi binarie sostituendo ad ogni
carattere il suo codice ASCII su 6 bit. Considerando parole fino a 15 lettere, le chiavi
risulterebbero numeri binari con 90 cifre, vale a dire decimali con circa 30 cifre.
Ma una memoria con un tali numeri di locazioni è assolutamente al di fuori delle
possibilità pratiche, per una ventina di ordini di grandezza.
Poiché non è praticamente possibile costruire in questo modo gli indirizzi, o gli indici
di tabelle ad accesso diretto, si deve modificare in qualche modo la costruzione .

• Hashing function: idea generale


Per costruire in modo semplice (cioè in tempo costante) dalla chiave k l’indice
(posizione) nella tabella senza gli inconvenienti visti prima, si rinuncia all’univocità.
Indice= h(k)
h(•) : funzione detta hash, semplice ma non necessariamente univoca.
Può accadere che sia:
h(ki)= h(kj) per ki≠ kj
Questo caso viene detto collisione.

Esempio
L’esempio che segue non è realistico, ma rivolto a mostrare i problemi da risolvere
per costruire una tabella con questa tecnica.

- Tabella: dizionario Italiano/Inglese di 20 parole


- Hashing function h(ki) := posizione nell’alfabeto della prima lettera(a→1, b→2,..)

107
A.Laurentini-Analisi degli algoritmi

Si supponga di inserire, una dopo l’altra, una serie di parole italiane con la
corrispondente inglese nella tabella:

cane-dog 1 dovere-duty 1

2 2

3 cane dog 3 cane dog

4 dovere duty

albero-tree 1 albero tree cavallo-horse 1 albero tree

2 2

3 cane dog 3 cane dog

4 dovere duty 4 dovere duty

5 cavallo horse

gomma-gum 1 albero tree età-age 1 albero tree

2 2

3 cane dog 3 cane dog

4 dovere duty 4 dovere duty

5 cavallo horse 5 cavallo horse

6 6 età age

7 gomma gum 7 gomma gum

Nel caso di collisioni, si è seguita la semplice regola di cercare la prima posizione


libera. La stessa regola deve essere seguita nella ricerca.

Dall’esempio si vede che:


108
A.Laurentini-Analisi degli algoritmi

- la funzione hash scelta non è molto buona: provoca sempre collisioni se le parole
iniziano con la stessa lettera. E’ bene scegliere funzioni che tengano conto di tutta
la chiave, e tendano a sparpagliare in modo circa uniforme le chiavi nella tabella.
- se non ci sono collisioni, l’accesso alla tabella(sia per inserire che per leggere) è in
tempo costante
- è bene lasciar vuota una parte della tabella per ridurre la probabilità di collisione e
rendere più brevi le ricerche dopo le collisioni

Nel seguito si vedranno:


- varie funzioni hash
- le tecniche per il trattamento delle collisioni
- le tecniche per la cancellazione di elementi

• Funzioni hash
Se gli indici della tabella sono m (0, 1,…..m-1), si vorrebbe che le chiavi si
mappassero sugli indici con probabilità uguali:

1
∑ p(k ) =
m
∀j
k :h ( k ) = j

dove p(k) è la probabilità della chiave k.


Ad esempio, se la chiave fosse un numero qualunque tra 0 e 1 con densità di
probabilità costante, una hashing function adatta è h(k)=km .
Tuttavia, di solito non conosciamo le probabilità delle chiavi, e si usa una delle
tecniche euristiche seguenti. Si suppone che le chiavi siano numeri, o stringhe
alfanumeriche riducibili a numeri (ad es. sostituendo i caratteri con i codici ASCII).

- Metodo di divisione
h(k)=k mod m (resto della divisione per la dimensione m della tabella)

Se fosse m=1000, il resto dipenderebbe solo dalle ultime tre cifre. E’ consigliabile
usare per m solo numeri primi.

- Metodo di moltiplicazione w bit


Si suppone m=2p . Sia 0<A<1
k

X A x2w

p bit

I p bit dipendono da tutta la chiave. Per A, Knuth suggerisce 0.6180339887.

109
A.Laurentini-Analisi degli algoritmi

- Numeri pseudocasuali.
Si usa per h(k) un generatore di numeri pseudocasuali, inizializzato con k. Se ci sono
collisioni, si usano i successivi numeri generati .

- Ripiegatura (folding)
Da usare per ridurre le dimensioni di chiavi lunghe.

XOR
E’ una tecnica veloce, che può essere usata più volte.
Prendendo p bit del risultato, si possono direttamente
indirizzare tabelle di dimensione 2p.
Vanno bene anche altre funzioni logiche che abbiano una tabella di verità contenenti
due zeri e due uni (quindi non AND e OR).

• Trattamento collisioni. Metodo 1: concatenazione (chaining)

Per ogni posizione, c’è una lista di elementi che collidono. Se le collisioni sono state
n, le liste sono lunghe mediamente n/m. Se la distribuzione delle collisioni è
uniforme, sia Search che Insert e Delete sono O (n/m).
Se il coefficiente di riempimento (detto anche di carico) α= n/m aumenta troppo, si
può ristrutturare la tabella.

• Trattamento collisioni. Metodo 2: indirizzamento aperto (open addressing)


In questo caso la tabella deve contenere tutti gli elementi del dizionario (n<m, α<1).
Se vi è collisione, sono possibili varie tecniche per la scansione della tabella, vale a
dire per trovare posizione successive.

- Scansione lineare(linear probing)


E’ la generalizzazione della semplice tecnica di esaminare l’elemento successivo
usata nell’esempio all’inizio del capitolo.

hr(ki)= [ h(ki)+qr ] mod m


con : r=1, 2…..; q= costante intera; q ed m primi tra loro (tutte le posizioni
vengono visitate).

110
A.Laurentini-Analisi degli algoritmi

Si può dimostrare che, sotto l’ipotesi di funzione hash ottima, il numero medio di
accessi (tentativi per il Search o Insert) dipende solo da α:

numero medio di accessi = S(α) = (1-α/2)/ (1-α)

α 0.1 0.25 0.5 0.75 0.9 0.95

S(α) 1.06 1.17 1.5 2.5 5.5 10.5

Si vede che, se non si supera un fattore di riempimento di ≅2/3, il numero medio di


tentativi è molto piccolo (circa minore di 2).
In teoria, tuttavia il caso pessimo è O(n), ma in pratica con una buona scelta della
funzione hash è impossibile raggiungerlo.

Un problema della scansione lineare è l’agglomerazione primaria. Se si forma una


sequenza di posizioni contigue piene, ogni chiave che genera un indirizzo
nell’agglomerazione si comporta come se ci fosse una collisione di chiavi. Se, nella
formula della scansione si pone q=1, l’agglomerazione si forma in posizioni contigue,
altrimenti in posizione a distanza q, senza cambiare la sostanza del fenomeno.

Un rimedio consiste nell’usare un passo non costante. Esempio:

ki ki
kg kg
ki kg
kg ki

6 accessi 5 accessi

- Scansione pseudocasuale.
Usa i numeri prodotti da un generatore pseudocasuale di interi con la chiave come
seme. Sotto ipotesi abbastanza generali si trova che:

α 0.1 0.5 0.75 0.9

S(α) 1.05 1.44 1.99 2.79

- Scansione quadratica
hr(ki)= [ h(ki)+r2 ] mod m

111
A.Laurentini-Analisi degli algoritmi

con : r=1, 2…..;


In generale non tocca tutte le posizioni (solo (m-1)/2). Una condizione perché tocchi
tutte le posizioni è che sia m=4j+3 (m primo, j intero).

Se due chiavi collidono, la ricerca successiva si sovrappone. Per migliorare


ulteriormente i risultati si può usare la:

- Scansione quadratica a coefficienti pesati


Chiavi diverse che collidono hanno percorsi di ricerca diversi:
hr(ki)= [ h(ki)+q(ki)r2 ] mod m

q(ki) è una funzione semplice della chiave. I numeri medi di accessi diventano:

α 0.1 0.5 0.75 0.9

S(α) 1.05 1.38 1.83 2.55

In conclusione, per tabelle con occupazioni inferiori al 60-70% il numero di accessi


medio varia poco con la tecnica di scansione.

Per le ricerche senza successo, le medie degli accessi possono variare notevolmente.
Il caso peggiore è quello della scansione lineare:

α 0.25 0.5 0.75 0.9 0.95

S(α) 1.39 2.5 7.5 50.5 200.5

• Cancellazioni
Le tecniche descritte servono sia per Search che per Insert. La cancellazione è in
pratica un’operazione più rara, e in diversi casi non è necessaria (ad es. per le tabelle
dei simboli costruite dal compilatore o dall’assembler).
Se non si prendono opportune precauzioni possono dar luogo a problemi. Ad
esempio, facendo riferimento all’esempio all’inizio del capitolo:

Cavallo Se cancello Cane, e poi Cavallo


Cane cerco Data, -
Data trovo una casella vuota Data

Una soluzione: un campo aggiuntivo con tre valori: libero, occupato, cancellato.
In fase di ricerca, cancellato=occupato. In fase di inserzione, cancellato=libero.

112
A.Laurentini-Analisi degli algoritmi

13- CODE PRIORITARIE

Una coda prioritaria P è un insieme di elementi x, ciascuno costituito da una chiave e


da dati associati, che ammette le seguenti operazioni.
- Insert(x,P)
- Find-Min(P) ritorna l’elemento con chiave minima
- Delete-Min cancella l’elemento con chiave minima

N.B. La coda prioritaria potrebbe essere strutturata per ricerca e cancellazione


dell’elemento con chiave massima.

• Realizzazione code prioritarie.


La struttura più adatta è l’heap (mucchio). L’heap è un albero binario in cui:
1) la chiave di ogni nodo è minore (o maggiore) di quella dei figli
2) vi sono tutti i nodi a tutti i livelli, salvo l’ultimo dove possono mancare alcuni
nodi a sinistra.
N.B. In alcuni testi viene detto heap l’albero binario che soddisfa alla prima
proprietà, e heap bilanciato quello che soddisfa anche alla seconda.

Gli heap si possono memorizzare in un vettore per livelli. Esempi:

9 50

5 7 24 30

1 4 3 6 20 21 18 3

12 5 6

9 5 7 1 4 3 6 50 24 30 20 21 18 3 12 5 6

• Complessità della varie operazioni

- Find-Min(P) (Find-Max(P)) è ovviamente O(1)


- Insert(x,P)
Deve essere effettuata mantenendo il bilanciamento. Si inserisce x al livello più basso
nella prima posizione vuota. Lo si confronta con il padre, e se non è maggiore
(minore) lo si scambia con questo. Si procede così per scambi finché la proprietà
dello heap non è soddisfatta. Evidentemente Insert(x,P) è O(lgn).

113
A.Laurentini-Analisi degli algoritmi

Esempio

Inserisco 36
50

24 30 24 36

20 21 18 3 20 36 20 24

12 5 6 36 12 5 6 21 12 5 6 21

- Delete-Min (Delete-Max)
Devo sempre mantenere il bilanciamento. Si sostituisce la radice con l’ultimo nodo
dell’ultimo livello. Si confronta la sua chiave con quelle dei figli, e, se non è minore
(maggiore), lo scambio con il più piccolo(il più grande) dei due. Continuo così finchè
non è disceso al posto giusto. Evidentemente anche Delete è O(p)=O(lgn).
Esempio
50 6 30 30

24 30 6 18

20 21 18 3 18 3 6 3

12 5

In conclusione, mantenendo il bilanciamento, le operazioni sono O(1) (Find) oppure


O(lgn)(Insert e Delete)

L’uso di questa tecnica è utile in vari casi, ad esempio negli algoritmi del minimo
albero ricoprente e dei percorsi minimi, dove ad ogni passo si deve scegliere una
distanza minima (Delete), ed aggiornare (Insert) le distanze dei nodi

114
A.Laurentini-Analisi degli algoritmi

• Heap SORT

Lo Heap può essere usato per costruire un SORT asintoticamente ottimo (O(nlgn))
nella classe dei SORT che fanno confronti.

Algoritmo HEAPSORT:
- costruisco lo Heap
- prelevo tutte le chiavi una dopo l’altra con Delete
In questo modo ottengo le chiavi ordinate in modo crescente o decrescente a seconda
di come ho costruito lo heap.

Analisi
- Costruzione dello Heap. Si può usare n volte Insert. Questo porta a dire che la
costruzione è O(nlgn). Un esame più accurato mostra che l’algoritmo è lineare.
- Il prelievo delle n chiavi con Delete è O(nlgn).
In conclusione l’algoritmo è O(nlgn), e quindi asintoticamente ottimo.

115
A.Laurentini-Analisi degli algoritmi

14- CONSIDERAZIONI CONCLUSIVE

Tecniche generali per la costruzione di algoritmi

• Tecniche di enumerazione
Tipiche di problemi di soluzione ottima. Consistono , in linea di massima, in:
- generare tutte le soluzioni
- confrontarle per scegliere quella ottima
Generano algoritmi esponenziali.

Esempio 1. Un algoritmo enumerativo per ordinare una lista è il seguente: a)si


generano le n! permutazioni possibili; b) si cerca quella ordinata. Ovviamente
l’algoritmo è esponenziale (vedi formule di Sterling). Fortunatamente conosciamo
algoritmi assai migliori !!

Esempio 2 La tecnica usata per l’algoritmo esatto(sezione 9.5) per il set covering è
enumerativa, genera tutte le soluzioni , ed è (mn)

Esempio 3 e problema L’algoritmo per la ricerca di un insieme indipendente massimo


di nodi di un grafo (sezione 10.13) è enumerativo. E’ equivalente al problema NP
completo di trovare la max-clique del grafo complementare.
Tuttavia, in alcuni casi si possono fare semplificazioni notevoli nelle enumerazioni.
Si consideri il problema di disporre il massimo numero di regine che non si danno
scacco sulla scacchiera, modellabile come un problema di ricerca di una max-clique.
 64 
Sembrerebbe che il problema richiedesse l’esame di   possibili combinazioni
8
(un numero grandissimo, circa 4x109). Se però osserviamo che per ogni riga (o
colonna) ci può essere solo una regina, le combinazioni possibili si riducono a
8!=40420. Se poi consideriamo le simmetrie (la scacchiera può essere avvolta su di
un cilindro, o anche su di un toro circolare) i casi si riducono ulteriormente (come?)

Esempio 4 e problema. Si consideri il seguente problema: data una scacchiera


dieciXdieci, e 50 pezzi da domino, ciascuno dei quali copre 2 caselle, si chiede se è
possibile ( in tal caso dire come) o non è possibile (in tal caso dimostrare
l’impossibilità) con 49 pezzi da domino coprire tutta la scacchiera tranne due caselle
agli angoli opposti.
Sembra che si debbano enumerare una gran quantità di soluzioni, ma esistono metodi
molto sintetici per risolvere il problema.

• Divide and Conquer (Divide et Impera)


Una tecnica che consiste in :
- suddividere un problema in sottoproblemi
- combinare le soluzioni dei sottoproblemi

116
A.Laurentini-Analisi degli algoritmi

In generale, la convenienza della tecnica è stata discussa nella sezione 8. Conviene in


generale per algoritmi che crescono più che linearmente, e per cui la ricombinazione
cresca meno dell’algoritmo.
Si prestano ad esecuzioni recursive. In tal caso, se ogni volta si suddivide in problemi
di dimensione metà, e la ricombinazione è lineare, gli algoritmi sono O(nlgn)

Esempio 1: Merge-Sort (O(nlgn))

Esempio 2: Ricerca dicotomica, ricerca su albero di ricerca (O(lgn) perché non c’è
ricombinazione)

• Algoritmi avidi (greedy)


Cercano un ottimo locale. Di solito sono semplici. Possono talora dare l’ottimo
globale.

Esempio 1. Algoritmo di Kruskal per il minimum spanning tree . Dà l’ottimo globale

Esempio 2. Algoritmo di Dijkstra per l’albero dei cammini minimi. Dà l’ottimo


globale.

Esempio 3. Algoritmo approssimato per bin-packing. Ha approssimazione garantita.

Esempio 4. Algoritmo per il problema del resto. Si tratta di dare il resto con il numero
minimo di monete, supponendo di avere monete di alcuni valori assegnati.
L’algoritmo è questo, K è il resto da dare:
a) si sceglie la moneta di valore più alto M inferire a K (scelta greedy)
b) K-M→K; se K=0, HALT; altrimenti vai ad a)

117
A.Laurentini-Analisi degli algoritmi

L’algoritmo non funziona sempre. Se infatti i valori delle monete fossero


11, 5, 1
con K=15 si avrebbe un resto fatto da 11, 1, 1, 1 (quattro monete), ma ovviamente il
numero minimo di monete è 3 (15=5+5+5)

• Algoritmi euristici
Contengono gli algoritmi greedy. Usano qualche tecnica semplice, che mediamente si
rivela abbastanza efficace.

118
A.Laurentini-Analisi degli algoritmi

15- ESEMPI DI ALGORITMI COMPLESSI

• Determinazione max-clique.

Caratteristiche:
- di tipo backtracking (non esplora tutto l’albero delle possibilità);
- segue la tecnica euristica di esplorare prima i casi in cui sono coinvolti nodi di
grado (numero nodi adiacente) elevato(e quindi + probabili membri di max-clique)
- semplice da realizzare

Osservazione: condizione necessaria perché ci sia una clique di k nodi è che ci siano
almeno k nodi di grado k.

Algoritmo:
1) ordino i vertici in gruppi di grado(numero nodi adiacente) decrescente almeno n,
n-1, n-2 … n-p ……. . Presumibilmente i primi insiemi sono vuoti
2) Siano k0 , k1 , …. kp , ….. le cardinalità degli insiemi. Si inizia ad esaminare,
partendo dall’alto, il primo insieme di vertici per cui è …. kp ≥ n-p ( e quindi
potrebbe contenere i nodi di max-clique)
3) Si ordinano i nodi dell’insieme in modo decrescente secondo il grado
4) Si generano tutte le combinazioni dei kp nodi a gruppi di p con la regola
lessicografica esemplificata sotto, che mantiene l’ordinamento e quindi privilegia
sempre la scelta del nodo di grado più alto.
Esempio: Insieme 123456 Sottoinsiemi da 4 elementi (sono 15= (6x5x4x3)/(4x3x2)):
1234, 1235, 1236, 1245, 1246, 1256, 1345, 1346, 1356, 1456, 2345, 2346, 2356,
2456, 3456

Ogni volta che si genera un elemento della permutazione, si verifica se è connesso ai


precedenti (se ce ne sono). Basta esaminare la sua lista di adiacenza . Se non è
connesso, non si generano più le permutazioni figlie (Riferendosi all’esempio
precedente, se dopo 1 si trova che 2 non è connesso, non si devono più generare
1234, 1235, 1236, 1245, 1246, 1256, ma si passa direttamente a 1345).
Se tutti gli elementi di una combinazione sono connessi, ho finito e trovato la max-
clique, o meglio il clique number.
Nel caso disgraziato nessuna combinazione dell’insieme fosse buona, si passa ad
esaminare l’insieme successivo dei nodi con grado (almeno) inferiore di uno.
L’algoritmo sarà molto rapido se l’insieme contiene una clique, e l’euristica funziona,
o anche se il numero di elementi dell’insieme è poco maggiore del clique number
previsto (poche combinazioni), o infine se il backtracking funziona abbastanza bene
ed elimina un bel po’ di combinazioni.

Possibile miglioramento. Quando si esaminano le liste di adiacenza, è inutile


esaminare quelle del grafo originale, che comprendono tutti i nodi e quindi anche
119
A.Laurentini-Analisi degli algoritmi

quelli con grado inferiore. Conviene generare le liste di adiacenza di un sottografo


che comprende solo i nodi dell’insieme sotto esame.

• Calcolo di un lower bound per il problema ART GALLERY


Il problema: data una pianta poligonale, posizionare il numero minimo di guardie
(sensori rotanti o omnidirezionali) capaci di vedere, o coprire, ogni punto della
pianta.

- Tipo del problema: NPcompleto


- Risultati noti (upper bounds):
- Se il poligono ha n lati , nel caso più sfortunato ci vogliono n/3  guardie
- Se ci sono n lati ed h buchi poligonali, ci vogliono al massimo (n+h)/3 
guardie

Sono state studiate molte variazioni del problema (poligoni con lati ortogonali,
limitati angoli visivi dei sensori,……) e trovati altri bound per questi casi. (vedi T.
Shermer, “Recent results in art galleries”, IEEE Proc. Vol. 80, pp. 1384-1399, 1992)

Tuttavia non si conoscono algoritmi finiti, neppure esponenziali, per trovare una
soluzione ottima, e si devono quindi usare algoritmi euristici.

Per valutare la bontà di questi algoritmi, sarebbe utile conoscere un lower bound
LB(P) specifico del poligono P. Se l’algoritmo fornisce un numero di guardie uguale
G al lower bound, la soluzione è ottima. Se G-LB(P) è piccolo, lo è anche la
differenza tra G ed il valore ottimo, compreso tra G e LB(P)

Consideriamo il caso dell’osservazione dei soli lati del poligono (boundary covering)
che corrisponde al problema pratico dell’ispezione.

120
A.Laurentini-Analisi degli algoritmi

Si noti che boundary covering è diverso da interior covering che può richiedere molte
più guardie, e dall’eventuale soluzione ottima di un problema non si sa come trovare
la soluzione ottima dell’altro.

Nel caso superiore, il rapporto tra interior guards


e boundary guards tende a 2.
Nel caso a fianco può assumere un valore
qualunque.

121
A.Laurentini-Analisi degli algoritmi

Come costruire un lower bound LB(P) specifico di un poligono per le boundary


guards? Consideriamo la definizione di integer visibility polygon (poligono dei punti
che vedono interamente un lato) e week visibility polygon (poligono dei punti che
vedono almeno un punto del lato)

E’ evidente che in ogni week visibility polygon c’è almeno una guardia. Ne consegue
che :

Un lower bound LB(P) è il massimo insieme di week visibility polygons che non si
intersecano

Esempio

Algoritmo per il calcolo del LB(P)


1) Calcolo tutti i week visibility polygons W(li) per tutti i lati li (esistono algoritmi
quadratici per farlo)
2) Associo un grafo con un nodo per ogni lato del poligono, e un lato tra due nodi
se i corrispondenti week visibility polygons si intersecano.(la verifica
dell’intersezione è polinomiale)
3) Il problema è ridotto a trovare il maximum independent set di nodi del grafo. Il
problema è equivalente a quello della maxclique sul grafo complementare
(stessi nodi, lati complementari) ( problema NP-completo). Esistono in pratica
algoritmi branch-and-bound per maxclique che mediamente hanno tempi
accettabili per grafi fino ad alcune centinaia di nodi.

122
A.Laurentini-Analisi degli algoritmi

• Determinazione coppia di punti più vicina nel piano

Problema. Dato un insieme di punti nel piano, determinare la coppia di punti più
vicina. Alla base di numerosi altri algoritmi.

Se i punti fossero su una retta, si potrebbe:


- ordinarli lungo la retta ( O(nlgn))
- calcolare le distanze tra un punto ed il successivo, e scegliere la minima (O(n))
In conclusione, O(nlgn).

Se i punti non sono allineati, sembra che si debbano calcolare n(n-1)/2 distanze,e
quindi costruire algoritmi quadratici.

Si può invece (ed in vari modi ) costruire algoritmi O(nlgn)

Algoritmo Recursivo Divide and Conquer O(nlgn)


http://www.cs.mcgill.ca/~cs251/ClosestPair/ClosestPairDQ.html

Si ordinino i punti lungo x e lungo y. Si dividano i punti in due insiemi uguali S1 e S2


(a meno di un punto, se il numero è dispari)con una linea verticale l.

Si trovi quindi le coppia più vicine, a distanza d1 e d2 nei due insiemi. Sia d la
distanza più piccole tra le 2.

123
A.Laurentini-Analisi degli algoritmi

Se fossimo sicuri che non ci sono distanze più piccole tra coppie di punti con un
punto in S1 e l’altro in S2, potremmo dividere recursivamente gli insiemi , ed
ottenere un algoritmo O(nlgn), (ricombinazione lineare, lgn passi).

Questo però non è vero: potrebbero esserci punti di questo genere, uno nella fascia P1
e l’altro nella fascia P2. larga ciascuna d. Dobbiamo quindi verificare che per ogni
punto nella fascia P1 non ce ne sia uno in P2 a distanza minore di d. Apparentemente
questa verifica è O(n2).

Si può invece dimostrare che:


Teorema Se considero la lista dei punti inclusi nelle due fasce, e ordinata lungo y, la
distanza nella lista tra due punti con distanza geometrica <d è al massimo di 15
posizioni

d l d
Prova per assurdo E’ chiaro che in ognuno dei 16 quadrati di lato d/2 può esserci (al
massimo) un solo punto, altrimenti sarebbero più vicini di d. Supponiamo, per
assurdo che due punti abbiano distanza < d, e siano separati da 16 o più posizioni.
Per la seconda condizione, devono essere separati da almeno 3 file di quadratini. Ma
tre file corrispondono ad un distanza di almeno 3X d/2 > d, contraddicendo la prima
condizione.

Nota. Con una dimostrazione più complessa, si può vedere che la distanza è al
massimo 5 . Agli effetti dell’algoritmo, questo non cambia la complessità asintotica.

Algoritmo.
1) Ordina in x e y i punti
2) Dividi i punti in 2 con la linea l
3) Applica recursivamente l’algoritmo alle due metà
4) Ricombina, trovando il massimo tra le due distanze ed esaminando che non ci
siano distanze minori nella fascia.

124
A.Laurentini-Analisi degli algoritmi

Analisi complessità

1) O(nlgn),
2) lineare
3) Sono lgn passi della recursione,
4) ciascuna divisione e ricombinazione lineare ( la ricombinazione richiede
trovare il minimo tra, al livello più basso, n/2 distanze, trovare e verificare i
punti nella fascia, che sono lineari)
In totale O(nlgn).

Algoritmo non recursivo


Esistono altri algoritmi O(nlgn), ad esempio quello del plane sweep mediante scan
line http://www.cs.mcgill.ca/~cs251/ClosestPair/ClosestPairPS.html

In breve, la linea verticale si sposta da sinistra a destra (richiede un ordinamento


iniziale lungo x. In ogni istante l’algoritmo ha: la minima distanza trovata d, la
lista ordinata lungo y dei punti nella fascia di larghezza d.
Per ogni nuovo punto incontrato
- si mantiene la lista ordinata, inserendo il punto e togliendo quelli usciti dalla
fascia ( O(lgn) con albero di ricerca)
- si cerca il più vicino nella fascia. Non bisogna confrontarsi con tutti, ma solo con
quelli distanti al max. 5 posizioni (tempo costante, usando l’albero di ricerca)
- si aggiorna eventualmente d
Complessità O(nlgn)

125
A.Laurentini-Analisi degli algoritmi

Ottimalità
Gli algoritmi descritti sono asintoticamente ottimi.
Per verificarlo, occorre trovare un lower bound. A questo scopo, si usa il problema
dell’unicità di un elemento (element uniqueness problem), che chiede, data una lista,
di trovare se vi sono due elementi uguali.
E’ stato dimostrato che il problema è Θ(nlgn)
Il problema può essere ridotto (trasformato) nel problema della coppia più vicina.
Infatti, se trovo due elementi a distanza 0, significa che due elementi sono
coincidenti. Quindi il problema della coppia più vicina ha lo stesso lower bound (non
può essere più semplice)

Estensione in spazi a d dimensioni


Vi sono algoritmi divide and conquer, o non recursivi, estensione di quelli 2D. Si
dimostra che in generale la complessità è O(n(lgn)d-1)
http://www.cs.ucsb.edu/~suri/cs235/ClosestPair.pdf

126
A.Laurentini-Analisi degli algoritmi

INDICE

DOCENTI, ORARI, DISPENSE ED ESAMI Pag. 1


1-INTRODUZIONE 2
2-ANALISI DEI PROBLEMI E DEGLI ALGORITMI 6
2,1-Correttezza 6
2.2-Lavoro fatto 7
2.2.1-Caso medio e caso pessimo 8
2.3-Spazio usato 10
2.4-Semplicità 11
2.5-Ottimalità 11
2.5.1- Caso pessimo 11
2.5.2- Caso medio 13
2.6- Ottimalità nell’uso dello spazio 13
3-CLASSIFICAZIONE ASINTOTICA 14
3.1- Notazioni O, Ω, ο, ω 15
3.2- Proprietà di O, Ω, ο, ω 17
4- RICHIAMI MATEMATICI 18
5- STRUTTURE DATI ELEMENTARI 21
5.1- Liste 21
5.2- Alberi 24
5.2.1- Alberi binari 24
5.2.2- Alberi in generale 26
ESERCIZI E PROBLEMI 27
6- ANALISI ALGORITMI DI RICERCA 30
6.1- Ricerca in liste ordinate 30
6.1.1- Algoritmo 1 30
6.1.2- Algoritmo 2- Ricerca binaria 31
6.1.3- Ottimalità della ricerca binaria 32
7- ALGORITMI DI ORDINAMENTO 34
7.1- Insertion SORT 34
7.2- Altri SORT O(n2) 36
7.3- Lower Bound per alcuni SORT 37
7.4- QUICKSORT: approccio “Divide and conquer” 38
7.5- MERGE (fusione) di liste ordinate 40
7.6- MERGE-SORT 41
7.6.1- Ottimalità del MERGE-SORT 43
7.7- Algoritmi di SORT lineari 45
7.7.1- BUCKET-SORT 45
7.7.2- RADIX-SORT 46
7.7.3- COUNTING-SORT 48
7.8- Altri SORT 48
ESERCIZI E PROBLEMI 49
8- ANALISI DELLA TECNICA “DIVIDE AND CONQUER” 51
9- PROBLEMI P, NP, NP-COMPLETI 52
9.1- Esempi di problemi 52
9.2- La classe P ( problemi di decisione) 54
9.3- La classe NP 55
9.4- La classe di problemi NP-completi 56
9.5- Come affrontare i problemi NP-completi 58

127
A.Laurentini-Analisi degli algoritmi

9.5.1- Esempi di algoritmi approssimati 58


9.5.2- Esempi di tecniche per ridurre i casi esplorati 60
10- ALGORITMI PARALLELI 62
10.1- Definizioni generali 62
10.2- Esempi di algoritmi “Shared memory” 63
10.3- Interconnection networks 65
10.4- Architettura sistolica 65
ESERCIZI E PROBLEMI 66
11- GRAFI 68
11.1- Definizioni e proprietà 68
11.2- Rappresentazione dei grafi 71
11.3- Chiusura transitiva 73
11.4- Visita di un grafo 75
11.5- Albero ricoprente minimo 78
11.6- Albero dei cammini minimi 80
11.7- Alcune definizioni 83
11.8- Grafi planari 84
11.9- Grafi Euleriani e semi-Euleriani 88
11.10- Grafi Hamiltoniani e semi- Hamiltoniani 91
11.11- Clique 92
11.12- Colorabilità 92
11.13- Dominanza 93
11. 14- Indipendenza 95
ESERCIZI E PROBLEMI 98
12- ALGORITMI DI RICERCA 102
12.1- Vettori 103
12.2- Liste concatenate 103
12.3- Alberi di ricerca 103
12.4- HASH table 106
13- CODE PRIORITARIE 112
14- CONSIDERAZIONI CONCLUSIVE 114
15- ESEMPI DI ALGORITMI COMPLESSI 118

128

You might also like