Docsity
Docsity

Prepara i tuoi esami
Prepara i tuoi esami

Studia grazie alle numerose risorse presenti su Docsity


Ottieni i punti per scaricare
Ottieni i punti per scaricare

Guadagna punti aiutando altri studenti oppure acquistali con un piano Premium


Guide e consigli
Guide e consigli


Algoritmi e strutture dati, Appunti di Algoritmi E Strutture Di Dati

Gli appunti seguono fedelmente tutto il programma svolto a lezione e comprendono le slide con eventuali chiarimenti del professore. Sono presenti annotazioni negli pseudocodici che aiutano nella loro comprensione.

Tipologia: Appunti

2023/2024

In vendita dal 03/10/2024

alefers
alefers 🇮🇹

5

(2)

10 documenti

1 / 251

Toggle sidebar

Questa pagina non è visibile nell’anteprima

Non perderti parti importanti!

bg1
ALGORITMI E STRUTTURE DATI
pf3
pf4
pf5
pf8
pf9
pfa
pfd
pfe
pff
pf12
pf13
pf14
pf15
pf16
pf17
pf18
pf19
pf1a
pf1b
pf1c
pf1d
pf1e
pf1f
pf20
pf21
pf22
pf23
pf24
pf25
pf26
pf27
pf28
pf29
pf2a
pf2b
pf2c
pf2d
pf2e
pf2f
pf30
pf31
pf32
pf33
pf34
pf35
pf36
pf37
pf38
pf39
pf3a
pf3b
pf3c
pf3d
pf3e
pf3f
pf40
pf41
pf42
pf43
pf44
pf45
pf46
pf47
pf48
pf49
pf4a
pf4b
pf4c
pf4d
pf4e
pf4f
pf50
pf51
pf52
pf53
pf54
pf55
pf56
pf57
pf58
pf59
pf5a
pf5b
pf5c
pf5d
pf5e
pf5f
pf60
pf61
pf62
pf63
pf64

Anteprima parziale del testo

Scarica Algoritmi e strutture dati e più Appunti in PDF di Algoritmi E Strutture Di Dati solo su Docsity!

ALGORITMI E STRUTTURE DATI

