lezione 7 n.
Download
Skip this Video
Loading SlideShow in 5 Seconds..
Lezione 7 PowerPoint Presentation
Download Presentation
Lezione 7

Loading in 2 Seconds...

play fullscreen
1 / 83

Lezione 7 - PowerPoint PPT Presentation


  • 158 Views
  • Uploaded on

Lezione 7. I Tipi di Dato Astratto (Abstract Data Type). Sommario. Cosa sono le Strutture Dati Astratte? Le strutture dati Le operazioni Come scegliere fra varie implementazioni? Analisi degli algoritmi Le strutture dati elementari vettori liste. Cosa sono gli ADT.

loader
I am the owner, or an agent authorized to act on behalf of the owner, of the copyrighted work described.
capcha
Download Presentation

PowerPoint Slideshow about 'Lezione 7' - remy


An Image/Link below is provided (as is) to download presentation

Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author.While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server.


- - - - - - - - - - - - - - - - - - - - - - - - - - E N D - - - - - - - - - - - - - - - - - - - - - - - - - -
Presentation Transcript
lezione 7

Lezione 7

I Tipi di Dato Astratto

(Abstract Data Type)

sommario
Sommario
  • Cosa sono le Strutture Dati Astratte?
    • Le strutture dati
    • Le operazioni
  • Come scegliere fra varie implementazioni?
    • Analisi degli algoritmi
  • Le strutture dati elementari
    • vettori
    • liste
cosa sono gli adt
Cosa sono gli ADT
  • Cosa è un tipo di dato
  • Cosa è un tipo di dato astratto
  • Quali sono le operazioni definibili
quale la questione
Quale è la questione?
  • Come organizzare (strutturare) i dati perché sia possibile elaborarli agevolmente tramite algoritmi?
  • Importanza:
    • in alcune applicazioni la scelta della struttura dati è l’unica scelta importante
    • data una struttura dati l’implementazione di un algoritmo può risultare più efficiente
    • guadagno di tempo o di spazio
tipo di dato
Tipo di dato
  • Definizione:

Un tipo di dato è definito da un insieme di valori e da una collezione di operazioni su questi valori

  • Es: un tipo di dato è il tipo intero in cui l’insieme di valori è costituito dai numeri naturali e le operazioni dalla somma, sottrazione, moltiplicazione, divisione, etc.
verso l astrazione
Verso l’astrazione
  • Preoccupazione principale nello scrivere un programma:
    • applicazione alla più ampia varietà possibile di situazioni
    • riutilizzo del programma
    • astrazione dalle implementazioni per poter lavorare a livelli di complessità maggiore
l astrazione
L’astrazione
  • Si può lavorare a diversi livelli di astrazione:
    • bit: entità di informazione binaria (astrae dal supporto fisico (tecnologia elettronica) con cui è rappresentato)
    • modello di calcolatore (astrae dalla rappresentazione dell’informazione)
    • linguaggi di programmazione (si astrae dal linguaggio macchina e quindi dal modello di calcolatore)
    • algoritmi (si astrae dai linguaggi di programmazione)
    • ADT (si astrae dalle implementazioni algoritmiche)
utilit delle astrazioni
Utilità delle astrazioni
  • Lavorare a livelli alti di astrazione permette di lavorare in modo semplice su problemi complessi
  • si possono analizzare gli algoritmi indipendentemente dai linguaggi con i quali sono poi implementati
  • si possono realizzare programmi complessi tramite le strutture dati astratte indipendentemente dalla loro implementazione algoritmica
tipo di dato astratto
Tipo di dato astratto
  • Definizione:

Un ADT (Abstract Data Type) è un tipo di dato accessibile solo attraverso una interfaccia

  • Si chiama programma client il programma che usa ADT
  • si chiama implementazione il programma che specifica il tipo di dato (cioè i valori e le operazioni)
esempio
Esempio
  • Un ADT che rappresenti un punto bidimensionale mette a disposizione delle operazioni come ad es. l’assegnazione, il confronto, la somma
  • questo viene fatto senza rivelare i dettagli implementativi interni: l’interfaccia maschera l’implementazione
  • è possibile rappresentare un punto mediante due coordinate cartesiane x,y oppure mediante coordinate polari r,
  • si vuole poter cambiare la rappresentazione interna senza che il programma client debba essere modificato
