


















Studia grazie alle numerose risorse presenti su Docsity
Guadagna punti aiutando altri studenti oppure acquistali con un piano Premium
Prepara i tuoi esami
Studia grazie alle numerose risorse presenti su Docsity
Prepara i tuoi esami con i documenti condivisi da studenti come te su Docsity
Trova i documenti specifici per gli esami della tua università
Preparati con lezioni e prove svolte basate sui programmi universitari!
Rispondi a reali domande d’esame e scopri la tua preparazione
Riassumi i tuoi documenti, fagli domande, convertili in quiz e mappe concettuali
Studia con prove svolte, tesine e consigli utili
Togliti ogni dubbio leggendo le risposte alle domande fatte da altri studenti come te
Esplora i documenti più scaricati per gli argomenti di studio più popolari
Ottieni i punti per scaricare
Guadagna punti aiutando altri studenti oppure acquistali con un piano Premium
I concetti fondamentali della teoria della computazione e della computabilità, tra cui il problema dell'arresto, il problema della totalità, i problemi di decisione e semidecidibilità, gli insiemi ricorsivi e ricorsivamente enumerabili, il teorema del punto fisso di Kleene e la riduzione. Vengono inoltre presentate le tesi di Church e l'enumerazione algoritmica degli insiemi. utile per gli studenti di informatica e matematica interessati alla teoria della computazione e alla computabilità.
Tipologia: Schemi e mappe concettuali
1 / 26
Questa pagina non è visibile nell’anteprima
Non perderti parti importanti!



















