66
Lez. 6 1 Universita' di Ferrara Facolta' di Scienze Matematiche, Fisiche e Naturali Laurea Specialistica in Informatica Algoritmi Avanzati Liste e Puntatori Copyright © 2006-2009 by Claudio Salati.

Lez. 61 Universita' di Ferrara Facolta' di Scienze Matematiche, Fisiche e Naturali Laurea Specialistica in Informatica Algoritmi Avanzati Liste e Puntatori

Embed Size (px)

Citation preview

Page 1: Lez. 61 Universita' di Ferrara Facolta' di Scienze Matematiche, Fisiche e Naturali Laurea Specialistica in Informatica Algoritmi Avanzati Liste e Puntatori

Lez. 6 1

Universita' di FerraraFacolta' di Scienze Matematiche, Fisiche e Naturali

Laurea Specialistica in Informatica

Algoritmi Avanzati

Liste e Puntatori

Copyright © 2006-2009 by Claudio Salati.

Page 2: Lez. 61 Universita' di Ferrara Facolta' di Scienze Matematiche, Fisiche e Naturali Laurea Specialistica in Informatica Algoritmi Avanzati Liste e Puntatori

2

LISTE

astrazione di dato che cattura l'idea di sequenza di lunghezza indefinita (e dinamica) di elementi

non necessariamente omogenei liste di liste (oltre che di elementi atomici) liste di elementi atomici eterogenei, eventualmente tipizzati

dinamicamente

sia di lunghezza indefinita, ma limitata a priori (da 0 a n), che di lunghezza massima teoricamente illimitata

possibilmente con modalita' di accesso ristretta, e.g. sequenziale (non diretto), o solo al primo e/o all'ultimo elemento

definibile ricorsivamente

lista (definizione ricorsiva/costruttiva) o e' una lista vuota o e' un elemento (atomico o lista) seguito da una lista

Page 3: Lez. 61 Universita' di Ferrara Facolta' di Scienze Matematiche, Fisiche e Naturali Laurea Specialistica in Informatica Algoritmi Avanzati Liste e Puntatori

3

LISTE

L’ordine degli elementi nella lista puo’ essere o no significativo, quindi una lista puo’ rappresentare: Un insieme (non ordinato) Una sequenza (ordinata), come un vettore

Sono le procedure d’accesso che stabiliscono cosa la lista rappresenta