propriet degli adt
Proprietà degli ADT
  • Gli ADT di interesse descrivono insiemi o collezioni di elementi (che a loro volta possono essere ADT)
  • Queste collezioni possono essere dinamiche, ovvero il numero di elementi può variare: si possono aggiungere o togliere elementi dalla collezione
  • Gli elementi hanno generalmente una struttura costituita da una chiave e (eventualmente) da altri dati satellite
  • La chiave ha in genere valori in un insieme totalmente ordinato(per cui vale la proprietà di tricomia cioè per ogni coppia di elementi a,b nell’insieme deve valere esattamente una delle seguenti relazioni: a=b, a<b, a>b)
operazioni per un adt
Operazioni per un ADT
  • inserimento di un nuovo elemento
  • cancellazione di uno specifico elemento
  • ricerca di un elemento avente una chiave specificata
  • minimo e massimo ovvero restituzione dell’elemento con chiave più piccola o più grande
  • successore e predecessore ovvero restituzione dell’elemento con la minore chiave maggiore di una data chiave (o la maggiore chiave minore)
  • selezione del k-esimo elemento più piccolo
  • ordinamento ovvero attraversamento della collezione in ordine di chiave
  • unione di due collezioni
quali adt vedremo
Quali ADT vedremo?
  • Vettori, Liste, Alberi, Grafi
  • Pile, Code e Code con priorità
  • Tabelle di simboli e alberi di ricerca
  • Ci interesseremo particolarmente delle operazioni di ordinamento e di ricerca
adt di prima categoria
ADT di Prima Categoria
  • Per una maggiore flessibilità è necessario garantire di poter utilizzare istanze degli ADT come parametri in ingresso o in uscita a funzioni, o averne istanze multiple (ad esempio un vettore di istanze)
  • Definizione:

Un tipo di dato di prima categoria è un tipo di dato del quale possono esistere istanze multiple e che possiamo assegnare a variabili che sono dichiarate in modo specifico per memorizzare queste istanze

e le implementazioni
E le implementazioni?
  • La differenza fra due implementazioni algoritmiche delle operazioni che permettono l’uso delle interfacce sta nell’efficienza
  • per poter caratterizzare l’efficienza si ricorre all’analisi degli algoritmi
  • l’analisi permette di stabilire quale algoritmo sia migliore in funzione delle caratteristiche dei dati su cui lavoriamo
    • es. l’algoritmo migliore che implementa l’operazione di ordinamento per collezioni di dati quasi ordinate è diverso da quello migliore per collezioni di dati ordinati casualmente
analisi
Analisi
  • L’oggetto del discorso
    • Algoritmi e pseudocodice
  • Cosa significa analizzare un algoritmo
  • Modello di calcolo
  • Analisi del caso peggiore e del caso medio
  • Ordini di grandezza
    • La notazione asintotica
  • La velocità di crescita delle funzioni
algoritmi
Algoritmi
  • Le operazioni su un ADT vengono implementate tramite algoritmi
  • durante l’analisi degli algoritmi conviene astrarsi dallo specifico linguaggio di programmazione
  • per fare questo si usa un linguaggio detto pseudocodice
  • nello pseudocodice si impiegano metodi espressivi più chiari e concisi che nei linguaggi di programmazione reali
  • nello pseudocodice si possono usare frasi in linguaggio naturale per sintetizzare procedure complesse ma non ambigue
convenzioni sullo pseudocodice
Convenzioni sullo pseudocodice
  • Adotteremo le stesse convenzioni utilizzate nel libro “Introduzione agli algoritmi” di T.H.Cormen, C.E.Leiserson, R.L.Rivest Jackson Libri,1999
  • Le indentazioni indicano la struttura dei blocchi
  • i costrutti iterativi while,repeat e for e quelli condizionali if, then, else hanno la stessa interpretazione dei linguaggi Pascal o C
  • il simbolo “” indica un commento
convenzioni sullo pseudocodice1
Convenzioni sullo pseudocodice
  • l’assegnamento si indica con il simbolo ‘’ come in i3
  • si indica l’accesso all’elemento di posizione i-esima di un array A tramite la notazione A[i]
  • si accede agli attributi o campi di un oggetto usando il nome del campo seguito dal nome dell’oggetto fra parentesi quadre come in length[A] per denotare la lunghezza del vettore A
  • nelle procedure o funzioni i parametri sono passati per valore (per copia)
esempio1
Esempio

INSERTION-SORT(A)

1 for j  2 to lenght[A]

2 do keyA[j]