ALGORITMI E STRUTTURE DATI

    1. INTRODUZIONE AGLI ALGORITMI E ALLE STRUTTURE DATI Sommario
    • 1.1 PROBLEMI COMPUTAZIONALI
    • 1.2 ALGORITMI
    • 1.3 MODELLI DI CALCOLO
    • 1.4 MODELLO DI COSTO
    • 1.5 PSEUDOCODICE
    • 1.6 PROGRAMMI
    • 1.7 STRUTTURE DATI
    1. RICHIAMI DI MATEMATICA
    • 2.1 PROPRIETÀ DELLE POTENZE
    • 2.2 PROPRIETÀ DEI LOGARITMI
    • 2.3 FLOOR E CEILING
    • 2.4 ARITMETICA MODULARE
    • 2.5 FATTORIALE
    • 2.6 SOMMATORIE
    • 2.7 INDUZIONE MATEMATICA
    • 2.7.1 INDUZIONE E TIPI RICORSIVI
    • 2.7.2 CORRETTEZZA DEGLI ALGORITMI: INVARIANTE DI CICLO
    1. COMPLESSITÀ DI ALGORITMI E PROBLEMI
    • 3.2 COMPLESSITA’ DI UN ALGORITMO
    • 3.3 ANALISI DI COMPLESSITA’ DEGLI ALGORITMI
    • 3.3.1 PRESENZA DI INVOCAZIONI
    • 3.3.2 CASO MIGLIORE – CASO PEGGIORE – CASO MEDIO
    • 3.4 ANALISI ASINTOTICA
      • 3.4.1 NOTAZIONE ASINTOTICA
      • 3.4.2 PROPRIETÀ DELLE NOTAZIONI ASINTOTICHE
      • 3.4.3 USO DELLE NOTAZIONI ASINTOTICHE E FUNZIONI DI RIFERIMENTO
      • 3.4.4 ANALISI DI ALGORITMI TRAMITE LA NOTAZIONE ASINTOTICA
      • 3.4.5 CONSIDERAZIONI SULL'ANALISI ASINTOTICA
    • 3.5 COMPLESSITA’ DEI PROBLEMI
      • 3.5.1 Esempi
      • 3.5.2 PROBLEMI TRATTABILI E NON TRATTABILI
    1. DIVIDE ET IMPERA E RICORRENZE
    • 4.1 RISOLUZIONE DI UN PROBLEMA CON LA TECNICA DIVIDE ET IMPERA
    • 4.2 RISOLUZIONE DI RICORRENZE
      • 4.2.1 METODO DI SOSTITUZIONE
      • 4.2.2 METODO DELL’ALBERO DI RICORSIONE
      • 4.2.3 METODO DELL’ESPERTO (MASTER THEOREM)
    1. STRUTTURE DATI ELEMENTARI
    • 5.1 RECAP STRUTTURE DATI
    • 5.2 PILE
      • 5.2.1 IMPLEMENTAZIONE DI UNA PILA E OPERAZIONI
      • 5.2.2 APPLICAZIONE DELLE PILE
    • 5.3 CODE
      • 5.3.1 IMPLEMENTAZIONE DI UNA CODA E OPERAZIONI
    • 5.4 LISTE
      • 5.4.1 IMPLEMENTAZIONE DI UNA PILA E OPERAZIONI
      • 5.4.2 COMPLESSITA’ AMMORTIZZATA
      • 5.4.3 LISTE COLLEGATE
      • 5.4.4 LISTE COLLEGATE VS LISTE CON ARRAY
    • 5.5 ALBERI RADICATI
      • 5.5.1 DEFINIZIONI
      • 5.5.2 ALBERI BINARI
      • 5.5.3 ALBERI 𝒎 − 𝑨𝑹𝑰
      • 5.5.4 ALBERI QUALSIASI
      • 5.5.5 VISTE DI ALBERI
    1. ORDINAMENTO
    • 6.1 PROBLEMA DELL’ORDINAMENTO
    • 6.2 SELECTION SORT
    • 6.3 BUBBLE SORT
      • 6.3.1 MIGLIORAMENTO DEL BUBBLE SORT
    • 6.4 INSERTION SORT
    • 6.5 MERGE SORT
    • 6.6 HEAP SORT
      • 6.6.1 HEAP BINARIO
      • 6.6.2 HEAP SORT
    • 6.7 QUICK SORT
      • 6.7.1 PROCEDURA PARTITION
      • 6.7.2 ANALISI QUICK SORT
      • 6.7.3 ALGORITMI RANDOMIZZATI
      • 6.7.4 PROPRIETA’
    • 6.8 COMPLESSITA’ DEL PROBLEMA DELL’ORDINAMENTO
      • 6.8.1 LOWER BOUND DELL’ORDINAMENTO PER CONFRONTI
    • 6.9 ORDINAMENTO IN TEMPO LINEARE
      • 6.9.1 COUNTING SORT
      • 6.9.2 BUCKET SORT
    1. HASHING
    • 7.1 STRUTTURA DATI DIZIONARIO
      • 7.1.1 TAVOLE ADINDIRIZZAMENTO DIRETTO
      • 7.1.2 TABELLE HASH
      • 7.1.3 CONCATENAMENTO / LISTE DI TRABOCCO
      • 7.1.4 INDIRIZZAMENTO APERTO
      • 7.1.5 CENNI DI ANALISI DELLA COMPLESSITA’
      • 7.1.6 CENNI ALLE FUNZIONI DI CODIFICA E REHASHING
    1. ALBERI BINARI DI RICERCA
    • 8.1 RICERCA IN UN ARRAY ORDINATO
    • 8.2 ALBERI BINARI DI RICERCA (abr)
      • 8.2.1 REALIZZAZIONE DI ABR
      • 8.2.2 OPERAZIONI SUGLI ABR
    • 8.3 RIASSUNTO DELLE OPERAZIONI
    • 8.4 ABR E ORDINAMENTO
    1. ALBERI ROSSO – NERI
    • 9.1 PROPRIETA’
    • 9.2 ALTEZZA DI UN ALBERO ROSSO – NERO
    • 9.3 OPERAZIONI SUGLI ALBERI ROSSO – NERI
      • 9.3.1 𝑰𝑵𝑺𝑬𝑹𝑰𝑴𝑬𝑵𝑻𝑶
      • 9.3.2 𝑪𝑨𝑵𝑪𝑬𝑳𝑳𝑨𝒁𝑰𝑶𝑵𝑬
    1. CODE DI PRIORITA’
    • 10.1 IMPLEMENTAZIONI DI CODE DI PRIORITA’
    • 10.2 HEAP BINARI
      • 10.2.1 HEAP BINARI CON ARRAY
      • 10.2.2 GENERALIZZAZIONE
    • 10.3 HEAP BINOMIALI
      • 10.3.1 ALBERI BINOMIALI
      • 10.3.2 HEAP BINOMIALI E PROPRIETA’
      • 10.3.3 RAPPRESENTAZIONE DI HEAP BINOMIALI
      • 10.3.4 OPERAZIONI CON GLI HEAP BINOMIALI
    • 10.4 HEAL DI FIBONACCI (CENNI)
    • 10.5 RIASSUNTO
    1. PROGRAMMAZIONE DINAMICA
    • 11.1 SOTTOPROBLEMI DISGIUNTI E NON DISGIUNTI
    • 11.2 IL PROBLEMA DEL TAGLIO DELLE ASTE
    • 11.3 𝑪𝑼𝑻 − 𝑹𝑶𝑫
    • 11.4 PROGRAMMAZIONE DINAMICA
      • 11.4.1 𝑴𝑬𝑴𝑶𝑰𝒁𝑨𝑻𝑰𝑶𝑵
      • 11.4.2 𝑩𝑶𝑻𝑻𝑶𝑴 − 𝑼𝑷...................................................................................................................................................
      • 11.4.3 RICOSTRUZIONE DELLA SOLUZIONE
    • 11.5 IL PROBLEMA DELLA MOLTIPLICAZIONE DI UNA SEQUENZA DI MATRICI
    • 11.6 ELEMENTI DELLA PROGRAMMAZIONE DINAMICA
      • 11.6.1 SOTTOSTRUTTURA OTTIMA
      • 11.6.2 RIPETIZIONE DEI SOTTOPROBLEMI
      • 11.6.3 ANNOTAZIONE
    • 11.7 LONGEST COMMON SUBSEQUENCE
    1. ALGORITMI GOLOSI
    • 12.1 PROBLEMA DELLA SELEZIONE DI ATTIVITA’
      • 12.1.1 PROGRAMMAZIONE DINAMICA
      • 12.1.2 ALGORITMO GOLOSO
    • 12.2 PROGETTAZIONE DI UN ALGORITMO GOLOSO
    • 12.3 PROBLEMA DELLO ZAINO (KNAPSACK PROBLEM)
    • 12.4 CODICI DI HUFFMAN
    1. GRAFI
    • 13.1 RAPPRESENTAZIONI DI GRAFI
    • 13.2 ALGORITMI DI VISTA DI GRAFI
      • 13.2.1 VISITA IN PROFONDITA DFS
      • 13.2.2 VISITA IN PROFONDITA’ BFS
      • 13.2.3 APPLICAZIONI DELLA DFS E DELLA BFS
      • 13.2.4 APPLICAZIONE DELLA DFS
    1. MINIMUM SPANNING TREE
    • 14.1 ALGORITMO GENERICO
    • 14.2 ALGORTIMO DI KRUSKAL
    • 14.3 ALGORITMO DI PRIM
    1. CAMMINI MINIMI
    • 15.1 ALBERO DI CAMMINI MINIMI
    • 15.2 RILASSAMENTO
    • 15.3 ALGORITMO DI BELLMAN E FORD
    • 15.4 ALGORITMO DI DIJKSTRA