Una proprieta' fondamentale delle liste e' che la struttura logica non e’ collegata a quella fisica (come e' invece per gli array) Questo rende essenziale in una lista la nozione di riferimento

esplicito da un elemento all’elemento successivo In una lista la relazione di contiguita' logica tra elementi e'

esplicita

N.B.: Un elemento puo’ appartenere contemporaneamente a diverse liste e.g. rappresentazione mediante liste di una matrice sparsa:

ogni elemento appartiene a due liste, una che rappresenta la riga cui l’elemento appartiene, l’altra che rappresenta la colonna

Page 4: Lez. 61 Universita' di Ferrara Facolta' di Scienze Matematiche, Fisiche e Naturali Laurea Specialistica in Informatica Algoritmi Avanzati Liste e Puntatori

4

LISTE: implementazione Rappresentazione "normale" (che utilizzeremo di solito qui)

in memoria centrale

utilizzando puntatori e memoria dinamica

ma esistono anche altre rappresentazioni:

array con elementi (omogenei) liberi o occupati e indici come puntatori

nell'array sono in effetti contenute (almeno) due liste, quella degli elementi liberi e quella/e degli elementi occupati

file (file system ad allocazione linkata, FAT del DOS, ...)

Diverse rappresentazioni possono essere piu’ o meno adatte a supportare

liste diversamente vincolate nei contenuti(e.g. una rappresentazione basata su array e' adatta solo per liste omogenee di elementi semplici)

diverse modalita’ (ristrette) di accesso alla lista

Page 5: Lez. 61 Universita' di Ferrara Facolta' di Scienze Matematiche, Fisiche e Naturali Laurea Specialistica in Informatica Algoritmi Avanzati Liste e Puntatori

5

LISTE: perche? Supponiamo di volere mantenere una sequenza ordinata di numeri, e di

volere inserire in questa sequenza un nuovo numero.

Supponiamo di rappresentare la sequenza tramite un vettore ordinato. Allora per inserire il nuovo numero devo:

1. Cercare la posizione corretta dove effettuare l’inserimento (il primo elemento, se gia’ esiste, che sia maggiore di quello che voglio inserire oppure la posizione successiva a quella dell’ultimo elemento significativo);

2. Spostare ordinatamente di una posizione tutti gli elementi maggiori di quello che voglio inserire (in ogni caso devo estendere il vettore);

3. Copiare nel “buco” cosi’ creato il nuovo elemento.

Complessita’ analoga ha la cancellazione di un elemento dalla sequenza.

Rappresentare una sequenza come una lista consente di: Evitare il passo 2 della procedura, dato che la contiguita’

logica non e’ associata alla contiguita’ fisica; Evitare di dovere necessariamente allocare a priori spazio di

memoria per il numero massimo di elementi ipotizzabile.

Page 6: Lez. 61 Universita' di Ferrara Facolta' di Scienze Matematiche, Fisiche e Naturali Laurea Specialistica in Informatica Algoritmi Avanzati Liste e Puntatori

6

LISTE

Possiamo considerare le liste da due punti di vista:

1. un tipo di dato astratto a se stante

2. una struttura dati (meglio, come vedremo nel seguito: una famiglia di strutture dati) utilizzabile per la rappresentazione concreta di altri tipi di dato astratto

normalmente ci limiteremo a considerare liste di elementi atomici omogenei

Page 7: Lez. 61 Universita' di Ferrara Facolta' di Scienze Matematiche, Fisiche e Naturali Laurea Specialistica in Informatica Algoritmi Avanzati Liste e Puntatori

7

Tipi di Dati

a livello macchina si hanno solo stringhe di bit che assumono ciascuno valore 0 o 1

siamo noi (e le istruzioni macchina che mettiamo nel programma) che decidiamo quale significato dare a ciascuna stringa di bit

nei linguaggi di programmazione di livello medio e alto (dal C o Pascal in su) tutti i dati, variabili e valori, appartengono ciascuno ad un tipo, sono cioe' descritti non in termini di configurazione di una stringa di bit, ma attraverso proprieta' astratte:

insieme (possibilmente strutturato) dei valori possibili

(e.g. range INT_MIN..INT_MAX per il tipo int del C)

possibilita' di denotare questi valori

(e.g. 138, 0x20, 077, INT_MAX per il tipo int del C)

insieme delle operazioni primitive possibili su ciascun tipo di dato, e semantica di ciascuna operazione.

Page 8: Lez. 61 Universita' di Ferrara Facolta' di Scienze Matematiche, Fisiche e Naturali Laurea Specialistica in Informatica Algoritmi Avanzati Liste e Puntatori

8

Tipi di Dati, esempio: tipo BOOLEAN

• insieme dei valori possibili: {true, false}

• denotazione di questi valori: TRUE, FALSE

• operazioni: { NOT, AND, OR, PRED, SUCC, =, , <, , >, , ORD } NOT: BOOLEAN BOOLEAN,

• totalmente definita NOT FALSE TRUE NOT TRUE FALSE

AND: BOOLEANBOOLEAN BOOLEAN, • totalmente definita FALSE AND FALSE FALSE FALSE AND TRUE FALSE TRUE AND FALSE FALSE TRUE AND TRUE TRUE

SUCC: BOOLEAN BOOLEAN, • parzialmente definita SUCC(FALSE) TRUE SUCC(TRUE) non definito

Page 9: Lez. 61 Universita' di Ferrara Facolta' di Scienze Matematiche, Fisiche e Naturali Laurea Specialistica in Informatica Algoritmi Avanzati Liste e Puntatori

9

ADT: astrazione vs. implementazione non ci interessa come e' rappresentato un valore BOOLEAN in

termini di stringa di bit (sizeof, bit-pattern per ciascun valore),

e ciascun linguaggio o ciascun sistema di programmazione lo puo' rappresentare come gli pare,

basta che il suo comportamento (behaviour) sia conforme alla definizione astratta: tipo di dato astratto o ADT

esempio: chi sa come sono rappresentati i double nel C Microsoft per Windows?

ovviamente un sistema di programmazione deve definire una rappresentazione concreta (stringa di bit, struttura dati) per ciascun valore di ciascun tipo; e.g.:

un byte con tutti i bit a 0 significa FALSE, un byte con almeno un bit a 1 significa TRUE

oppure un byte con tutti i bit a 0 significa FALSE, un byte con il solo bit

piu' leggero a 1 significa TRUE, e tutte le altre configurazioni di bit sono illegali

Page 10: Lez. 61 Universita' di Ferrara Facolta' di Scienze Matematiche, Fisiche e Naturali Laurea Specialistica in Informatica Algoritmi Avanzati Liste e Puntatori

10

ADT: citazione

Antonio Natali:

"una delle metodologie piu' incisive per la costruzione di sistemi SW corretti, modulari, documentabili, consiste nel mantenere quanto piu' possibile separate le parti del programma che accedono e manipolano la rappresentazione concreta in memoria dei dati (detti gestori), dalle parti (clienti) che trattano i dati come enti concettuali, ignorandone i dettagli interni e realizzativi."

Page 11: Lez. 61 Universita' di Ferrara Facolta' di Scienze Matematiche, Fisiche e Naturali Laurea Specialistica in Informatica Algoritmi Avanzati Liste e Puntatori

11

ADT: cliente e implementatore

tipo di dato astratto 2 punti di vista diversi

• gestore dell'ADT:

• vede la rappresentazione

• implementa l'astrazione (e.g. le operazioni primitive) basandosi sulla rappresentazione del tipo

• cliente dell'ADT:

• non vede la rappresentazione

• utilizza l'astrazione (e.g. le operazioni primitive) senza sapere come e' implementata

• puo' definire (implementare) nuove operazioni, ma solo operazioni non primitive,

• implementate a partire dalle operazioni gia' disponibili (e.g. le operazioni primitive)

• e che (ovviamente) non si basano sulla conoscenza della rappresentazione concreta dei valori (che e' ignota)

Page 12: Lez. 61 Universita' di Ferrara Facolta' di Scienze Matematiche, Fisiche e Naturali Laurea Specialistica in Informatica Algoritmi Avanzati Liste e Puntatori

12

LISTE: definizione dell'ADT

operazioni primitive su liste

null()

list BOOLEAN

data una lista L, restituisce TRUE se L e' vuota.

La lista vuota e' indicata come EMPTYLIST o () o <>

precondizioni: nessuna

cons()

element list list

dato un elemento E e una lista L, restituisce la lista (E L)

precondizioni: nessuna

N.B.: cons() e' un operatore funzionale come gli altri tre; parte da dei valori, e produce un valore: gli operandi non sono modificati

Page 13: Lez. 61 Universita' di Ferrara Facolta' di Scienze Matematiche, Fisiche e Naturali Laurea Specialistica in Informatica Algoritmi Avanzati Liste e Puntatori

13

LISTE: definizione dell'ADT

operazioni primitive su liste

car()(detta anche head())

list element

data una lista L = (E RestOfL) ne restituisce il primo elemento E

precondizioni: !null(L)

cdr()(detta anche tail())

list list

data una lista L = (E RestOfL) ne restitusce la sottolista RestOfL (eventualmente vuota) degli elementi successivi al primo

precondizioni: !null(L)

Page 14: Lez. 61 Universita' di Ferrara Facolta' di Scienze Matematiche, Fisiche e Naturali Laurea Specialistica in Informatica Algoritmi Avanzati Liste e Puntatori

14

LISTE: definizione dell'ADT

assiomi

vincoli che le operazioni devono soddisfare

descrizione della semantica delle operazioni

(insieme alle precondizioni)

null(EMPTYLIST)

!null(cons(e, l))

car(cons(e, l)) == e

cdr(cons(e, l)) == l

Page 15: Lez. 61 Universita' di Ferrara Facolta' di Scienze Matematiche, Fisiche e Naturali Laurea Specialistica in Informatica Algoritmi Avanzati Liste e Puntatori

15

LISTE: definizione dell'ADT

note

• N.B.: (E L) (E (L))

L non e' essa stessa un elemento di (E L), ma gli elementi di L costituiscono gli elementi di (E L) successivi ad E.

• Notare che le operazioni sono definite in modo indipendente dalla rappresentazione delle liste.

• Indipendentemente dalla rappresentazione che vorro' utilizzare per realizzare concretamente una lista, posso usare l'astrazione.

Page 16: Lez. 61 Universita' di Ferrara Facolta' di Scienze Matematiche, Fisiche e Naturali Laurea Specialistica in Informatica Algoritmi Avanzati Liste e Puntatori

16

ADT LISTA: esempi .1

int length (list l)

{

return (null(l) ? 0

: 1 + length(cdr(l)));

}

la dimostrazione di correttezza e' per induzione matematica sulla lunghezza di l (per esercizio)

Page 17: Lez. 61 Universita' di Ferrara Facolta' di Scienze Matematiche, Fisiche e Naturali Laurea Specialistica in Informatica Algoritmi Avanzati Liste e Puntatori

17

ADT LISTA: esempi .2

list append (list l1, list l2) {

// date due liste l1 e l2 ritorna una lista che e' la // concatenazione delle due.// N.B.: non una lista di due elementi, l1 ed l2, ma// una lista contenente tutti gli elementi di l1, // nell'ordine, seguiti da tutti gli elementi di l2, // nell'ordine// N.B.: l1 e l2 non vengono in alcun modo alterate

return (null(l1) ? l2 : cons(car(l1), append(cdr(l1), l2) ) );}

la dimostrazione di correttezza e' per induzione matematica sulla lunghezza di l1 (per esercizio, vedi esempio seguente)

Page 18: Lez. 61 Universita' di Ferrara Facolta' di Scienze Matematiche, Fisiche e Naturali Laurea Specialistica in Informatica Algoritmi Avanzati Liste e Puntatori

18

ADT LISTA: esempi .3

list reverse (list l) { 1

return (null(l) ? l 2

: append(reverse(cdr(l)), 3

cons(car(l), 4

EMPTYLIST) 5

) 6

); 7

} 8

la dimostrazione di correttezza e' per induzione matematica sulla lunghezza di l

se l=() la funzione ritorna l (cioe' ()), che e' ovviamente la lista inversa di se stessa (riga 2)

se l() la funzione ritorna (righe 3-6) una lista formata dall'inverso di cdr(l) (per ipotesi induttiva, applicabile perche' cdr(l) contiene un elemento meno di l, e cdr(l) esiste perche' !null(l)) seguita da car(l), cioe' proprio la lista inversa di l

Page 19: Lez. 61 Universita' di Ferrara Facolta' di Scienze Matematiche, Fisiche e Naturali Laurea Specialistica in Informatica Algoritmi Avanzati Liste e Puntatori

19

ADT LISTA: completezza

L'insieme delle operazioni primitive sopra indicato, unitamente all’operatore triadico del C (?:) e alla ricorsione (oltre che all’istruzione return) e' funzionalmente completo:

tutte le altre operazioni sulle liste possono essere realizzate utilizzando solo questo insieme di operazioni.

In realta’ si puo’ dimostrare (vedi Z. Manna, “Teoria matematica della computazione”) che questo sistema di programmazione e’ equivalente ad una Macchina di Turing, e che quindi ci consente di calcolare qualunque cosa sia calcolabile.

Page 20: Lez. 61 Universita' di Ferrara Facolta' di Scienze Matematiche, Fisiche e Naturali Laurea Specialistica in Informatica Algoritmi Avanzati Liste e Puntatori

20

ADT LISTA: Esercizio

Esercizio: scrivere, basandosi solo sulle funzioni primitive prima definite, una funzione C che concateni un elemento in coda alla lista anziche' in testa (come fa cons()).

Dimostrarne anche la correttezza.

Il suo prototipo deve essere:

list tailCons(element e, list l);

Quale e' la complessita' di questa operazione?

O(n)!

Scrivere anche le due funzioni:

list tailCdr(list l);

element tailCar(list l);

che operano anch’esse sulla coda della lista e dal significato ovvio

Page 21: Lez. 61 Universita' di Ferrara Facolta' di Scienze Matematiche, Fisiche e Naturali Laurea Specialistica in Informatica Algoritmi Avanzati Liste e Puntatori

21

Esempio di implementazione: lista di element 1

typedef . . . element;

struct listElement { element el; struct listElement *next; };typedef struct listElement *list;

// tipicamente:

#define EMPTYLIST NULL

poiche' secondo la definizione ricorsiva l'ultimo elemento della lista e' seguito dalla lista vuota, il valore del campo next dell'ultimo elemento della lista sara' NULL

Page 22: Lez. 61 Universita' di Ferrara Facolta' di Scienze Matematiche, Fisiche e Naturali Laurea Specialistica in Informatica Algoritmi Avanzati Liste e Puntatori

22

Esempio di implementazione: lista di element 2

boolean null(list l) {

return (l==NULL); // meglio l==EMPTYLIST

}

list cons(element e, list l) {

// costruisce un altro valore list senza // alterare il valore dell'operando l

list t = malloc(sizeof(listElem));

t->el = e;

t->next = l; // structure sharing: // gli elementi di l sono condivisi da l // e dal nuovo valore

return t;

}

Page 23: Lez. 61 Universita' di Ferrara Facolta' di Scienze Matematiche, Fisiche e Naturali Laurea Specialistica in Informatica Algoritmi Avanzati Liste e Puntatori

23

Esempio di implementazione: lista di element 3

element car(list l) {

// N.B. car non altera l

assert(!null(l));

return l->el;

}

list cdr(list l) {

// N.B. cdr non altera l

assert(!null(l));

return l->next;

}

Page 24: Lez. 61 Universita' di Ferrara Facolta' di Scienze Matematiche, Fisiche e Naturali Laurea Specialistica in Informatica Algoritmi Avanzati Liste e Puntatori

24

LISTE: implementazione .1

Mentre e’ vero che le operazioni primitive definite ci consentono di realizzare qualunque operazione su una lista, per quanto complessa,

non e’ detto che ci consentano di farlo in modo efficiente.

e.g. Inserire un elemento in testa alla lista ha costo costante, inserirlo in coda alla lista ha costo O(n) essendo n la dimensione della lista.

Quello che ci interessa da qui in poi non e’ tanto il punto di vista del cliente, ma quello dell’implementatore.

Non ci interessa utilizzare l'ADT lista, ma piuttosto implementare diversi ADT utilizzando una struttura dati "a lista" per la loro rappresentazione concreta.

ADT che ci possono interessare:

polinomi, vettori di lunghezza variabile, vettori e matrici sparse, insiemi, ...

Page 25: Lez. 61 Universita' di Ferrara Facolta' di Scienze Matematiche, Fisiche e Naturali Laurea Specialistica in Informatica Algoritmi Avanzati Liste e Puntatori

25

LISTE: implementazione .2

Diversi ADT offrono in generale operazioni molto diverse.

Saranno quindi molto diverse le modalita' con cui diversi ADT dovranno accedere alla struttura dati lista utilizzata per la loro rappresentazione concreta.

Quello che ci interessa qui non e’ il punto di vista del cliente dell'ADT lista, ma quello dell’implementatore di diversi generi di lista:

Come realizzare in modo efficiente una lista

una volta che siano note le modalita’ operative con cui voglio poterla manipolare.

E.g.:

Stack (pila) LIFO (Last-In-First-Out)

Coda FIFO (Fist-In-First-Out)

Potremo anche rimuovere il vincolo di operare in modo funzionale, cioe' producendo valori a partire da valori, senza modificare i valori in ingresso (in effetti e’ quello che faremo normalmente).

Page 26: Lez. 61 Universita' di Ferrara Facolta' di Scienze Matematiche, Fisiche e Naturali Laurea Specialistica in Informatica Algoritmi Avanzati Liste e Puntatori

26

LISTE: implementazione

Inserzione di un nuovo elemento nella lista:

Alloco un nodo inutilizzato

Gli assegno il valore dell’elemento da inserire

Lo inserisco nella lista nel posto desiderato

Per fare cio’ devo manipolare i riferimenti espliciti tra gli elementi della lista adiacenti a quello che voglio inserire

Rimozione di un elemento:

Estraggo l’elemento dalla lista

Per fare cio’ devo manipolare i riferimenti espliciti tra gli elementi della lista adiacenti a quello che voglio eliminare

Disalloco il nodo non piu’ utilizzato

Page 27: Lez. 61 Universita' di Ferrara Facolta' di Scienze Matematiche, Fisiche e Naturali Laurea Specialistica in Informatica Algoritmi Avanzati Liste e Puntatori

27

LISTE: implementazione Devo possedere una riserva di nodi non utilizzati

Devo avere dei meccanismi per acquisire nodi dalla riserva e per rilasciare nodi alla riserva

Devo avere procedure per:

Inizializzare una lista

Inserire un elemento nella lista (in quale posizione?)

Togliere un elemento dalla lista (da quale posizione?)

Scandire gli elementi della lista

Ogni nodo deve contenere almeno un campo riferimento che consenta di descrivere la strutturazione logica della lista

Devo avere la possibilita’ di indicare che il campo riferimento di un nodo non e’ significativo (non riferisce nulla, e.g. nel caso dell’ultimo elemento della lista)

Devo avere una testata che mi consenta di accedere alla lista (e.g. al suo “primo” elemento)

Page 28: Lez. 61 Universita' di Ferrara Facolta' di Scienze Matematiche, Fisiche e Naturali Laurea Specialistica in Informatica Algoritmi Avanzati Liste e Puntatori

28

Puntatori

una variabile o un valore che riferisce una variabile

esiste in C una funzione/operatore che si applica ad una variabile (meglio, ad una denotazione di variabile) e ne ritorna l'indirizzo (e' l'operatore unario &)

un puntatore puo' riferire variabili di qualunque classe di allocazione (statica, automatica, dinamica)

regole di compatibilita' di tipo? vedi relazioni tra tipi

in C e' definita la costante NULL di tipo void*, compatibile con tutti i tipi puntatore: un puntatore che ha valore NULL ha un valore ben definito, ma non riferisce alcuna variabile

– #define NULL ((void *) 0)

– definita nella libreria standard: sia in stdio.h che in stdlib.h

il tipo void* del C e' definito come compatibile con tutti i tipi puntatore

Page 29: Lez. 61 Universita' di Ferrara Facolta' di Scienze Matematiche, Fisiche e Naturali Laurea Specialistica in Informatica Algoritmi Avanzati Liste e Puntatori

29

Puntatori

operazioni su puntatori in C assegnamento confronto

uguaglianza disuguaglianza relazioni d'ordine (con certi limiti)

aritmetica dei puntatori somma con un intero sottrazione di un intero sottrazione di puntatori

indiciamento funzioni della libreria standard (stdlib)

void *malloc(size_t size); void free(void *p); calloc() e realloc()

dereferenziamento

Page 30: Lez. 61 Universita' di Ferrara Facolta' di Scienze Matematiche, Fisiche e Naturali Laurea Specialistica in Informatica Algoritmi Avanzati Liste e Puntatori

30

Allocazione e Inizializzazione di Variabili in C C++ prevede che ogni volta che viene creata una variabile, essa

venga anche inizializzata tramite l'invocazione dell'opportuno costruttore di tipo

pertanto tutte le variabili in C++ sono comunque inizializzate; un comportamento analogo si ha per il C con le variabili statiche

(che sono quindi sempre inizializzate), per le quali sono previsti un costruttore di assegnamento (o copia: copia la

rappresentazione del valore indicato), invocabile esplicitamente nel programma

un costruttore di default (che "azzera" la variabile) che viene utilizzato in assenza di utilizzo esplicito del costruttore di copia

per le variabili automatiche il costruttore (solo assegnamento) deve essere invocato esplicitamente (e non esiste il costruttore default)

per le variabili dinamiche non esiste la nozione di costruttore, per cui esse sono sempre non inizializzate

Page 31: Lez. 61 Universita' di Ferrara Facolta' di Scienze Matematiche, Fisiche e Naturali Laurea Specialistica in Informatica Algoritmi Avanzati Liste e Puntatori

31

Allocazione e Inizializzazione di Variabili in C

int *pi;

pi=malloc(sizeof(int));

*pi=5;

memoria statica

memoria dinamica o heap

codice

pi = NULL

pi = ?

pi = 5

Page 32: Lez. 61 Universita' di Ferrara Facolta' di Scienze Matematiche, Fisiche e Naturali Laurea Specialistica in Informatica Algoritmi Avanzati Liste e Puntatori

32

Allocazione e Inizializzazione di Variabili in C

int *pi;

pi=malloc(sizeof(int));

*pi=5;

memoria automatica

memoria dinamica o heap

codice

pi = ?

pi = ?

pi = 5

Page 33: Lez. 61 Universita' di Ferrara Facolta' di Scienze Matematiche, Fisiche e Naturali Laurea Specialistica in Informatica Algoritmi Avanzati Liste e Puntatori

33

Allocazione e Inizializzazione di Variabili in C

int *pi=NULL;

pi=malloc(sizeof(int));

*pi=5;

memoria automatica

memoria dinamica o heap

pi = NULL

pi = ?

pi = 5

codice

Page 34: Lez. 61 Universita' di Ferrara Facolta' di Scienze Matematiche, Fisiche e Naturali Laurea Specialistica in Informatica Algoritmi Avanzati Liste e Puntatori

34

Definizione di Puntatori in C

• non sono consentiti riferimenti (in avanti) a nomi typedef non ancora definiti

• sono consentiti (ma deprecati) riferimenti (in avanti) a nomi tag non ancora definiti

• e' infatti possibile pre-dichiarare parzialmente un nome tag, per poterlo riferire prima della definizionestruct nomeStruct;• (pre-) dichiara che nomeStruct e' il nome tag di una struct.

• da questo momento il nome tag nomeStruct e' riferibile.• la struttura e' ancora opaca: non si sa come e' fatta!

Se mi basta solo riferirla tramite puntatori non e' nemmeno necessario (ne' opportuno) completarne la definizione

• quando si definisce un tipo puntatore, il tipo puntato e' bene che sia gia' stato definito/dichiarato (salvo quanto detto prima).

• la definizione di tipi (strutture dati) ricorsivi, in particolare di record (struct) uno dei cui campi riferisce il record stesso, avviene utilizzando il nome tag del record

Page 35: Lez. 61 Universita' di Ferrara Facolta' di Scienze Matematiche, Fisiche e Naturali Laurea Specialistica in Informatica Algoritmi Avanzati Liste e Puntatori

35

Definizione di Strutture Dati Ricorsive in C

typedef struct structTag { ... struct structTag *next; // a questo punto il nome tag // della struct e' gia' definito } typedefName;

sfruttando riferimenti in avanti e' possibile anche la definizione di tipi mutuamente ricorsivi (vedi anche dichiarazioni parziali!)

struct s { ... struct t *tRef; // riferimento in avanti };

struct t { ... struct s *sRef; };

Page 36: Lez. 61 Universita' di Ferrara Facolta' di Scienze Matematiche, Fisiche e Naturali Laurea Specialistica in Informatica Algoritmi Avanzati Liste e Puntatori

36

Memoria dinamica e puntatori

il sistema di programmazione deve supportare la mancanza di una disciplina di dis/allocazione predefinita

evitando frammentazione

ricompattando blocchi di memoria al momento della loro liberazione

in modo efficiente

struttura dati detta heap: algoritmi ben noti (vedi lezione 7).

una variabile dinamica puo' essere creata in una procedura e sopravviverle

l'indirizzo delle variabili dinamiche e' definito solo a tempo di esecuzione

le variabili dinamiche sono visibili se c'e' un puntatore statico o automatico visibile che, direttamente o indirettamente, le riferisce

Page 37: Lez. 61 Universita' di Ferrara Facolta' di Scienze Matematiche, Fisiche e Naturali Laurea Specialistica in Informatica Algoritmi Avanzati Liste e Puntatori

37

Memoria dinamica e puntatori problemi

garbage

variabili dinamiche allocate, non piu' visibili nel programma, e quindi non rilasciabili

... p = malloc(); p = NULL; ...

dangling reference

puntatore che riferisce una variabile dinamica che e' gia' stata disallocata

... p = malloc(); q = p; free(p); ...

N.B.: dato che in C il passaggio dei parametri e' per valore, dopo la chiamata free(p), p e' necessariamente un dangling reference!

Page 38: Lez. 61 Universita' di Ferrara Facolta' di Scienze Matematiche, Fisiche e Naturali Laurea Specialistica in Informatica Algoritmi Avanzati Liste e Puntatori

38

Stack LIFO

La disciplina di inserzione/cancellazione degli elementi e’ LIFO (Last In - First Out): l’ultimo elemento inserito e’ il primo elemento estratto

E’ sufficiente:

Un riferimento tra gli elementi della lista

Un marker per indicare che un riferimento e’ non significativo (e.g. per l’ultimo elemento della lista)

Una testata per accedere alla lista (alla sua testa)

Le operazioni di inserimento e cancellazione (entrambe in testa!) hanno costo costante (O(1))

el.n el.2 el.1 •••testata

Page 39: Lez. 61 Universita' di Ferrara Facolta' di Scienze Matematiche, Fisiche e Naturali Laurea Specialistica in Informatica Algoritmi Avanzati Liste e Puntatori

39

Stack LIFO: definizione e implementazione

struct node {

element value;

struct node *next; };

typedef struct node *stack;

void stackInit(stack *s) {

// P = {}

*s = NULL;

}

boolean stackEmpty(stack s) {

// P = { s e' gia stato inizializzato }

return(s == NULL);

}

Page 40: Lez. 61 Universita' di Ferrara Facolta' di Scienze Matematiche, Fisiche e Naturali Laurea Specialistica in Informatica Algoritmi Avanzati Liste e Puntatori

40

Stack LIFO: definizione e implementazione

void stackPush(stack *s, element v) { // P = { s e' gia stato inizializzato } struct node *t = malloc(sizeof(struct node)); t->value = v; t->next = *s; *s = t;}

element stackPop(stack *s) { // P = { s e' gia stato inizializzato; // s e' non vuoto } assert(*s!=NULL); struct node *t = *s; element v = t->value; *s = t->next; free(t); return v;}

Page 41: Lez. 61 Universita' di Ferrara Facolta' di Scienze Matematiche, Fisiche e Naturali Laurea Specialistica in Informatica Algoritmi Avanzati Liste e Puntatori

41

Coda FIFO : doppio puntatore di testata

La disciplina di inserzione/cancellazione degli elementi e’ FIFO (First In - First Out): il primo elemento inserito e’ il primo elemento estratto

Inserzione ed estrazione avvengono agli estremi opposti della lista L' operazione di cancellazione in testa ha costo costante (O(1))

(anche quella di inserimento in testa, ma non ci interessa!) L'operazione di inserimento in fondo ha costo costante (O(1))

Con un solo puntatore di testata una delle due operazioni (l’inserimento in fondo) avrebbe un costo O(n) (dovrei scandire tutta la lista)

Con due puntatori di testata, uno al primo e l'altro all'ultimo elemento della lista, si riconducono entrambe le operazioni a complessita' costante

el.1 el.2 el.n •••

Testata

- first

- last

Page 42: Lez. 61 Universita' di Ferrara Facolta' di Scienze Matematiche, Fisiche e Naturali Laurea Specialistica in Informatica Algoritmi Avanzati Liste e Puntatori

42

Coda FIFO : doppio puntatore di testata Esaminando RadixSort abbiamo visto che c’e’ un’altra operazione su liste che

sarebbe utile poter eseguire in tempo costante: la concatenazione di 2 liste. L'operazione di concatenazione porta alla scomparsa di entrambe le liste

iniziali e alla creazione di una nuova lista che e’ l’unione delle due. Gli elementi delle liste iniziali sono riutilizzati per la costruzione della lista

finale. L' operazione di concatenazione e’ ordinata: nella lista risultato gli elementi

di una lista seguono, in modo ordinato, gli elementi dell’altra.

Quanto costa concatenare tra loro due liste fondendole in una singola? Se la lista e’ uno stack LIFO O(n) Se la lista e’ una coda FIFO con doppio puntatore di testata O(1)

L’implementazione dell’operazione di concatenazione e’ lasciata come esercizio

Un’altra operazione interessante e’ l'eliminazione di un elemento qualunque della lista (possedendone il riferimento): questa operazione e’ richiesta ad esempio dall’algoritmo dei boundary tag.

Quanto costa? Se la lista e’ una coda FIFO con doppio puntatore di testata O(n) Devo scandire tutta la lista fino ad identificare l’elemento precedente quello

che voglio eliminare.

Page 43: Lez. 61 Universita' di Ferrara Facolta' di Scienze Matematiche, Fisiche e Naturali Laurea Specialistica in Informatica Algoritmi Avanzati Liste e Puntatori

43

Coda FIFO: definizione e implementazione

struct node { element value; struct node *next; };

struct queue { struct node *first; struct node *last; };

typedef struct queue queue;

void queueInit(queue *q) { q->first = q->last = NULL;}

boolean queueEmpty(queue *q) { return(q->first == NULL);}

Page 44: Lez. 61 Universita' di Ferrara Facolta' di Scienze Matematiche, Fisiche e Naturali Laurea Specialistica in Informatica Algoritmi Avanzati Liste e Puntatori

44

Coda FIFO: definizione e implementazione

void queueInsert(queue *q, element v) {

if (q->first == NULL) {

// caso lista vuota

q->first = malloc(sizeof(struct node));

q->last = q->first;

} else {

q->last->next = malloc(sizeof(struct node));

q->last = q->last->next;

}

q->last->value = v;

q->last->next = NULL;

}

Page 45: Lez. 61 Universita' di Ferrara Facolta' di Scienze Matematiche, Fisiche e Naturali Laurea Specialistica in Informatica Algoritmi Avanzati Liste e Puntatori

45

Coda FIFO: definizione e implementazione

element queueRemove(queue *q) {

assert(q->first!=NULL);

struct node *t = q->first;

element v = t->value;

q->first = t->next;

if (q->first == NULL)

// caso lista vuota

q->last = NULL;

// end if

free(t);

return v;

}

Page 46: Lez. 61 Universita' di Ferrara Facolta' di Scienze Matematiche, Fisiche e Naturali Laurea Specialistica in Informatica Algoritmi Avanzati Liste e Puntatori

46

Complessita' delle operazioni

Perche' tanta parte del codice scritto e' dedicato a trattare il caso particolare di lista vuota?

Si puo' migliorare questa situazione? Complessita’ temporale delle operazioni Casi particolari

operazione

lista semplicemente linkata con testata con doppio puntatore

lista semplicemente linkata con testata con singolo puntatore

Inserzione di un elemento in testa O(1) O(1)

Inserzione di un elemento in coda O(1) O(n)

Rimozione di un elemento in testa O(1) O(1)

Rimozione di un elemento in coda O(n) O(n)

Concatenazione di liste O(1) O(n)

Rimozione di un elemento dato qualunque O(n) O(n)

Page 47: Lez. 61 Universita' di Ferrara Facolta' di Scienze Matematiche, Fisiche e Naturali Laurea Specialistica in Informatica Algoritmi Avanzati Liste e Puntatori

47

Coda FIFO : lista circolare Immaginiamo che l'ultimo elemento della lista, anziche' avere un

riferimento marcato come non significativo, riferisca il primo elemento della lista

Si possono liberare tutti i nodi di una lista in tempo O(1) inserendoli in una lista dei liberi

Ma: la rimozione del primo elemento implica l'aggiornamento del riferimento nell'ultimo elemento ( scandire l'intera lista): O(n)

Analogamente le altre operazioni, e.g. l'inserzione di un elemento in fondo alla coda

el.1 el.2 el.n•••testata

el.1 el.2 el.n•••testata

el.1 el.2 el.n •••liberi

Page 48: Lez. 61 Universita' di Ferrara Facolta' di Scienze Matematiche, Fisiche e Naturali Laurea Specialistica in Informatica Algoritmi Avanzati Liste e Puntatori

48

Coda FIFO : lista circolare con testata che riferisce l'ultimo elemento della lista

Immaginiamo una lista circolare in cui la testata riferisca l'ultimo elemento anziche' il primo:

Si puo' inserire in tempo costante un elemento sia in testa che in coda alla lista

Si puo' cancellare in tempo costante l'elemento in testa alla lista

Si possono liberare tutti i nodi di una lista in tempo costante inserendoli in una lista dei liberi

La cancellazione dell'elemento in coda alla lista ha costo O(n)

L'eliminazione di un elemento qualunque della lista (possedendone il riferimento) ha tempo O(n)

el.1 el.2 el.n•••testata

Page 49: Lez. 61 Universita' di Ferrara Facolta' di Scienze Matematiche, Fisiche e Naturali Laurea Specialistica in Informatica Algoritmi Avanzati Liste e Puntatori

49

Coda FIFO : lista circolare con testata che riferisce l'ultimo elemento della lista

Esercizio: scrivere in C le procedure per:

L'inserimento di un elemento in testa alla lista

L'inserimento di un elemento in coda alla lista

La cancellazione di un elemento in testa alla lista

La liberazione di tutti i nodi di una lista inserendoli in una lista dei liberi

L'eliminazione di un elemento qualunque della lista (possedendone il riferimento, che e' un parametro di ingresso alla procedura)

Page 50: Lez. 61 Universita' di Ferrara Facolta' di Scienze Matematiche, Fisiche e Naturali Laurea Specialistica in Informatica Algoritmi Avanzati Liste e Puntatori

50

Eliminazione dei casi particolari - 1 Consideriamo di avere una lista ordinata di interi rappresentata

come in figura:

dove el.1 el.2 … el.n

e di volere scrivere una procedura per l'inserzione ordinata di un nuovo elemento:

struct node { int value; struct node *next; };

typedef struct node *sortedIntList;

el.1 el.2 el.n •••testata

Page 51: Lez. 61 Universita' di Ferrara Facolta' di Scienze Matematiche, Fisiche e Naturali Laurea Specialistica in Informatica Algoritmi Avanzati Liste e Puntatori

51

Eliminazione dei casi particolari - 2struct node *ordInsert(sortedIntList *l, int newEl) { if (*l == NULL) { // caso 1: lista vuota *l = malloc(sizeof(struct node)); (*l)->value = newEl; (*l)->next = NULL; return (*l); } else if (newEl <= (*l)->value) { // caso 2: inserisco elemento minimo, al primo posto struct node *t = *l; *l = malloc(sizeof(struct node)); (*l)->value = newEl; (*l)->next = t; return (*l); } else { // caso 3 : inserisco elemento non minimo // in lista non vuota

// continua alla prossima pagina

Page 52: Lez. 61 Universita' di Ferrara Facolta' di Scienze Matematiche, Fisiche e Naturali Laurea Specialistica in Informatica Algoritmi Avanzati Liste e Puntatori

52

Eliminazione dei casi particolari - 3

struct node *s = *l;

// individua il nodo precedente il nuovo

// elemento

while (s->next != NULL &&

s->next->value < newEl) {

s = s->next;

}

struct node *t = malloc(sizeof(struct node));

t->value = newEl;

t->next = s->next;

s->next = t;

return (t);

}

}

Page 53: Lez. 61 Universita' di Ferrara Facolta' di Scienze Matematiche, Fisiche e Naturali Laurea Specialistica in Informatica Algoritmi Avanzati Liste e Puntatori

53

Eliminazione dei casi particolari - 4

I casi 1 e 2 rappresentano casi particolari,

a fronte del caso 3 che e' il caso generale.

(N.B.: in realta' la distinzione tra i casi 1 e 2 e' stata costruita ad arte a scopo didattico)

Si potrebbero riassorbire i casi particolari in quello generale, semplificando cosi' la procedura?

Si', basta inserire un nodo dummy in testa alla lista!

Il valore del nodo dummy e' ovviamente irrilevante.

Il nodo dummy e' sempre presente nella lista, anche quando questa e' vuota.

dummy el.1 el.n •••testata

Page 54: Lez. 61 Universita' di Ferrara Facolta' di Scienze Matematiche, Fisiche e Naturali Laurea Specialistica in Informatica Algoritmi Avanzati Liste e Puntatori

54

Eliminazione dei casi particolari - 5

struct node { int value; struct node *next; };

typedef struct node *sortedIntList;

void listInit(sortedIntList *l) { *l = malloc(sizeof(struct node)); (*l)->value = 0; // un valore dummy (*l)->next = NULL;}

boolean listEmpty(sortedIntList l) { return(l->next == NULL);}

Page 55: Lez. 61 Universita' di Ferrara Facolta' di Scienze Matematiche, Fisiche e Naturali Laurea Specialistica in Informatica Algoritmi Avanzati Liste e Puntatori

55

Eliminazione dei casi particolari - 6

struct node *ordInsert(sortedIntList l, int newEl) { // N.B.: l puo' essere passata per valore perche' // non c'e' mai necessita' di modificare la // testata struct node *s = l; // individua il nodo precedente il nuovo elemento while (s->next != NULL && s->next->value < newEl) { s = s->next; } struct node *t = malloc(sizeof(struct node)); t->value = newEl; t->next = s->next; s->next = t; return (l);}

Page 56: Lez. 61 Universita' di Ferrara Facolta' di Scienze Matematiche, Fisiche e Naturali Laurea Specialistica in Informatica Algoritmi Avanzati Liste e Puntatori

56

Lista Doppiamente Linkata

Le liste i cui elementi sono collegati da un unico riferimento (link) hanno diversi problemi. Dato un elemento (il riferimento ad un nodo della lista)

la lista puo' essere scandita solo in una direzione

(e’ vero? Vedi esercizi!)

non si puo' conoscere l'elemento precedente quello dato (e.g. per rimuovere l'elemento dato) se non scandendo tutta la lista

quindi l'eliminazione di un elemento qualunque della lista (pur possedendone il riferimento) ha complessita' lineare (O(n))

Questi problemi sono risolti se

dotiamo ogni elemento della lista di due riferimenti, uno in avanti e l'altro all'indietro,

cosi' che la lista possa essere scandita in entrambe le direzioni.

In questo modo inserzione e rimozione di un elemento in un punto dato della lista (in particolare all'inizio e alla fine) hanno sempre complessita' costante (O(1))

Page 57: Lez. 61 Universita' di Ferrara Facolta' di Scienze Matematiche, Fisiche e Naturali Laurea Specialistica in Informatica Algoritmi Avanzati Liste e Puntatori

57

Lista Doppiamente Linkata Struttura di una lista

circolare, doppiamente linkata, con nodo dummy di testata.

dummy rLinklLink

el.1 rLinklLink

el.2 rLinklLink

el.n rLinklLink

testata

Page 58: Lez. 61 Universita' di Ferrara Facolta' di Scienze Matematiche, Fisiche e Naturali Laurea Specialistica in Informatica Algoritmi Avanzati Liste e Puntatori

58

Lista circolare doppiamente linkata con nodo dummy di testata

Esercizio: scrivere in C le procedure per:

L'inserimento di un elemento in testa e in coda alla lista

La cancellazione di un elemento in testa e in coda alla lista

L'eliminazione di un elemento qualunque della lista (possedendone il riferimento, che e' un parametro di ingresso alla procedura)

Page 59: Lez. 61 Universita' di Ferrara Facolta' di Scienze Matematiche, Fisiche e Naturali Laurea Specialistica in Informatica Algoritmi Avanzati Liste e Puntatori

59

Liste e Insiemi

Esercizio:

Una lista puo' essere utilizzata per rappresentare un insieme.

Operazioni tipiche su insiemi sono:

intersezione

unione

inserimento di un elemento in un insieme

rimozione di un elemento (di cui si indica il riferimento) da un insieme.

Definire una rappresentazione tramite lista per insiemi di numeri interi e scrivere delle funzioni C che realizzino queste operazioni.

Quale e' la complessita' di queste funzioni?

Come cambiano le cose a seconda del fatto che le liste siano mantenute ordinate o meno?

Page 60: Lez. 61 Universita' di Ferrara Facolta' di Scienze Matematiche, Fisiche e Naturali Laurea Specialistica in Informatica Algoritmi Avanzati Liste e Puntatori

60

Insiemi: altra rappresentazione concreta

Esercizio:

Immaginiamo che l'universo su cui costruiamo i nostri insiemi sia finito e in effetti abbastanza limitato, e.g.

U = { k N | 0 k sizeof(unsigned long long)*8-1 }

Definire una rappresentazione che non si basi su liste per insiemi di numeri appartenenti a U e scrivere due funzioni C che realizzino le operazioni di intersezione e unione.

Definire anche le operazioni di inserimento e eliminazione di un elemento.

Quale e' la complessita' di queste funzioni?

Page 61: Lez. 61 Universita' di Ferrara Facolta' di Scienze Matematiche, Fisiche e Naturali Laurea Specialistica in Informatica Algoritmi Avanzati Liste e Puntatori

61

Ordinamento di liste per straight insertion

3 procedure basate su diverse semantiche/modalita' implementative

1. Opera per valore:

non altera la lista in ingresso, ma

costrusice una nuova lista ordinata contenente gli stessi elementi della lista originaria.

Si basa sull'utilizzo dell'ADT lista definito in precedenza (funzioni car(), cons(), …).

2. Opera per valore come la funzione 1, ma opera direttamente sulla rappresentazione della lista (che e' diversa da quella utilizzata nella rappresentazione dell'ADT lista utilizzato per la funzione 1).

3. Non opera per valore, e riutilizza gli stessi elementi della lista di ingresso per costruire la lista ordinata.

Opera direttamente sulla rappresentazione.

Page 62: Lez. 61 Universita' di Ferrara Facolta' di Scienze Matematiche, Fisiche e Naturali Laurea Specialistica in Informatica Algoritmi Avanzati Liste e Puntatori

62

Ordinamento di liste per straight insertion: 1a

list insertSort1(list l) { return( null(l) ? l : sSort(cons(car(l), EMPTYLIST), cdr(l)) );}

list sSort(list sed, list unSed) { return( null(unSed) ? sed : sSort(sIns(car(unSed), sed), cdr(unSed)) );}

Page 63: Lez. 61 Universita' di Ferrara Facolta' di Scienze Matematiche, Fisiche e Naturali Laurea Specialistica in Informatica Algoritmi Avanzati Liste e Puntatori

63

Ordinamento di liste per straight insertion: 1b

list sIns(element e, list l) { return( null(l) ? cons(e, l) : e<=car(l) ? cons(e, l) : cons(car(l), sIns(e, cdr(l))) );}

Page 64: Lez. 61 Universita' di Ferrara Facolta' di Scienze Matematiche, Fisiche e Naturali Laurea Specialistica in Informatica Algoritmi Avanzati Liste e Puntatori

64

Ordinamento di liste per straight insertion: 2

struct node { int value; struct node *next; };

typedef struct node *list;

// vedi pag. 51-53: lista con nodo dummy

list insertSort2(list l) { list scan = l->next; list newL = malloc(sizeof(struct node)); newL->value = 0; newL->next = NULL; while (scan!=NULL) { newL = ordInsert(scan->value, newL); // vedi pag. 53 scan = scan->next; } return(newL);}

Page 65: Lez. 61 Universita' di Ferrara Facolta' di Scienze Matematiche, Fisiche e Naturali Laurea Specialistica in Informatica Algoritmi Avanzati Liste e Puntatori

65

Ordinamento di liste per straight insertion: 3a

struct node { come in insertSort2 };

typedef struct node *list;

list insertSort3(list l) { list scan = l; list newL = malloc(sizeof(struct node)); newL->value = 0; newL->next = NULL; while (scan->next!=NULL) { struct node*temp = scan->next; newL = ordInsertM(temp, newL); } free(l); return(newL);}

Page 66: Lez. 61 Universita' di Ferrara Facolta' di Scienze Matematiche, Fisiche e Naturali Laurea Specialistica in Informatica Algoritmi Avanzati Liste e Puntatori

66

Ordinamento di liste per straight insertion: 3b

list ordInsertM(struct node *el, list l) { struct node *scan = l; while (scan->next!=NULL && scan->next->value < el->value) { scan = scan->next; } el->next = scan->next; scan->next = el; return(l);}