3 si inserisce A[j] nella sequenza ordinata A[1..j-1]

4 i  j - 1

5 while i>0 e A[i]>key

6 do A[i+1] A[i]

7 i  i - 1

8 A[i+1]  key

spiegazione intuitiva
Spiegazione intuitiva
  • Supponiamo di avere i primi x elementi del vettore già ordinati
  • consideriamo l’elemento di posizione x+1 e chiamiamolo key
  • l’idea è di scorrere gli elementi già ordinati e più grandi di key e di trovare la posizione giusta di key
  • mentre si scorrono gli elementi si scambia di posizione l’elemento che stiamo confrontando con key
  • appena si trova un elemento più piccolo di key ci si ferma
cosa significa analizzare un algoritmo
Cosa significa analizzare un algoritmo
  • Analizzare un algoritmo significa determinare le risorse richieste per il completamento con successo dell’algoritmo stesso
  • le risorse di interesse possono essere quelle di memoria, di tempo, numero di porte di comunicazione, numero di porte logiche
  • noi saremo interessati principalmente alla risorsa di tempo computazionale
modello di calcolo
Modello di calcolo
  • Per poter indicare il tempo di calcolo è necessario specificare un modello (ancorché astratto) di calcolo
  • Noi faremo riferimento ad un modello di calcolo costituito da un mono processore con accesso casuale della memoria (Random Access Machine RAM)
  • in questo modello ogni istruzione è eseguita in successione (ovvero senza concorrenza)
  • ogni istruzione viene eseguita in tempo costante anche se in generale diverso da istruzione a istruzione
dimensione dell input
Dimensione dell’input
  • Per poter comparare l’efficienza di due algoritmi in modo generale si definisce una nozione di dimensione dell’input e si compara il tempo di calcolo dei due algoritmi in relazione ad esso
  • per un algoritmo di ordinamento è ragionevole aspettarsi che al crescere del numero di dati da ordinare cresca il tempo necessario per completare l’algoritmo
  • in questo caso la dimensione dell’input coincide con la numerosità dei dati in ingresso
dimensione dell input1
Dimensione dell’input
  • Nota: non sempre la dimensione dell’input coincide con il numero di elementi in ingresso
  • un algoritmo di moltiplicazione fra due numeri naturali ha come dimensione il numero di bit necessari per rappresentare la codifica binaria dei numeri
  • Nota: non sempre la dimensione dell’input è rappresentabile con una sola quantità
  • un algoritmo che opera su grafi ha come dimensione il numero di nodi e di archi del grafo
analisi del tempo computazionale
Analisi del tempo computazionale
  • Lo scopo dell’analisi del tempo computazionale è di dare una descrizione sintetica del tempo di calcolo dell’algoritmo al variare della dimensione dell’ingresso
  • inizieremo con un calcolo esatto del tempo
  • successivamente utilizzeremo un formalismo più sintetico e compatto che fa uso degli ordini di grandezza
esempio2
Esempio

Sia n  length[A]

N° Costo INSERTION-SORT(A)

n c1 1 for j  2 to lenght[A]

n-1 c2 2 do keyA[j]

n-1 0 3 si inserisce A[j] ...

n-1 c4 4 i  j - 1

j=2..n tj c5 5 while i>0 e A[i]>key

j=2..n (tj-1) c6 6 do A[i+1] A[i]

j=2..n (tj-1) c7 7 i  i - 1

n-1 c8 8 A[i+1]  key

Dove tj è il numero di volte che l’istruzione while è eseguita per un dato valore di j

Il tempo complessivo è dato da:

T(n)=c1.n + c2.(n-1)+c4.(n-1)+c5.(j=2..n tj)+c6.(j=2..n (tj-1))

+c7.(j=2..n (tj-1))+c8.(n-1)

caso migliore peggiore
Caso migliore/peggiore
  • Anche a parità di numerosità dei dati in ingresso il tempo di esecuzione può dipendere da qualche caratteristica complessiva sui dati, ad esempio da come sono ordinati inizialmente
  • si distinguono pertanto i casi migliore e peggiore a seconda che i dati abbiano (a parità di numerosità) le caratteristiche che rendono minimo o massimo il tempo di calcolo del dato algoritmo
  • nell’esempio dell’insertion sort
    • il caso migliore è che i dati siano già ordinati
    • il caso peggiore è che siano ordinati in senso inverso
