Svolgimento Esercizio 1
Con riferimento al precedente esercizio,
sapresti dimostrare
che in un qualsiasi momento,
ed ove sia un qualsiasi nodo di albero
della nostra foresta con, diciamo,
figli,
allora la cardinalità della discendenza di
,
ossia il numero di nodi del sottoalbero radicato in
,
è almeno
.
Svolgimento Esercizio 2
Si fissi l'attenzione su un qualsiasi istante
della felice storia della nostra famigliola di guardiaboschi.
Si indichi con la cardinalità della discendenza di
.
Per induzione su
,
mostreremo che,
per un qualsiasi nodo
con
figli.
A quel punto il risultato desiderato seguirà come conseguenza
della proprietà di cui al Punto 2 dell'Esercizio 1.
base: Chiaramente, se ha
figli,
allora la sua discendenza si compone solamente di
stesso,
ossia
.
Inoltre, se
ha un figlio,
allora
.
passo:
Siano
i figli di
nell'ordine in cui sono stati innestati in
.
Chiaramente,
.
Mostreremo innanzittutto che
per ogni
.
Infatti, quando
è stato innestato in
,
il nodo
aveva almeno
figli:
.
Siccome una radice può venire innestata come
figlia di un'altra radice solamente quando esse hanno lo stesso
numero di figli, allora
aveva almeno
figli
quando
è stato innestato in
.
Da quel momento
può aver perso al più un solo
figlio, per come è stata definita l'operazione di potatura
e visto che
non è stato potato via da
posteriormente
all'innesto discusso.
Ne consegue che
ha almeno
figli nell'istante
su cui abbiamo deciso di fissare la nostra attenzione,
e quindi, per ipotesi induttiva,
come sopra affermato.
In virtù della proprietà di cui al Punto 1 dell'Esercizio 1 possiamo ora concludere il nostro passo induttivo come segue:
Svolgimento Esercizio 3
Siccome in uno heap di Fibonacci teniamo un puntatore
ad una radice che ospita un elemento a chiave minima
tra le radici (e quindi tra tutti gli elementi ospitati nello heap,
a causa della proprietà dello heap),
non sarà certo difficile restituire un elemento a chiave
minima in
.
Tuttavia vorremmo non solo individuare ma anche
estrarre dallo heap l'elemento indicatoci da .
L'estrazione di per se non è difficoltosa in quanto
la lista delle radici è una lista circolare e doubled linked e quindi ho
a disposizione tutte le informazioni necessarie per aprire
il ``braccialetto'' nel punto indicatomi da
,
estrarre la perla indirizzata da
,
e ricomporre le estremità del ``braccialetto'',
il tutto in
.
Dove si annida il problema?
Il punto è che non sò più a quale radice dovrà
ora puntare ,
e di fatto per scoprirlo non posso evitare di scandirmi
l'intera lista delle radici, di lunghezza
,
impiegando quindi un tempo effettivo di calcolo
.
Visto allora che questo lo debbo comunque spendere,
ne approfitto per giocare una Consolidate,
ossia man mano che,
durante la mia scansione della lista redici,
individuo due radici dello stesso grado,
effettuo allora un innesto di una radice nell'altra come
i nostri amici guardiaboschi di cui all'esercizio precedente.
Ovviamente sceglierò quale radice và innestata nell'altra
con l'avvertenza di mantenere la proprità dell'ordinamento dello heap,
cosa che mi è sempre possibile fare in
.
Il costo effettivo della Consolidate non sforerà
pertanto sul costo asintotico di
richiesto
per la sola scansione dell'intera lista delle radici,
purchè io abbia l'accortezza di avvalermi di un vettore di appoggio
che mi ricordi, per ogni
, se ho lasciato dietro di me nella scansione
una radice con
figli, ed eventualmente il puntatore alla stessa.
In base a quanto dimostrato nell'Esercizio 2,
siamo garantiti che comunque
,
e siccome a seguito della Consolidate nel mio giardino
non saranno presenti alberi della stessa varietà (con radici
aventi lo stesso numero di figli),
allora il numero di radici
a seguito della Consolidate
sarà limitato superiormente da
(ossia
).
Durante la consolidate sarà inoltre mia cura di smarcare
quelle radici che vanno ad innestarsi in altre radici,
in modo da mantenere l'invariante che un nodo sia marcato
se e solo se ha subito la perdita di un figlio per potatura
da quando è andato a scegliersi il padre corrente tramite
suo ultimo innesto.
Pertanto, ove si sia indicato con
ed
il numero di nodi marcati prima e dopo l'esecuzione della Consolidate,
allora
.
Ricordiamo che la funzione potenziale scelta
per condurre l'analisi ammortizzata per gli heaps di Fibonacci
ha la seguente forma:
.
Il costo ammortizzato per l'operazione di EstraiMinimo
sarà pertanto limitato da costo effettivo
meno differenza di potenziale, ossia da
L'unità di misura scelta per l'espressione della funzione potenziale è infatti assunta essere tale da dominare ogni altro fattore costante che appare nel costo effettivo di qualsivoglia operazione prevista per gli heaps di Fibonacci. Solitamente, su una qualsiasi struttura dati, viene definito (ed implementato) solamente un numero finito di operazioni, pertanto questa assunzione è sempre legittima e fa parte della prassi nel progetto di una struttura dati nell'ottica dell'analisi ammortizzata.
Svolgimento Esercizio 3
Faremo riferimento (come black-box)
ad uno heap
ed assumeremo implicitamente che la chiave
dell'elemento
in seno ad
sia il valore di
.
Dimostriamo ora le invarianti proposte:
Svolgimento Esercizio 4
StampaCammino
Per l'analisi della coplessità dell'algoritmo di Dijkstra
si noti che l'operazione di estrazione di un nodo a
minimo è eseguita al più
volte (
se il grafo è
connesso) e quindi possiamo considerare
che il tempo speso in tale operazione sia in tutto
,
poichè l'estrazione del minimo prende
sia con gli heaps Binomiali che con quelli di Fibonacci
(ammortizzata).
Parimenti, ogni nodo viene inserito al più una volta
e quindi il tempo complessivo imputabile ad inserimenti
è
.
Si noti che ogni arco può causare al più un solo decremento
di chiave, e precisamente nell'istante in cui il primo dei suoi
estremi diventa nero.
Pertanto, il tempo speso decrementando chiavi
è dominato complessivamente da
,
dove
nel caso degli heaps Binomiali
e
nel caso degli heaps di Fibonacci,
ove si faccia riferimento all'analisi ammortizzata.
Il tempo complessivo di calcolo sarà pertanto
limitato da
nel caso di implementazione
tramite heaps Binomiali
e da
nel caso di implementazione
tramite heaps di Fibonacci.
Svolgimento Esercizio 5
Sia un vettore di
interi
rappresentati ciascuno da
cifre in base
.
RADISORT
Se l'algoritmo di ordinamento stabile chiamato ogni
volta è il COUNTINGSORT,
allora esso avrà complessità .
Pertanto la complessità di RADIXSORT
sarà
.
L'algoritmo COUNTINGSORT è stabile, ossia in caso di pareggio sulla chiave, viene piazzato prima in output l'elemento che risultava piazzato prima in input. Il RADIXSORT come descritto sopra risulta corretto se l'algoritmo cui fa riferimento è stabile, in quanto possiamo verificare la seguente invariante:
se dei numeri presenti inconsideriamo solo le cifre meno significative, dalla cifra
appena considerata in poi, il vettore
è ordinato.
Controesempio richiesto:
si consideri .
Dopo averlo ordinato sulla cifra meno significativa otterremo
necessariamente
.
Ora però dobbiamo ordinarlo anche sulla cifra più signifivativa.
Se nessuno ci garantisce che l'algoritmo impiegato
per ordinare sulla cifra più significativa sia stabile,
allora non possiamo escludere che l'output sia
. Ossia un output errato.