1.3 MODELLI DI CALCOLO

Come detto, i passi che costituiscono un algoritmo devono essere eseguibili da un qualche agente di

calcolo in grado di eseguire ogni passo in un tempo finito

  • Un algoritmo viene quindi definito in riferimento ad un dato agente di calcolo o modello di

calcolo

Un modello di calcolo è un modello teorico di una macchina di calcolo.

Usare un modello di calcolo piuttosto che un altro ha impatto:

  • sull’ esistenza di algoritmi per la soluzione di un problema
  • sull’ efficienza di tali algoritmi

RANDOM ACCESS MACHINE

Nell’ambito dello studio degli algoritmi si fa di solito riferimento alla Random Access Machine

(RAM), ossia un modello astratto e semplificato dei calcolatori reali “equivalente” alla macchina di

Turing (con essa è possibile cioè calcolare tutto ciò che è possibile calcolare con la macchina di Turing)

In essa:

  • c’è un numero finito ma arbitrario di registri indirizzabili individualmente

o due di essi sono registri speciali: accumulatore (memorizza il risultato di calcoli) e

Instruction Counter IC (memorizza l’indirizzo della prossima istruzione)

  • ciascun indirizzo può contenere un intero grande a piacere

o non ci poniamo problemi sulla dimensione dei numeri che utilizziamo ( assunzione

forte! )

  • ci sono nasci di ingresso e uscita che sono sequenze di celle, ognuna delle quali può

memorizzare un intero grande a piacere