analisi del caso migliore
Analisi del caso migliore
  • Per ogni j=2,3,…,n in 5) si ha che A[i]<key quando i ha il suo valore iniziale di j-1
  • quindi vale tj=1 per ogni j=2,3,…,n
  • il tempo di esecuzione diviene quindi:

T(n)=c1.n+c2(n-1)+c4.(n-1)+c5.(n-1)+c8.(n-1)

ovvero

T(n)=(c1+c2+c4+c5+c8).n -(c2+c4+c5+c8)

ovvero

T(n)=a.n+b

  • diciamo che T(n) è una funzione lineare di n
analisi del caso peggiore
Analisi del caso peggiore
  • Se l’array è ordinato in ordine decrescente allora si deve confrontare l’elemento key=A[j] con tutti gli elementi precedenti A[j-1], A[j-2],…,A[1]
  • in questo caso si ha che tj=j per j=2,3,4,…,n
  • si ha che:

j=2..n j = n(n+1)/2 -1

j=2..n (j-1) = n(n-1)/2

  • il tempo di esecuzione diviene quindi:

T(n)=c1.n+c2(n-1)+c4.(n-1) +c5.(n(n+1)/2 -1) +c6.(n(n-1)/2 ) +c7.(n(n-1)/2 )+c8.(n-1)

T(n)=(c5/2+c6/2+c7/2).n2+(c1+c2+c4+c5/2-c672-c7/2+c8).n-(c2+c4+c5+c8)

T(n)=a.n2+b.n+c

  • diciamo che T(n) è una funzione quadratica di n
analisi del caso medio
Analisi del caso medio
  • Se si assume che tutte le sequenze di una data numerosità siano equiprobabili allora mediamente per ogni elemento key=A[j] vi saranno metà elementi nei restanti A[1,..,j-1] che sono più piccoli e metà che sono più grandi
  • di conseguenza in media tj=j/2 per j=2,3,4,…,n
  • si computa T(n) come nel caso peggiore
  • il tempo di calcolo risulta di nuovo quadratico in n
quale caso analizzare
Quale caso analizzare?
  • Come è accaduto anche nel caso appena visto, spesso il caso medio è dello stesso ordine di grandezza del caso peggiore
  • inoltre la conoscenza delle prestazioni nel caso peggiore fornisce una limitazione superiore al tempo di calcolo, cioè siamo sicuri che mai per alcuna configurazione dell’ingresso l’algoritmo impiegherà più tempo
  • infine per alcune operazioni il caso peggiore si verifica abbastanza frequentemente (ad esempio il caso di ricerca con insuccesso)
  • pertanto si analizzerà spesso solo il caso peggiore
ordine di grandezza
Ordine di grandezza
  • Per facilitare l’analisi abbiamo fatto alcune astrazioni
  • si sono utilizzate delle costanti ciper rappresentare i costi ignoti delle istruzioni
  • si è osservato che questi costi forniscono più dettagli del necessario, infatti abbiamo ricavato che il tempo di calcolo è nel caso peggiore T(n)=a.n2+b.n+c ignorando così anche i costi astratti ci
  • si può fare una ulteriore astrazione considerando solo l’ordine di grandezza del tempo di esecuzione perché per input di grandi dimensioni è solo il termine principale che conta e dire che T(n)=(n)
un algoritmo tecnologia
Un algoritmo è tecnologia
  • Si consideri il seguente caso:
    • si abbia un personal computer capace di eseguire 106 operazioni al secondo ed un supercomputer 100 volte più veloce
    • si abbia un codice di insertion sort che una volta ottimizzato sia in grado di ordinare un vettore di n numeri con 2n2 operazioni
    • si abbia un altro algoritmo (mergesort) in grado di fare la stessa cosa con 50 n log n operazioni
    • si esegua l’insertion sort su un milione di numeri sul supercomputer e il mergsort sul personal computer
  • il risultato è che il supercomputer impiega 2(106)2/108= 5.56 ore
  • mentre il personal computer impiega 50 106 log 106 /106= 16.67 minuti
efficienza asintotica
Efficienza asintotica
  • L’ordine di grandezza del tempo di esecuzione di un algoritmo caratterizza in modo sintetico l’efficienza di un algoritmo e consente di confrontare fra loro algoritmi diversi per la soluzione del medesimo problema
  • quando si considerano input sufficientemente grandi si sta studiando l’efficienza asintotica dell’algoritmo
  • ciò che interessa è la crescita del tempo di esecuzione al tendere all’infinito della dimensione dell’input
  • in genere un algoritmo asintoticamente migliore di un altro lo è in tutti i casi (a parte input molto piccoli)