Problema risolvibile: esiste una MT che lo risolve~decidibile Problema irrisolvibile~indecidibile
Halting problem : indecidibile (ma semidecidibile) → Nessuna TM può decidere se, data una generica TM M e un
generico ingresso x, M si arresta (in uno stato finale) con l’ingresso x.
Lemma : ℎ
′
𝑥
non è computabile.
Se un problema è non risolvibile →{
𝑢𝑛𝑎 𝑠𝑢𝑎 𝑔𝑒𝑛𝑒𝑟𝑒𝑙𝑖𝑧𝑧𝑎𝑧𝑖𝑜𝑛𝑒 è 𝑛𝑜𝑛 𝑟𝑖𝑠𝑜𝑙𝑣𝑖𝑏𝑖𝑙𝑒
Se un problema è risolvibile →{
𝑢𝑛 𝑠𝑢𝑜 𝑐𝑎𝑠𝑜 𝑝𝑎𝑟𝑡𝑖𝑐𝑜𝑙𝑎𝑟𝑒 è 𝑟𝑖𝑠𝑜𝑙𝑣𝑖𝑏𝑖𝑙𝑒
Problema della totalità : la funzione k(y)=1 se 𝑓
𝑦
è totale non è computabile. Dato un (generico) programma, vogliamo
sapere se terminerà l’esecuzione per qualsiasi dato in ingresso o se potrà, per qualche dato, andare in loop infinito.
Problema di decisione : domanda che ha due possibili risposte sì o no, la domanda riguarda un qualche ingresso. È
quindi un problema di calcolo di una funzione totale che ha come immagini 0 e 1.
Problema semidecidibile : problema per il quale c’è un algoritmo che dice sì se la risposta è sì, può andare in loop se la
risposta è no.
Funzione caratteristica : 𝑐 𝑠
Insieme ricorsivo (decidibile): la sua funzione caratteristica è computabile.
Insieme ricorsivamente enumerabile (semidecidibile) : se S è l’insieme vuoto o S è l’immagine di una funzione g s
totale e computabile.
Teorema ½ + ½ = 1 : Se S è ricorsivo allora è anche ricorsivamente enumerabile. S è ricorsivo sse sia S sia il suo
complementare sono ricorsivamente enumerabili.
La classe di insiemi decidibili è chiusa rispetto al complemento.
Se S è l’insieme di indici di funzioni totali e computabili allora S non è RE: non esiste un formalismo RE che possa
definire tutte le funzioni computabili e totali e solo quelle.
Teorema : S è RE ↔ S = D h
(h: computabile e parziale) | S è RE ↔ S = I g
(g: computabile e parziale).
Teorema del punto fisso di Kleene : sia t una funzione totale e computabile. Allora si può sempre trovare un intero p
tale che 𝑓 𝑝
𝑡(𝑝)
, la funzione 𝑓
𝑝
è detta punto fisso di t.
Teorema di Rice : sia F un insieme di funzioni computabili. L’insieme S degli indici delle TM che calcolano le funzioni
di F: 𝑆 = {𝑥 | 𝑓
𝑥
∈ 𝐹} è decidibile sse 𝐹 = ∅ o F è l’insieme di tutte le funzioni computabili, in tutti i casi non banali S
non è decidibile.
Riduzione : a. Vogliamo verificare se x∈S, siamo in grado di verificare se y∈ 𝑆′, se c’è una funzione computabile e
totale t tale che 𝑥 ∈ 𝑆 ↔ 𝑡(𝑥) ∈ 𝑆′ allora possiamo rispondere alla domanda ‘x∈S’ in modo algoritmico.
b. Vogliamo sapere se possiamo risolvere 𝑥 ∈ 𝑆, sappiamo che non si può risolvere 𝑦 ∈ 𝑆′ (𝑆′ non è
decidibile), se troviamo una funzione t computabile e totale tale che 𝑦 ∈ 𝑆
′
∈ 𝑆 allora possiamo concludere
che 𝑥 ∈ 𝑆 non è decidibile.
Tesi di Church
Non c'è nessun formalismo per modellare il calcolo meccanico che sia più potente della TM (o formalismi equivalenti).
→Tutti i problemi risolvibili sono tutti e soli quelli risolvibili da una MT.
Enumerazione algoritmica
Un insieme S può essere enumerato algoritmicamente ( E ) se possiamo trovare una biiezione tra e S e 𝑁:
→Le TM possono essere enumerate algoritmicamente: è possibile associare a ciascuna macchina di Turing un indice
sull'insieme dei naturali (detto numero di Goedel) che la descrive in maniera univoca E :{TM}↔ 𝑁.
Possiamo sempre scrivere un programma in C (ovvero una TM) che, dato n, produce l'n-esima TM, e viceversa. E (M)
è detto numero di Gödel di M e E è una Gödelizzazione. Il numero di Gödel in particolare identifica l'indice della
macchina di Turing.
Convenzioni
Se n non appartiene al dominio di f, scriverò 𝑓(𝑛) =⊥
Se P è un programma (una MT) e n un suo possibile input, scriverò P(n)↓ per dire che P termina la computazione su n,
P(n)↑ altrimenti.
Poiché parliamo di numeri, le seguenti nozioni saranno considerate equivalenti:
Macchine di Turing Universali
Una MT universale è una TM programmabile, che computa la funzione 𝑔
𝑦
(𝑥), in cui 𝑓
𝑦
(𝑥) è la funzione
calcolata dall'y-esima TM sull'ingresso x. → Le UTM non possono computare tutte le funzioni.
Risolvibilità e soluzioni
Sapere che un problema è risolvibile non vuol dire che sappiamo come risolverlo (e quale sia la soluzione).
→Un problema è risolvibile se esiste una TM che lo risolve.
Caso banale: il problema consiste nel rispondere a una domanda booleana [si/no]. Un problema è una funzione, e
risolverne uno implica calcolare una funzione. Nel caso banale: se un problema è booleano (anche detto problema di
decisione) è decidibile, ovvero esiste una macchina di Turing che può risolverlo.
Giacché le macchine di Turing possono essere enumerate e che tutti i problemi computabili sono tutti e soli quelli
risolvibili dalle MT, possiamo concludere che l'insieme dei problemi computabili ha la stessa cardinalità dei numeri
naturali.
Problemi noti decidibili
Se siamo in grado di trovare un algoritmo che termina sempre il problema è decidibile. (f calcolabile e totale)
calcolabile (essendo descrivibile mediante una tabella finita).
di input è finito.
Se 𝑥 ∈ { 0 , … , 2
61
− 1 }? Sì
Problemi noti semidecidibili
Se troviamo un algoritmo che potrebbe non terminare ma se lo fa restituisce ‘Sì’ come risposta il problema è
semidecidibile. (f calcolabile e non totale)
Problemi noti indecidibili
con l'ingresso x. Con arresto si intende arresto in uno stato finale. Formalmente, nessuna TM può calcolare la
funzione totale 𝑔: 𝑁𝑥𝑁 → { 0 , 1 } definita come 𝑔
𝑦
𝑖
Lemma
La funzione ℎ
′
𝑥
non è computabile (calcolabile).
funzione y è definita per ogni x su N.
In formule: 𝑘
𝑦
𝑦
𝑦
non è computabile (calcolabile).
In realtà questo problema è una versione ancora più generale dell’halting, in quanto ci chiediamo se dato un
programma esso terminerà sempre a prescindere dagli input (nel problema dell'halt invece si chiede se un dato
programma con un dato input termina o meno).
Teorema di Rice
più generale del problema di stabilire se un programma calcola una funzione data.
ecc.)?
0
={x | z 0
𝑓 𝑥
}(insieme delle funzioni computabili che hanno z 0
nella loro immagine) non ricorsivo per Rice.
Riduzione : ci si riconduce a problemi noti non decidibili (halt della MT, funzione totale, ecc.)
Un problema P' è ridotto a un problema P se un algoritmo per risolvere P viene usato per risolvere P', cioè:
algoritmicamente la soluzione dell'istanza di P' dalla soluzione dell'istanza di P.
➔ Il problema si riduce ad un problema conosciuto universalmente come decidibile, semidecidibile o non
decidibile. Esempio: sapere se una macchina di Turing termina non un problema decidibile.
Formalmente, se 𝐴, 𝐵 ⊆ 𝑁, una funzione 𝑟: 𝑁 → 𝑁 è una riduzione di A a B (si scrive 𝐴 →
𝑟
𝐵) se:
Se 𝐴 →
𝑟
𝐵 allora: 1.Se A è non ricorsivo → anche B è non ricorsivo; 2. Se B è ricorsivo → anche A è ricorsivo.
Dall’indecidibilità del problema dell’arresto della MT deduciamo l’indecidibilità del problema della terminazione del
calcolo in generale:
È decidibile dire se, durante l’esecuzione di un generico programma P si accede ad una variabile non inizializzata?
avendo cura di usare variabili non presenti in Q
problema della terminazione del calcolo.
Dimostrazione mediante reductio ad absurdum : si suppone per assurdo che un problema sia decidibile: se si giunge
a delle contraddizioni allora la tesi sbagliata ed il problema non decidibile.
Modelli di calcolo e loro complessità
Data la computazione 𝑐 0
∗
𝑟
di una macchina di Turing M a k nastri deterministica:
Si definisce complessità temporale: 𝑇 𝑚
(𝑥) = 𝑟 se M termina in 𝑐
𝑟
Si definisce complessità spaziale: 𝑆 𝑚
(𝑥)=la somma delle quantità massime di nastro occupate, per ogni nastro.
𝑆
𝑚
(𝑥)
𝑘
𝑚
𝐹𝑆𝐴
𝐹𝑆𝐴
𝐴𝑃𝐷
𝐴𝑃𝐹
Notazioni
g, cioè ∃𝑛
0
0
∶ 𝑓(𝑛) ≤ 𝑐 ∙ 𝑔(𝑛), ovvero: lim
𝑛→∞
𝑓(𝑛)
𝑔(𝑛)
𝑛𝑜𝑛 è 𝑖𝑛𝑓𝑖𝑛𝑖𝑡𝑜
La notazione O-grande indica il limite asintotico superiore della complessità, ovvero un valore sicuramente
più grande del reale valore della complessità della funzione all'infinito.
g, cioè ∃𝑛
0
0
∶ 𝑓(𝑛) ≥ 𝑐 ∙ 𝑔(𝑛), ovvero: lim
𝑛→∞
𝑓(𝑛)
𝑔(𝑛)
è > 0
La notazione Omega-grande indica il limite asintotico inferiore della complessità, ovvero un valore
sicuramente più piccolo del reale valore della complessità della funzione all'infinito.
da multipli di g, cioè ∃𝑛
0
0
∶ 𝑑 ∙ 𝑔(𝑛) ≤ 𝑓(𝑛) ≤ 𝑐 ∙ 𝑔(𝑛), ovvero: 0 < lim
𝑛→∞
𝑓(𝑛)
𝑔(𝑛)
La notazione Teta-grande indica il limite asintotico esatto della complessità.
Nozioni di base per l’analisi di complessità
volte che esso viene eseguito (per indici incrementali da 0 a q la complessità è O(q), se l'indice viene
moltiplicato o diviso per un numero k ad ogni ciclo allora la complessità è log
𝑘
Teoremi di accelerazione lineare
𝑀
(𝑛), per ogni 𝑐 ∈ 𝑅
posso costruire una TM M' a k nastri che
accetta L con 𝑆
𝑀
′
𝑀
𝑀
(𝑛), posso costruire una TM M' a 1 nastro (non nastro singolo)
che accetta L con 𝑆
𝑀
𝑀
(𝑛) (concateno i contenuti dei k nastri su uno solo).
𝑀
(𝑛), per ogni 𝑐 ∈ 𝑅
posso costruire una TM M' a 1 nastro che
accetta L con 𝑆
𝑀
𝑀
𝑀
(𝑛), per ogni 𝑐 ∈ 𝑅
posso costruire una TM M' a k+1 nastri che
accetta L con 𝑇
𝑀
′ (𝑛) = max (𝑛 + 1 , 𝑐𝑇
𝑀
Macchina RAM
La macchina RAM è dotata di un nastro di lettura In, uno di scrittura Out, ed è dotata di una memoria con accesso a
indirizzamento diretto: l'accesso non necessita di scorrimento delle celle.
Criterio di costo logaritmico
Il criterio logaritmico viene introdotto per sopperire alle non idealità della macchina RAM: fare qualunque operazione
su un intero costa quanto il suo numero di cifre in base b: log 𝑏
𝑖 = Θ(log
), il costo delle operazioni aritmetico-logiche
elementari dipende dall'operazione (definiamo 𝑑 = log 2
2
2
Per i cicli bisogna considerare che se gli indici vengono incrementati di valori costanti allora la complessità del ciclo è
2
𝑛) (ovvero n il numero di giri e log(n) per tener conto dell'addizione), mentre se l'indice viene moltiplicato o
diviso per k allora la complessità diventa 𝑂(log 𝑘
𝑛 𝑙𝑜𝑔(𝑛)). Visto che per cambiare la base di un logaritmo è sufficiente
dividere il suo valore per una costante che dipende dalle due basi la complessità diventa 𝑂((𝑙𝑜𝑔(𝑛))
2
Tabella costi logaritmici
Rapporti tra criterio di costo
Ci sono casi in cui la macchina RAM è peggiore della macchina di Turing: un esempio è 𝐿 = {𝑤𝑐𝑤
𝑟
} in cui la macchina
RAM ha costo minimo Θ(𝑛𝑙𝑜𝑔(𝑛)) per colpa del contatore.
Teorema di correlazione polinomiale
Sotto ragionevoli ipotesi di criteri di costo, se un problema è risolvibile mediante il modello 𝑀 1
con complessità (spaziale
o temporale) 𝐶 1
(𝑛), allora è risolvibile da un qualsiasi altro modello (Turing completo) 𝑀
2
con complessità 𝐶
2
1
), dove 𝜋(. ) è un opportuno polinomio.
Correlazione tra TM a k nastri e RAM
𝑅𝐴𝑀
(𝑛)) per simulare una mossa della RAM.
𝑅𝐴𝑀
(𝑛) essa effettua al più 𝑇
𝑅𝐴𝑀
(𝑛) mosse.
𝑅𝐴𝑀
2
Note:
𝑛
𝑘= 1
𝑛
( 𝑛+ 1
)
2
𝑛 2
𝑘= 1
𝑛(𝑛+ 1 )( 2 𝑛+ 1 )
6
𝑛 3
𝑘= 1
𝑛
2
(𝑛+ 1 )
2
4
𝑛 𝑘
𝑘= 0
𝑥
𝑛+ 1
− 1
𝑥− 1
𝑘
∞
𝑘= 0
1
1 −𝑥
𝑛 𝑘
𝑘= 0
𝑎−𝑎𝑟
𝑛+ 1
1 −𝑟
𝑛 𝑘
𝑘= 0
𝑛+ 1
Ridurre la complessità:
vettore.
Ordinamento:
) vale solo per gli algoritmi basati su scambi.
come 2
2
𝑛
, invece la sua memorizzazione a criterio di costo logaritmico costa il logaritmo del suo valore ( 2
𝑛
), mentre
a costo costante costa 1.
) per
memorizzare l’i-esimo elemento, e quindi 𝜃(𝑛𝑙𝑜𝑔
) per memorizzare l’intera sequenza.
Linguaggi:
costo logaritmico e di costo costante, infatti essendo essi riconoscibili mediante memoria finita, una macchina RAM
utilizzata per il loro riconoscimento non ha bisogno di una quantità di memoria dipendente dai dati, quindi qualsiasi
operazione, anche a criterio di costo logaritmico, richiede un tempo limitato a priori. Invece alcuni linguaggi non
regolari richiedono un tempo di riconoscimento che, valutato con costo logaritmico è necessariamente superiore alla
complessità valutata a criterio di costo costante.
), allora è possibile riconoscere
anche il suo complemento con la stessa complessità. Infatti, una MT che riconosce un linguaggio ricorsivo L può
essere sempre trasformata in una MT che riconosce 𝐿
𝑐
semplicemente scambiando accettazione con non
accettazione all'ultima mossa che comporta l'arresto della MT. Ciò evidentemente non altera la complessità.
𝜃(𝑓(𝑛)), allora è possibile riconoscere anche il suo complemento con la stessa complessità. Infatti, il complemento
di un linguaggio ricorsivamente enumerabile potrebbe non essere neanche ricorsivamente enumerabile e quindi non
esistere una MT che lo accetti.
2
con complessità 𝜃(𝑓
1
2
). Infatti, basta
costruire una MT che simuli prima M1 e poi M2 e accetti solo se accettano entrambe: la complessità è la somma
delle due complessità.
2
con complessità 𝜃(𝑀𝑎𝑥{𝑓
1
2
}). Infatti, basta
costruire una MT che simuli M1 e M2 in parallelo.
2
con complessità 𝜃 < 𝜃(𝑓
1
2
). Infatti, l'intersezione
tra due linguaggi può essere linguaggio vuoto, riconoscibile in tempo costante.
simulazione parallela da parte della macchina nostro singolo).
complessità temporale 𝜃(𝑛). Infatti, la MT a K nastri può simulare l'automa a pila deterministico senza alterare la
complessità ed è noto che ogni automa a pila deterministico ha complessità lineare.
singolo con complessità temporale 𝜃(𝑛). Infatti, è stato dimostrato che il linguaggio 𝑤𝑐𝑤
𝑅
richiede una complessità
almeno 𝜃(𝑛
2
) per una MT a nastro singolo.
complessità temporale 𝜃(𝑛). Infatti, è stato dimostrato che il linguaggio 𝑤𝑐𝑤
𝑅
richiede una complessità almeno
temporale 𝜃(𝑛). Infatti, la MT a nastro singolo può emulare un automa a stati finiti senza alterarne la complessità.
Infatti, la RAM può emulare un automa a stati finiti con una quantità di memoria finita e quindi in modo tale che
ogni singola mossa costi un valore costante anche a criterio di costo logaritmico.
𝜃 < 𝜃(𝑙𝑜𝑔𝑛). Infatti, un linguaggio regolare può essere riconosciuto usando una quantità di memoria costante.
Altro:
categoria che risolve un certo problema, posso sempre costruirne una di un’altra categoria con prestazioni peggiori.
comunque inferiore a quella ottenuta mediante una qualsiasi macchina RAM, infatti il problema della riscrittura in
ordine inverso di una sequenza di caratteri può essere risolto da una MT in tempo lineare. Siccome però esso richiede
la memorizzazione di tutti i dati prima di poterli scrivere, la memorizzazione dell’i-esimo dato richiederà ad una
RAM un tempo 𝜃(log
). Ciò comporta una complessità totale almeno 𝜃(𝑛𝑙𝑜𝑔
RAM è comunque inferiore a quella ottenuta mediante una qualsiasi MT, infatti sappiamo che l'accesso, ad esempio
all’elemento mediano di una sequenza di n elementi richiede un tempo 𝜃(𝑙𝑜𝑔𝑛) mediante una RAM ma
necessariamente almeno 𝜃(𝑛) mediante una MT.
RAM, la cui complessità, valutata a criterio di costo logaritmico sia <= di un opportuno polinomio applicato alla
complessità valutata a criterio di costo costante. Infatti, basta che A sia un algoritmo che simuli il comportamento
di una MT che calcola f.
per cui non esiste un algoritmo A1 ad esso equivalente la cui complessità, valutata a criterio di costo logaritmico sia
<= di un opportuno polinomio applicato alla complessità di A, valutata a criterio di costo costante. Infatti, è ben
noto che la funzione 2
2
𝑛
è calcolabile in tempo lineare a criterio di costo costante ma ha complessità spaziale, e
quindi temporale, almeno esponenziale a criterio logaritmico.
a criterio di costo costante che a criterio di costo logaritmico. Non è quindi possibile trovare un algoritmo che a
criterio di costo costante abbia complessità minore che a criterio di costo logaritmico.
Nota: la coda funziona in modo "circolare", ovvero:
prossimo valore di Q.tail sarà 1
Una lista doppiamente concatenata è fatta di oggetti con 3 attributi:
Implementazione della lista doppiamente concatenata
Sia x un oggetto nella lista:
Operazioni su liste doppiamente concatenate
I dizionari sono insiemi dinamici che supportano solo le operazioni di INSERT, DELETE, SEARCH. Agli oggetti di un
dizionario si accede tramite le loro chiavi. Assumiamo che le chiavi siano numeri naturali, e che la cardinalità m
dell'insieme delle possibili chiavi U è ragionevolmente piccola, così da realizzare un dizionario tramite un array di
puntatori di m elementi: in questo modo si ha indirizzamento diretto , e l'array prende il nome di tabella a indirizzamento
diretto. Ogni elemento T[k] dell'array contiene il riferimento all'oggetto di chiave k , se un tale oggetto è stato inserito
in tabella, NIL altrimenti.
Operazioni sulle tabelle a indirizzamento diretto:
Una tabella di hash è un caso speciale di dizionario, la quale usa una memoria proporzionale al numero di chiavi
effettivamente memorizzate al suo interno. Il vantaggio rispetto alle altre strutture dati è che in generale tutte le
operazioni sono O(1).
Idea fondamentale: un oggetto di chiave k è memorizzato in tabella in una cella di indice h(k) , dove h è una funzione
hash :
Problema delle collisioni: ho |U| possibili chiavi, ed una funzione che le deve mappare su un numero m < | U |, quindi
necessariamente avrò molte chiavi diverse tali che ℎ
1
2
). Per risolvere questi problemi si adottano diverse
strategie:
gli elementi vengono semplicemente aggiunti alla lista ogni volta che ne viene richiesto l'inserimento risolvendo
difatti il problema della collisione.
Definiamo fattore di carico 𝛼 =
𝑛
𝑚
dove n è il numero di elementi memorizzati ed m il numero di slot disponibili nella
hashtable. Supponendo che la probabilità di collidere sia 1/m allora la lunghezza media di una lista è 𝐿 = 𝑛
1
𝑚
Quindi il tempo medio per cercare una chiave k nella hashtable con indirizzamento chiuso è 𝑇(𝑛) = 𝜃( 1 + 𝛼).
Se n = O(m) , allora 𝛼 = 𝑂( 1 ), quindi in media ci mettiamo tempo costante.
tutte le chiavi. L'idea è quella di calcolare l'indice dello slot in cui va memorizzato l'oggetto; se lo slot è già
occupato, si cerca nella tabella uno slot libero. La ricerca non viene fatta in ordine: la sequenza di ricerca (detta
sequenza di ispezione ) è un valore calcolato dalla funzione hash.
Ispezione lineare: ℎ
′
𝑚𝑜𝑑 𝑚 → si individua il primo slot libero dopo quello
individuato dalla funzione di hash. Soffre di problemi di addensamento in quanto vi sono molte celle
consecutive occupate che peggiorano le prestazioni in fase di ricerca.
Ispezione quadratica: ℎ(𝑘, 𝑖) = (ℎ
′
1
2
2
) 𝑚𝑜𝑑 𝑚 → in questo caso si procede per tentativi
controllando gli slot, i è il numero di tentativi fatti e c 1
e c 2
scelte in modo che la sequenza di tentativi
copra (prima o poi) tutta la tabella (altrimenti si rischia il loop). Anche in questo caso chiavi con la stessa
posizione danno luogo alla stessa sequenza di ispezione.
Hashing doppio: ℎ(𝑘, 𝑖) = (ℎ
1
2
2
altra funzione di hash e i è il numero di
tentativo di inserimento. In questo caso è bene che ℎ
2
(𝑘) sia sempre primo rispetto a m.
Operazioni in caso di indirizzamento aperto:
È possibile utilizzare un vettore per contenere le chiavi dell’albero (efficiente se l’albero è completo).
2i+1.
Note:
➢ Isomorfismo di alberi: dati due alberi T e T’ determinare se hanno la stessa struttura a meno del valore delle chiavi
e dei dati: faccio la visita degli alberi in parallelo, prima di continuare la visita controllo che ci sia il figlio per
entrambi gli alberi → O(n)
➢ In un albero binario pieno , se ho n nodi, il numero delle foglie è
𝑛+ 1
2
, mentre il numero di nodi interni è
𝑛− 1
2
➢ Algoritmo per contare i nodi di un albero: contatutti(A,r) → O(n)
Un albero binario di ricerca o BST è un albero binario in cui per ogni nodo x vale la proprietà che tutti i nodi del
sottoalbero sinistro siano minori o uguali di x e tutti quelli del sottoalbero destro siano maggiori.
~Un vettore più essere trasformato in un albero binario di ricerca tramite l'algoritmo di Heapsort che viene eseguito in
Algoritmi di visita e operazioni
È possibile attraversare un albero secondo diversi modi:
(Stampa le chiavi in ordine).
sono uguali, l'elemento è quello cercato, se la chiave della
radice è più grande cerca nel sottoalbero sinistro, se la chiave
della radice è più piccola cerca nel sottoalbero destro. Il tempo
di esecuzione è O(h), con h l'altezza dell'albero.
algoritmi hanno tempo di esecuzione O(h).
x in un BST è l'elemento y del BST tale che y.key è
la più piccola (risp. più grande) tra le chiavi che sono
più grandi (risp. piccole) di x.key. Quindi, se il
sottoalbero destro di un oggetto x dell'albero non è
vuoto, il successore di x è l'elemento più piccolo
(cioè il minimo ) del sottoalbero destro di x. Se il
sottoalbero destro di un oggetto x dell'albero è vuoto,
il successore di x è il progenitore più prossimo a x
per cui x appare nel suo sottoalbero sinistro.
Il tempo di esecuzione è O(h).
raggiunge il posto in cui il nuovo elemento deve essere inserito, ed aggiunge questo
come foglia. Il tempo di esecuzione è O(h).
da cancellare:
se z non ha sottoalberi: si mette a NIL il puntatore del padre di z
che puntava a z
se z ha 1 sottoalbero: bisogna spostare l'intero sottoalbero di z in
su di un livello
se z ha 2 sottoalberi: bisogna trovare il successore del nodo z da
cancellare, copiarne la chiave in z , quindi cancellare il successore.
Il tempo di esecuzione per tree-delete è O(h). [y: nodo da eliminare,
x: quello con cui lo sostituiamo]
Altezza di un albero (n è il numero di nodi):
dei 2 sottoalberi di x differiscono al massimo di 1.
h
Note:
➢ Costruire un BST bilanciato con chiavi in un array A di lunghezza n.
centrale del sottoarray di sinistra, a destra l’elemento centrale del sottoarray di destra, e itero questo
procedimento → 𝑂(𝑛𝑙𝑜𝑔
La routine per l'inserimento dei nodi è la seguente:
Fondamentalmente la routine si comporta come un inserimento
normale in un BST solo che il nodo appena inserito è sempre rosso
(per evitare di cambiare l'altezza nera) e i suoi figli puntano a
T.NIL (e non a NULL). Alla fine della procedura, tuttavia, è
possibile che siano state violate delle proprietà degli alberi. Per
aggiustare tutte le possibili violazioni si richiama la routine RB-
I casi di violazione a cui ci si riferisce nel codice sono i seguenti:
potrebbe avere un padre rosso, se x.p è la radice posso colorarla
di nero.
LeftRotate(T,x), ora la riparazione va eseguita su z, con
z.left=x rosso (Caso 3).
ed eseguiamo RightRotate(T,p.x).
rosso e figlio destro nero. Ricade nel caso 4.
rosso.
La routine per la cancellazione è la seguente:
Come per la INSERT esiste una routine in grado di aggiustare l'albero
qualora qualche proprietà sia stata violata:
I casi riportati si riferiscono ai seguenti:
dall'1 allora il genitore di x è rosso.
Note:
➢ Operazioni di ‘fixaggio’ su alberi rosso neri senza puntatori al padre:
in modo di sistemare la colorazione
→ prima di inserire, mi accerto con modifiche locali, che sto scendendo lungo una direzione nera, non devo
cambiare le black height.
assumendo che il padre sia nero, perché io parto dalla radice che è nera e per induzione non posso generare
violazioni nuove). Quindi se il fratello del nodo verso cui voglio dirigermi è rosso, lo coloro di nero ed i figli di
rosso.
del nodo rosso verso sopra, successivamente lo coloro di nero ed il figlio di rosso: adesso il nodo della direzione
in cui volevo andare è diventato nero e continuo la ricerca su di lui. Quindi se il fratello del nodo verso cui
voglio dirigermi è nero, ruoto il nodo su cui voglio dirigermi e scambio il colore con il fratello.
➔ Nell’inserimento, prima di richiamare la insert al nodo successivo, se questo è rosso, applicare una delle due
soluzioni risolutive.
Un heap è una struttura dati ad albero, la chiave del nodo padre è sempre maggiore (max-heap) di quella dei figli, e non
vi è nessuna relazione tra le chiavi di due fratelli. Un heap binario è un albero binario quasi completo, ovvero che ha
tutti i livelli occupati tranne al più l'ultimo che può essere anche incompleto fino ad un certo punto (partendo da sinistra).
È conveniente materializzare lo heap come una struttura dati implicita:
stoccato.
Code con priorità
Una coda con priorità è una struttura dati a coda in cui è possibile dare una priorità numerica agli elementi all’interno.
Elementi con priorità maggiore verranno estratti sempre prima di elementi con priorità minore indipendentemente
dall’ordine di inserimento. L’implementazione più comune di una coda con priorità è un max-heap: la priorità di un
elemento è data dalla sua chiave.
Max-Heapify
Max-Heapify riceve un array e una posizione in esso: assume che i due
sottoalberi con radice stoccata in Left(i)=2i e Right(i)=2i+1 siano dei max
heap, e modifica A in modo che l’albero radicato in i sia un max-heap.
La procedura causa la discesa del nuovo valore verso le foglie sino al punto
in cui è maggiore dei figli.
La complessità temporale di maxheapify è proporzionale all'altezza
dell'albero T(n) = O(log(n)).
Build Max-Heap
A questo punto dato un array da ordinare lo trasformiamo in un
max-heap mediante la seguente (si parte dalla metà dell'array perché
la parte destra contiene tutti i nodi foglia che sono già max-heap per
definizione). La complessità temporale è 𝑇(𝑛) = 𝑂(𝑛𝑙𝑜𝑔(𝑛)), da
una più profonda analisi otteniamo che è in realtà 𝑂(𝑛).