Un programma per RAM consiste in una sequenza di istruzioni di vario tipo:

  • Istruzioni aritmetiche (addizione, sottrazione, moltiplicazione, divisione, resto, floor e ceiling)
  • Istruzioni per spostare i dati (load, store, copy)
  • Istruzioni di controllo (salto condizionato e incondizionato, chiamata di subroutine e return)

La RAM è un modello astratto e semplificato dei calcolatori reali (le istruzioni della RAM assomigliano

alle tipiche istruzioni di un linguaggio assembler), tuttavia ci sono delle differenze :

  • Il programma non è in memoria ma " cablato " nella macchina (l’algoritmo è fisicamente parte

della macchina stessa, non richiede un caricamento separato in memoria per essere eseguito)

  • I registri e le celle dei nastri possono memorizzare dati di dimensione arbitraria

1.4 MODELLO DI COSTO

Per valutare l’efficienza di un programma per la RAM è necessario definire un modello di costo, ossia

definire quando costa in termine di “tempo” ogni istruzione della RAM.

Esistono due tipologie di modelli:

  1. Modello a costi uniformi : ogni istruzione ha costo costante indipendentemente dalla

dimensione degli operandi

  1. Modello a costi logaritmici : ogni istruzione ha costo che dipende dalla dimensione degli

operandi

a. il costo è proporzionale al logaritmo del valore degli operandi, cioè al numero di bit

necessari a rappresentarli

Il modello a costi uniformi è più semplice in quanto la valutazione dell’efficienza di un algoritmo si

riduce al conteggio delle operazioni previste dall’algoritmo, MA è più irrealistico (sommare due

numeri da 8 bit o due numeri da 109 bit ha lo stesso costo)

  • tuttavia vorremmo utilizzare questo modello perchè ci semplifica molto, dunque occorre fare

delle assunzioni

o assumendo che gli operandi abbiano una dimensione massima prefissata , il modello

di costo uniforme può essere ragionevolmente utilizzato

Noi faremo riferimento alla RAM con le seguenti assunzioni :

  • Le operazioni elementari della RAM sono: istruzioni aritmetiche, istruzioni per spostare i dati e

istruzioni di controllo

  • Ogni operazione elementare ha costo costante
  • Si assume che ci sia un limite alla dimensione di ogni dato primitivo

o In particolare per un input di dimensione 𝑛 si assume che i valori su cui si opera siano

rappresentabili con c ⋅ log 𝑛 bit con 𝑐 ≥ 1

Queste assunzioni saranno ragionevoli per certi tipi di algoritmi (studiati in questo corso), ma in altri

casi potrebbero essere assunzioni eccessivamente semplificative (come ad esempio nell’algoritmo RSA

per la crittografia)

1.5 PSEUDOCODICE

Per descrivere i nostri algoritmi faremo uso di uno pseudocodice, ossia un “linguaggio” che:

  • presenta dei costrutti strutturati come i linguaggi di programmazione (istruzioni condizionali,

cicli, ecc.)

  • ma permette di semplificare il codice ignorando una serie di dettagli non essenziali

vantaggi:

  • Sufficientemente strutturato per descrivere gli algoritmi in maniera non ambigua
  • Ignora i dettagli tecnici non rilevanti alla fine della descrizione dell’algoritmo
  • Indipendente da ogni specifico linguaggio di programmazione (un algoritmo in pseudocodice

può essere tradotto in qualsiasi linguaggio)

Convenzioni:

  • Indentazione : la struttura a blocchi dello pseudocodice viene indicata tramite l’indentazione.

Non ci sono quindi delimitatori di inizio e fine blocco

  • Le variabili dello pseudocodice hanno associato un tipo (intero, reale, booleano, …) ma non

necessitano di dichiarazione

o in caso di equivoco si ricorre ai commenti

  • Il simbolo di assegnazione è = (come in Java)

o Sono possibili assegnazioni multiple: i = j = k

  • Istruzione condizionale: if – else

o I blocchi nei due rami dell’istruzione vengono mostrati

tramite l’indentazione: l’else viene indentato allo stesso

livello del corrispondente if

▪ Non viene intrapresa nessuna azione per gestire l’errore

1.6 PROGRAMMI

Gli algoritmi vengono realizzati sotto forma di programmi

  • Un programma è dunque una realizzazione concreta di un algoritmo in uno specifico

linguaggio di programmazione

  • Possono esistere diverse realizzazioni di uno stesso algoritmo sotto forma di programma

1.7 STRUTTURE DATI

Il concetto di algoritmo e inscindibile da quello di dato.

  • Un algoritmo può essere visto come un manipolatore di dati : a fronte di dati in ingresso che