notazione asintotica
Notazione Asintotica
  • La notazione asintotica è un modo per indicare certi insiemi di funzioni caratterizzati da specifici comportamenti all’infinito
  • Questi insiemi sono indicati come

 O  o 

  • quando una funzione f(n) appartiene ad uno di questi insiemi lo si indica equivalentemente come
    • f(n)  (n2)
    • f(n) = (n2)
  • la seconda notazione è inusuale ma vedremo che ha dei vantaggi di uso
notazione g n
Notazione (g(n))
  • Con la notazione (g(n)) si indica l’insieme di funzioni f(n) che soddisfano la seguente condizione

(g(n))={f(n):  c1, c2, n0 tali che

 n n0

0  c1 g(n)  f(n)  c2 g(n) }

  • ovvero f(n) appartiene a (g(n)) se esistono due costanti c1, c2 tali che essa possa essere schiacciata fra c1 g(n) e c2 g(n) per n sufficientemente grandi
notazione g n1
Notazione (g(n))
  • Graficamente

c2 g(n)

f(n)

c1 g(n)

n0

notazione o g n
Notazione O(g(n))
  • Con la notazione O(g(n)) si indica l’insieme di funzioni f(n) che soddisfano la seguente condizione

O(g(n))={f(n):  c, n0 tali che

 n n0

0  f(n)  c g(n) }

  • ovvero f(n) appartiene a O(g(n)) se esiste una costante c tali che essa possa essere maggiorata da c g(n) per n sufficientemente grandi
notazione o g n1
Notazione O(g(n))
  • Graficamente

c g(n)

f(n)

n0

notazione g n2
Notazione (g(n))
  • Con la notazione (g(n)) si indica l’insieme di funzioni f(n) che soddisfano la seguente condizione

(g(n))={f(n):  c, n0 tali che

 n n0

0  c g(n)  f(n) }

  • ovvero f(n) appartiene a (g(n)) se esiste una costante c tali che essa sia sempre maggiore di c.g(n) per n sufficientemente grandi
notazione g n3
Notazione (g(n))
  • Graficamente

f(n)

c g(n)

n0

notazione o g n2
Notazione o(g(n))
  • Il limite asintotico superiore può essere stretto o no
  • 2 n2 = O(n2) è stretto
  • 2 n = O(n2) non è stretto
  • con la notazione o(g(n)) si indica un limite superiore non stretto
  • formalmente, con la notazione o(g(n)) si indica l’insieme di funzioni f(n) che soddisfano la seguente condizione

o(g(n))={f(n):  c>0  n0 tali che

 n n0

0  f(n)  c g(n) }

notazione o g n3
Notazione o(g(n))
  • La definizione di o() differisce da quella di O() per il fatto che la maggiorazione i o() vale per qualsiasi costante positiva mentre in O() vale per una qualche costante
  • L’idea intuitiva è che la f(n) diventa trascurabile rispetto alla g(n) all’infinito ovvero

limx f(n)/g(n)=0

notazione g n4
Notazione (g(n))
  • Analogamente nel caso di limite inferiore non stretto si definisce che con la notazione (g(n)) si indica l’insieme di funzioni f(n) che soddisfano la seguente condizione

(g(n))={f(n):  c>0  n0 tali che

 n n0

0  c g(n)  f(n)}

  • Qui l’idea intuitiva è che sia la g(n) a diventare trascurabile rispetto alla f(n) all’infinito ovvero

limx f(n)/g(n)=

tralasciare i termini di ordine pi basso
Tralasciare i termini di ordine più basso
  • Giustifichiamo perché è possibile tralasciare i termini di ordine più basso, ovvero perché possiamo scrivere 1/2 n2 - 3 n= (n2)
  • dalla definizione di (g(n)) si ha che si devono trovare delle costanti c1, c2 tali che 1/2 n2 - 3 n possa essere schiacciata fra c1 n2 e c2 n2 per n sufficientemente grandi, ovvero per n>n0

c1 n2  1/2 n2 - 3 n  c2 n2

c1 n2  1/2 n2 - 3 n è vera per n  7 e per c1  1/14