rappresentano l’istanza del problema, produce dati in uscita che sono la risposta al problema

per l’istanza data

È fondamentale dunque che i dati siano ben organizzati e strutturati in modo che il calcolatore li possa

elaborare efficientemente

  • L'efficienza di un algoritmo dipende in maniera critica dal modo in cui sono organizzati i dati su

cui l'algoritmo stesso deve operare

Una struttura dati può essere vista come un "contenitore" in cui è possibile memorizzare dati e che ci

offre delle operazioni per inserire, modificare, cancellare e recuperare i dati memorizzati

  • Le operazioni messe a disposizione dalla struttura dati dovrebbero essere il più possibile

efficienti

Le strutture dati possono essere classificate in due modi

Prima classificazione:

  1. Strutture dati statiche : la quantità di memoria di cui necessitano e determinabile a priori

(esempi: array, record)

  1. Strutture dati dinamiche : la quantità di memoria di cui esse necessitano varia a tempo

d’esecuzione e può essere diversa da esecuzione a esecuzione (esempi: liste, code, pile, alberi,

grafi)

Seconda classificazione:

A. Strutture dati astratte : definiscono la tipologia di dati contenuti nella struttura stessa e

l’insieme di operazioni che la struttura dati mette a disposizione per manipolarli

a. non specificano come le operazioni vengono realizzate

B. Strutture dati concrete : realizzazioni concrete delle strutture dati astratte

c. realizzano il comportamento descritto dalle strutture dati astratte tramite costrutti

“concreti” (array, record e puntatori, ecc.)

esempio:

Struttura dati astratta: collezione di n numeri con le seguenti operazioni:

  • read(i): legge l’i-esimo numero
  • size(): restituisce il numero di elementi nella collezione
  • modify(i, x): modifica l’elemento i-esimo rendendolo pari ad x

Struttura dati (concreta):

  • rappresentiamo la collezione con un array di n elementi A
  • read(i): restituisce A[i]
  • size(): restituisce n
  • modify(i, x): A[i]=x

2. RICHIAMI DI MATEMATICA

Al fine di stabilire le proprietà degli algoritmi, come la loro correttezza o la loro complessità si farà uso

di vari strumenti matematici. Di seguito analizzeremo le principali.

2.1 PROPRIETÀ DELLE POTENZE

0

1

− 1

1

𝑎

𝑚

𝑛

𝑚𝑛

𝑚

𝑛

𝑛

𝑚

𝑚

𝑛

𝑚+𝑛

2.2 PROPRIETÀ DEI LOGARITMI

Notazione:

  • log 𝑛 = log

2

  • ln 𝑛 = log

𝑒

  • log

𝑘

𝑘

Proprietà:

log 𝑏

𝑎

  • log

𝑐

𝑎𝑏 = log

𝑐

𝑎 + log

𝑐

  • log

𝑏

𝑛

= 𝑛 log

𝑏

  • log

𝑏

1

𝑎

= − log

𝑏

  • log

𝑏

log

𝑐

𝑎

log

𝑐

𝑏

log

𝑏

𝑐

log

𝑏

𝑎

2.3 FLOOR E CEILING

Dato un numero reale x, si chiama floor di x (o parte intera inferiore di x), denotato con ⌊x⌋, l’intero più

grande che è minore o uguale a x.

Dato un numero reale x, si chiama ceiling di x(o parte intera superiore di x),denotato con ⌈x⌉, l’intero

più piccolo che è maggiore o uguale a x.

Per un qualsiasi numero reale x si ha che

Per un qualsiasi numero intero n si ha che

Per x, y ∈ R, n ∈ Z e m ∈ N+ si ha che

  • Caso base: una lettera è un’espressione
  • Caso induttivo: se 𝛼 𝑒 𝛽 sono espressioni o allora lo sono anche (𝛼 + 𝛽), (𝛼 − 𝛽), (𝛼 × 𝛽) e

L’induzione può essere usata per dimostrare proprietà sui tipi di dato ricorsivi. Ad esempio:

In un’espressione il numero di parentesi aperte è uguale al numero di parentesi chiuse.

Dimostrazione.

  • Caso base: se l’espressione consiste di un numero o di una lettera non ci sono né parentesi

aperte né parentesi chiuse, la proprietà è vera.

  • Passo induttivo:

o Ipotesi induttiva: 𝛼 𝑒 𝛽 sono due espressioni in cui il numero di parentesi aperte è

uguale al numero di parentesi chiuse.

o Si dimostra che una combinazione di 𝛼 𝑒 𝛽 hanno un numero di parentesi aperte uguale