1/2 n2 - 3 n  c2 n2 è vera per n  1 e per c2  1/2

  • quindi per n0=7 c1 = 1/14 e c2 = 1/2 si è soddisfatta la tesi (altri valori sono possibili ma basta trovarne alcuni)
tralasciare i termini di ordine pi basso1
Tralasciare i termini di ordine più basso
  • Intuitivamente si possono tralasciare i termini di ordine più basso perché una qualsiasi frazione del termine più alto prima o poi sarà più grande di questi
  • quindi assegnando a c1 un valore più piccolo del coefficiente del termine più grande e a c2 un valore più grande dello stesso consente di soddisfare le disegualianze della definizione di (g(n))
  • il coefficiente del termine più grande può poi essere ignorato perché cambia solo i valori delle costanti
slide48
Nota
  • In sintesi si può sempre scrivere che

a n2 + b n + c = (n2)

  • ovvero

j=o..d ajnj= (nd)

  • inoltre dato che una costante è un polinomio di grado 0 si scrive:

c = (n0) = (1)

uso della notazione asintotica
Uso della notazione asintotica
  • Dato che il caso migliore costituisce un limite inferiore al tempo di calcolo, si usa la notazione (g(n)) per descrivere il comportamento del caso migliore
  • analogamente dato che il caso peggiore costituisce un limite superiore al tempo di calcolo, si usa la notazione O(g(n)) per descrivere il comportamento del caso peggiore
  • Per l’algoritmo di insertion sort abbiamo trovato che nel caso migliore si ha T(n)= (n) e nel caso peggiore T(n)=O(n2)
la notazione asintotica nelle equazioni
La notazione asintotica nelle equazioni
  • Seguendo la notazione n = O(n) possiamo pensare di scrivere anche espressioni del tipo
  • 2n2+3n+1= 2n2+O(n)
  • il significato di questa notazione è che con O(n) vogliamo indicare una anonima funzione che non ci interessa specificare (ci basta che sia limitata superiormente da n)
  • nel nostro caso questa funzione è proprio 3n+1 che è O(n)
  • tramite l’uso della notazione asintotica possiamo eliminare da una equazione dettagli inessenziali
la notazione asintotica nelle equazioni1
La notazione asintotica nelle equazioni
  • La notazione asintotica può anche apparire a sinistra di una equazione come in
  • 2n2+O(n)= O(n2)
  • il significato è che indipendentemente da come viene scelta la funzione anonima a sinistra è sempre possibile trovare una funzione anonima a destra che soddisfa l’equazione per ogni n
  • in questo modo possiamo scrivere:
  • 2n2+3n+1= 2n2+O(n)=O(n2)
le funzioni di interesse
Le funzioni di interesse
  • O(1) il tempo costante è caratteristico di istruzioni che sono eseguite una o al più poche volte.
  • O(log n) il tempo logaritmico è caratteristico di programmi che risolvono un problema di grosse dimensioni riducendone la dimensione di un fattore costante e risolvendo i singoli problemi più piccoli. quando il tempo di esecuzione è logaritmico il programma rallenta solo leggermente al crescere di n: se n raddoppia log n cresce di un fattore costante piccolo.
le funzioni di interesse1
Le funzioni di interesse
  • O(n) il tempo lineare è caratteristico di programmi che eseguono poche operazioni su ogni elemento dell’input. Se la dimensione dell’ingresso raddoppia, raddoppia anche il tempo di esecuzione.
  • O(n log n) il tempo n log n è caratteristico di programmi che risolvono un problema di grosse riducendoli in problemi più piccoli, risolvendo i singoli problemi più piccoli e ricombinando i risultati per ottenere la soluzione generale. Se n raddoppia nlog n diventa poco più del doppio.
le funzioni di interesse2
Le funzioni di interesse
  • O(n2) il tempo quadratico è caratteristico di programmi che elaborano l’input a coppie. Algoritmi con tempo quadratico si usano per risolvere problemi abbastanza piccoli. Se n raddoppia n2 quadruplica.
  • O(2n) il tempo esponenziale è caratteristico di programmi che elaborano l’input considerando tutte le possibili permutazioni. Rappresentano spesso la soluzione naturale più diretta e facile di un problema. Algoritmi con tempo esponenziale raramente sono applicabili a problemi pratici. Se l’input raddoppia il tempo di esecuzione viene elevato al quadrato
la conversione dei secondi
La conversione dei secondi
  • Secondi

102 1.7 minuti

104 2.8 ore

105 1.1 giorni

106 1.6 settimane

107 3.8 mesi

108 3.1 anni

109 3.1 decenni

1010 3.1 secoli

1011 mai

andamento dei tempi di calcolo1
Andamento dei tempi di calcolo

Tempo impiegato da un calcolatore capace di 10^6 operazioni al secondo

strutture dati elementari
Strutture dati elementari
  • Le strutture dati vettore e lista sono fra le strutture dati più usate e semplici
  • il loro scopo è quello di permettere l’accesso ai membri di una collezione generalmente omogenea di dati
  • per alcuni linguaggi di programmazione sono addirittura primitive del linguaggio (vettori in C/C++ e liste in LISP)
  • Sebbene sia possibile realizzare l’una tramite l’altra, i costi associati alle operazioni di inserzione e cancellazione variano notevolmente nelle diverse implementazioni
vettori
Vettori
  • Un vettore è una struttura dati che permette l’inserimento di dati e l’accesso a questi tramite un indice intero
  • generalmente la memorizzazione avviene in aree contigue di memoria
  • nella maggior parte degli elaboratori vi è una corrispondenza diretta con la memoria centrale (questo implica alta efficienza)
esempio di programma che usa vettori crivello di eratostene
Esempio di programma che usa vettoriCrivello di Eratostene

static const int N = 1000;

int main(){

int i, a[N];

//inizializzazione a 1 del vettore

for (i = 2; i < N; i++)

a[i] = 1;

for (i = 2; i < N; i++)

if (a[i]) //se numero primo elimina tutti multipli

for (int j = i; j*i < N; j++) a[i*j] = 0;

//stampa

for (i = 2; i < N; i++)

if (a[i]) cout << " " << i;

cout << endl;

}

crivello di eratostene
Crivello di Eratostene
  • Intuitivamente:
    • si prende un vettore di N elementi a 1
    • si parte dal secondo elemento e si cancellano (mettono a 0) tutti gli elementi di posizione multipla di 2
    • si considera l’elemento successivo che non sia stato cancellato
    • questo elemento non è divisibile per alcun numero precedente (altrimenti sarebbe stato messo a 0) e deve pertanto essere primo
    • si cancellano pertanto tutti i suoi multipli
liste
Liste
  • Una lista concatenata è un insieme di oggetti, dove ogni oggetto è inserito in un nodo che contiene anche un link (un riferimento) ad un (altro) nodo
  • si usa quando è necessario scandire un insieme di oggetti in modo sequenziale
  • è vantaggiosa quando sono previste frequenti operazioni di cancellazione o inserzioni
  • lo svantaggio sta nel fatto che si può accedere ad un elemento di posizione i solo dopo aver acceduto a tutti gli i-1 elementi precedenti
liste1
Liste
  • Di norma si pensa ad una lista come ad una struttura che implementa una disposizione sequenziale di oggetti
  • in linea di principio tuttavia l’ultimo nodo potrebbe linkare il primo ed avremo così una lista circolare
liste2
Liste
  • Una lista può essere:
    • concatenata semplice: un solo link
    • concatenata doppia (bidirezionale): due link
  • le liste bidirezionali hanno un link al nodo che le precede nella sequenza ed uno al nodo che le segue
  • con le liste concatenate semplici non è possibile risalire al nodo precedente ma si deve nuovamente scorrere tutta la sequenza
  • le liste concatenate doppie tuttavia occupano più spazio in memoria
convenzioni
Convenzioni
  • In una lista si ha sempre un nodo detto testa ed un modo convenzionale per indicare la fine della lista
  • La testa di una lista semplice non ha predecessori
  • I tre modi convenzionali di trattare il link del nodo dell’ultimo elemento sono:
    • link nullo
    • link a nodo fittizio o sentinella
    • link al primo nodo (lista circolare)
implementazione c
Implementazione C++
  • La struttura di un nodo di una lista si implementa in C++ attraverso l’uso dei puntatori

struct Node {

int key

Node * next;

};

struct Node {

int key

Node * next;

Node * prec;

};

esempio di lista problema di giuseppe flavio
Esempio di lista(Problema di Giuseppe Flavio)

struct node{

int item;

node* next;

node(int x, node* t){ item = x; next = t; }

};

typedef node * link;