al numero di parentesi chiuse.

(𝛼 + 𝛽), (𝛼 − 𝛽), (𝛼 × 𝛽) e (𝛼/𝛽) hanno un numero di parentesi aperte uguale al

numero di parentesi chiuse.

2.7.2 CORRETTEZZA DEGLI ALGORITMI: INVARIANTE DI CICLO

Al fine di dimostrare la correttezza di algoritmi in cui compaiono cicli si fa uso dell’ invariante di ciclo ,

ovvero una proprietà che risulta essere vera prima di ogni iterazione del ciclo.

Dato che l’invariante di ciclo si mantiene vera ad ogni iterazione, risulterà vera anche quando il ciclo

termina. Scegliendo una proprietà opportuna si può dimostrare la correttezza dell’algoritmo.

Per dimostrare la validità di un’invariante di ciclo si usa l’induzione:

  • Si dimostra che la proprietà vale prima della prima iterazione.
  • Si dimostra che se la proprietà vale all’inizio di una certa iterazione vale anche al termine di

quella iterazione, cioè all’inizio della prossima iterazione.

Esempio 1: dimostrazione correttezza algoritmo

Dimostrare la validità del seguente algoritmo: ricerca del minimo in un array

Invariante di ciclo : all’inizio dell’iterazione i-esima la variabile 𝑚𝑖𝑛 contiene il minimo del sottoarray

𝐴[ 1... 𝑖 − 1 ]

Dimostrazione:

  • Caso base: per i = 2(prima della prima iterazione) si ha 𝑚𝑖𝑛 = 𝐴[ 1 ] che è il minimo del

sottoarray 𝐴[ 1.. 1 ].

  • Passo induttivo:

o Ipotesi induttiva: si assume che l’invariante sia vera per un certo i (inizio dell’iterazione

i-esima).

o Si dimostra che alla fine dell’iterazione i-esima (e inizio dell’iterazione (i + 1) - esima)

l’invariante di ciclo rimane vera

Durante l’iterazione i-esima si ha uno tra due casi:

▪ Caso 1. 𝐴

[

]

< 𝑚𝑖𝑛, allora 𝑚𝑖𝑛 viene posto a 𝐴

[

]

. E quindi l’invariante rimane

ancora vera dato che 𝑚𝑖𝑛 è il minimo di 𝐴

[

]

e 𝐴

[

]

è minore di 𝑚𝑖𝑛, il

che implica che 𝐴[𝑖] è il minimo del sottoarray 𝐴[ 1.. 𝑖]

▪ Caso 2. 𝐴[𝑖] ≥ 𝑚𝑖𝑛, allora 𝑚𝑖𝑛 non viene modificato. E quindi l’invariante

riamane vera dato che 𝑚𝑖𝑛 è il minimo di 𝐴[ 1.. 𝑖 − 1 ] e 𝐴[𝑖] è maggiore o

uguale a min, ovvero min è il minimo del sottoarray 𝐴[ 1... 𝑖]

Correttezza algoritmo: data l’invariante di ciclo risulta essere vero l’algoritmo.

Dimostrazione:

Quando il ciclo termina l’invariante è vera e 𝑖 = 𝐴. 𝑙𝑒𝑛𝑔𝑡ℎ + 1.

In base all’invariante di ciclo 𝑚𝑖𝑛 = 𝑚𝑖𝑛(𝐴[ 1.. 𝑖 − 1 ]) ovvero 𝑚𝑖𝑛 = 𝑚𝑖𝑛(𝐴[ 1.. 𝐴. 𝑙𝑒𝑛𝑔𝑡ℎ]), cioé è

pari al minimo dell’intero array.

  • vanno individuati i dati di input che devono avere dimensioni e configurazioni diverse • vanno

infine elaborati ed interpretati i risultati ottenuti

3.3 ANALISI DI COMPLESSITA’ DEGLI ALGORITMI

In definitiva vorremmo un procedimento per valutare la complessità di un metodo che:

  • superi i limiti evidenziati
  • possa essere applicato già a livello di algoritmo senza bisogno di implementare ed eseguire il

codice

L’ analisi di complessità degli algoritmi è un procedimento per stimare le risorse utilizzate da un

algoritmo per la sua esecuzione. Tale metodo ci permette di valutare se l’algoritmo è più o meno

efficiente

Occorre precisare che, poiché un algoritmo è qualcosa di astratto non ha molto senso parlare di utilizzo

di risorse di calcolo da parte di un algoritmo. Ciò che dobbiamo/vogliamo valutare è l’utilizzo di risorse