int main(int argc, char * argv[]){

int i, N = atoi(argv[1]), M = atoi(argv[2]);

link t = new node(1, 0); t->next = t;

link x = t;

for (i = 2; i <= N; i++) //creazione della lista

x = (x->next = new node(i, t));

while (x != x->next){ //eliminazione

for (i = 1; i < M; i++) x = x->next; //spostamento

x->next = x->next->next;

}

cout << x->item << endl;//stampa l’ultimo elemento

}

spiegazione intuitiva1
Spiegazione intuitiva
  • Si parte da una lista circolare di N elementi
  • Si elimina l’elemento di posizione M dopo la testa
  • ci si muove a partire dall’elemento successivo di M posizioni e si elimina il nodo corrispondente
  • Vogliamo trovare l’ultimo nodo che rimane
operazioni definite sulla lista
Operazioni definite sulla lista
  • Per una lista si possono definire le operazioni di:
    • inserimento
    • cancellazione
    • ricerca
  • di seguito se ne danno le implementazioni in pseudocodice per una lista bidirezionale
inserimento
Inserimento

List-Insert(L,x)

1 next[x]head[L]

2 if head[L]  NIL

3 then prev[head[L]]x

4 head[L]x

5 prev[x]NIL

cancellazione
Cancellazione

List-Delete(L,x)

1 if prev[x]  NIL

2 then next[prev[x]]next[x]

3 else head[L]next[x]

4 if next[x]  NIL

5 then prev[next[x]]prev[x]

nota memory leakage
Nota: Memory leakage
  • Quando si cancella un nodo si deve porre attenzione alla sua effettiva deallocazione dallo heap
  • nel caso in cui si elimini un nodo solamente rendendolo inaccessibile non si libera effettivamente la memoria
  • se vi sono molte eliminazioni si può rischiare di esaurire la memoria disponibile
ricerca
Ricerca

List-Search(L,k)

1 xhead[L]

2 while x  NIL e key[x]  k

3 do x  next[x]

4 return x

la sentinella
La sentinella
  • Si può semplificare la gestione delle varie operazioni se si eliminano i casi limite relativi alla testa e alla coda
  • per fare questo si utilizza un elemento di appoggio detto NIL[L] che sostituisca tutti i riferimenti a NIL
  • tale elemento non ha informazioni significative nel campo key ed ha inizialmente i link next e prev che puntano a se stesso
implementazioni con sentinella
Implementazioni con sentinella

List-Delete(L,x)

1 next[prev[x]]next[x]

2 prev[next[x]]prev[x]

List-Insert(L,x)

1 next[x]next[nil[L]]

2 prev[next[nil[l]]]x

3 next[nil[L]]x

4 prev[x]nil[L]

List-Search(L,k)

1 xnext[nil[L]]

2 while x  nil[L] e key[x]  k

3 do x  next[x]

4 return x

rappresentazione grafica
Rappresentazione Grafica

9

16

4

1

nil[L]

25

9

16

4

1

nil[L]

inserzione

9

16

4

nil[L]

cancellazione

implementazione di lista con pi vettori
Implementazione di lista con più vettori
  • Si può rappresentare un insieme dei oggetti che abbiano gli stessi campi con un vettore per ogni campo
  • per realizzare una lista concatenata si possono pertanto utilizzare tre vettori: due per i link e uno per la chiave
  • un link adesso è solo l’indice della posizione del nodo puntato nell’insieme di vettori
  • per indicare un link nullo di solito si usa un intero come 0 o -1 che sicuramente non rappresenti un indice valido del vettore
esempio3
Esempio

head

7

1

2

3

4

5

6

7

8

next

3

/

2

5

4

1

16

9

key

prev

5

2

7

/

slide81
Nota
  • L’uso nello pseudocodice della notazione next[x] prev[x] e key[x] corrisponde proprio alla notazione utilizzata nella maggior parte dei linguaggi di programmazione per indicare l’implementazione vista
implementazione lista con singolo vettore
Implementazione lista con singolo vettore
  • La memoria di un calcolatore può essere vista come un unico grande array.
  • Un oggetto è generlamente memorizzato in un insieme contiguo di celle di memoria, ovvero i diversi campi dell’oggetto si trovano a diversi scostamenti dall’inizio dell’oggetto stesso
  • si può sfruttare questo meccanismo per implementare liste in ambienti che non supportano i puntatori:
    • il primo elemento contiene la key
    • il secondo elemento l’indice del next
    • il terzo elemento l’indice del prev
esempio4
Esempio

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

19

4

7

13

1

/

4

16

4

19

9

13

/

prev

next

key