da parte di un programma che realizzi l’algoritmo.

  • Grazie all’utilizzo di un modello di calcolo astratto (macchina RAM) possiamo avere una stima

delle risorse utilizzate già a livello di algoritmo

  • ovviamente, le semplificazioni del modello RAM rispetto ad una macchina reale, introducono

una certa approssimazione nella valutazione fatta

Il tempo utilizzato dall’algoritmo durante la sua esecuzione di solito cresce al crescere della

dimensione dell’input. É pertanto necessario descrivere la sua complessità tramite una funzione, detta

funzione di costo , che indica il tempo impiegato per eseguire l’algoritmo in funzione della dimensione

dell’input.

  • Per semplificare l’analisi la funzione di costo viene indicata quasi sempre utilizzando la

notazione asintotica

Precisiamo meglio la dimensione dell’input e la funzione di costo

  1. Dimensione dell’input: quantità dei dati in input ad un algoritmo

a. Dipende dal tipo di problema (ad esempio numero 𝑛 di elementi da ordinare)

b. In alcuni casi è espressa tramite due (o più) parametri (ad esempio dimensione 𝑚 𝑥 𝑛

delle matrici)

  1. Funzione di costo: tempo impiegato per eseguire l’algoritmo in funzione della dimensione

dell’input. La indichiamo con 𝑇(𝑛) (o 𝑇(𝑛, 𝑚)), essendo 𝑛 la dimensione dell’input

Il tempo impiegato per un’esecuzione dipende da:

a. costo delle operazioni elementari

b. configurazione dell’input

Un algoritmo è una sequenza di passi, ciascuno corrispondente ad un’operazione elementare

eseguibile nel modello di calcolo che stiamo adottando (nel nostro caso un’operazione elementare del

modello RAM). Dunque, se stabiliamo un costo per ciascuna di tali istruzioni, il costo dell’algoritmo è

dato dalla somma dei costi delle istruzioni che compongono la sequenza di passi.

Esempio:

Affinché si possa assegnare un valore numerico a 𝑇(𝑛) bisogna assegnare un costo alle operazioni 𝑐 𝑖

Abbiamo visto che nel modello RAM esistono due modelli di costo per le operazioni elementari

  • Nel modello di costo logaritmico ogni operazione ha un costo proporzionale al numero di bit

dei suoi operandi e quindi proporzionale al logaritmo del valore degli operandi

  • Nel modello di costo costante ogni operazione ha un costo costante, cioè non dipendente dal

valore degli operandi

Abbiamo detto che, sebbene più irrealistico, il modello di costo uniforme può essere ragionevolmente

utilizzato per i nostri scopi

Con l’assunzione del modello a costi costanti tutti i coefficienti 𝑐

𝑖

sono costanti e potremmo dunque

pensare di definire il valore di ciascun parametro in base all’operazione corrispondente.

  • Ciò ci costringerebbe a definire in maniera dettagliata il modello di costo della macchina RAM e

complicherebbe l’analisi

  • Effettuiamo dunque una semplificazione : assumiamo che tutte le operazioni elementari

abbiano lo stesso costo e sia pari ad 1

o Sebbene questa sia una approssimazione forte, risulta accettabile in pratica poiché

comunque il costo delle operazioni elementari non dipende dalla dimensione dell’input

e tale semplificazione non cambia il tipo di funzione di costo (funzione lineare,

quadratica, logaritmica, ecc.) ma solo i coefficienti

La funzione di costo diviene quindi una funzione che, per ogni valore di input, ci dice quante operazioni

elementari vengono eseguite

3.3.1 PRESENZA DI INVOCAZIONI

Supponiamo ora che all’interno del nostro algoritmo venga invocato un altro algoritmo (si ha cioè

un’invocazione di una funzione/metodo). Come valutiamo il costo dell’invocazione?

  • Il costo dell’invocazione è pari al costo dell’esecuzione della funzione per un input di

dimensione pari a quella utilizzata nell’invocazione

o Nella realtà c’è un costo dovuto all’invocazione e al passaggio dei parametri, ma nel

modello RAM tale costo viene ignorato

Assumendo la probabilità che l’elemento sia presente pari a 𝑝, possiamo calcolare la complessità di

caso medio come:

Dunque

3.4 ANALISI ASINTOTICA

Anche con le semplificazioni introdotte, abbiamo visto che il calcolo preciso della funzione di costo

risulta essere piuttosto macchinoso.

  • Al fine di semplificare tale operazione si introduce un ulteriore livello di semplificazione: ci si

accontenta di ricavare l’ andamento asintotico della funzione di costo, cioè la velocità con cui

la funzione “va a infinito”.

o La velocità di crescita di un algoritmo ci da informazioni sulla velocità con la quale

degradano le sue prestazioni

  • Questa semplificazione introduce un ulteriore livello di approssimazione nella nostra analisi

Perché ci interessa il comportamento all’infinito della funzione? Principalmente per 3 motivi:

  • Per input di piccole dimensioni qualsiasi algoritmo impiegherà un tempo piuttosto basso, di

fatto trascurabile

  • Per input di grandi dimensioni la differenza nei tempi di esecuzione tra algoritmi più o meno

efficienti può essere sostanziale

  • Conoscere la velocità di crescita di un algoritmo ci dice con che velocità degradano le sue

prestazioni

3.4.1 NOTAZIONE ASINTOTICA

Le notazioni asintotiche sono un formalismo matematico che permette di esprimere la velocità di

crescita di una funzione di costo, e quindi di effettuare confronti fra diverse funzioni

  • Le notazioni asintotiche si applicano a funzioni 𝑓(𝑛) il cui dominio è l’insieme ℕ dei naturali e il

cui codominio è l’insieme ℝ dei reali

Vengono usate tre tipi di notazione:

1. Notazione O

Data una funzione 𝑔(𝑛), si denota con 𝑂(𝑔(𝑛)) l’insieme di funzioni

0

0

Se 𝑓(𝑛) ∈ 𝑂(𝑔(𝑛)) allora si dice che:

  • 𝑔(𝑛) è un limite asintotico superiore per 𝑓(𝑛)
  • 𝑓(𝑛) cresce al più come 𝑔(𝑛)

Esempio: 3 n

2

− 8n ∈ O(n

2

Dimostrazione: Si devono trovare 𝑐 ed 𝑛

0

tali che ∀𝑛 ≥ 𝑛

0

, 0 ≤ 𝑓(𝑛) ≤ 𝑐𝑔(𝑛), ovvero 0 ≤

2

2

. Ma 0 ≤ 3 𝑛

2

− 8 𝑛 è soddisfatta per ogni 𝑛 ≥ 3. Ci si

concentra ora su 3 𝑛

2

2

, che può essere riscritta come ( 3 − 𝑐)𝑛

2

8 𝑛 ≤ 0 e se si sceglie 𝑐 = 4 si ha −𝑛

2

− 8 𝑛 ≤ 0 che è vera per ogni 𝑛 ≥ 0.

  1. Notazione 𝛀

Data una funzione 𝑔(𝑛), si denota con Ω(𝑔(𝑛)) l’insieme di funzioni

0

0

Se 𝑓(𝑛) ∈ Ω(𝑔(𝑛)) allora si dice che:

  • 𝑔(𝑛) è un limite asintotico inferiore per 𝑓(𝑛)
  • 𝑓(𝑛) cresce almeno come 𝑔(𝑛) 3. Notazione 𝚯

Data una funzione 𝑔(𝑛), si denota con 𝛩(𝑔(𝑛)) l’insieme di funzioni

1

2

0

1

2

0

Se 𝑓(𝑛) ∈ 𝛩(𝑔(𝑛)) allora si dice che:

  • 𝑔(𝑛) è un limite asintotico stretto per 𝑓(𝑛)
  • 𝑓(𝑛) cresce come 𝑔(𝑛)

3.4.2 PROPRIETÀ DELLE NOTAZIONI ASINTOTICHE

Per le notazioni asintotiche valgono le seguenti proprietà:

  • Proprietà riflessiva

o 𝑓(𝑛) ∈ 𝑂(𝑓(𝑛))

o 𝑓(𝑛) ∈ Ω(𝑓(𝑛))

o 𝑓(𝑛) ∈ 𝛩(𝑓(𝑛))

  • Proprietà transitiva

o se 𝑓(𝑛) ∈ 𝑂(𝑔(𝑛)) e 𝑔(𝑛) ∈ 𝑂(ℎ(𝑛)) allora 𝑓(𝑛) ∈ 𝑂(ℎ(𝑛))

o se 𝑓(𝑛) ∈ Ω(𝑔(𝑛)) e 𝑔(𝑛) ∈ Ω(ℎ(𝑛)) allora 𝑓(𝑛) ∈ Ω(ℎ(𝑛))

o se 𝑓(𝑛) ∈ Θ(𝑔(𝑛)) e 𝑔(𝑛) ∈ Θ(ℎ(𝑛)) allora 𝑓(𝑛) ∈ Θ(ℎ(𝑛))