PROGRAMMAZIONE
AVANZATA JAVA E C
Massimiliano Redolfi
Lezione 7: Code, Stack, Liste
PAJC
Ricerca
prof. Massimiliano Redolfi – PAJC
2
PAJC
Ricerca
Se dobbiamo cercare un
elemento in un array possiamo
trovarci in due situazioni
Elementi non
ordinati
Elementi ordinati
Ricerca
sequenziale
Ricerca
binaria
prof. Massimiliano Redolfi – PAJC
3
PAJC
Ricerca sequenziale
La ricerca sequenziale è molto semplice: si passano tutti gli elementi
dell’array, dal primo all’ultimo, alla ricerca dell’elemento chiave:
int search_seq(const char *items, int count, char key)
{
for(int i=0; i<count; i++)
if(items[i] == key)
return i; // corrispondenza trovata
return -1; // nessuna corrispondenza trovata
}
La ricerca restituisce l’indice dell’elemento trovato oppure, se nessun
elemento corrisponde alla chiave -1.
Evidentemente una ricerca richiederà, in media, count/2 confronti.
prof. Massimiliano Redolfi – PAJC
4
PAJC
Ricerca sequenziale
E’ possibile migliorare l’efficienza dell’algoritmo scorrendo l’array tramite
puntatori e non tramite il sistema di indicizzazione:
int search_seq(const char *items, int count, char key)
{
char *p = items;
for(int i=0; i<count; i++)
if(*p++ == key)
return i; // corrispondenza trovata
return -1; // nessuna corrispondenza trovata
}
prof. Massimiliano Redolfi – PAJC
5
PAJC
Ricerca binaria
Se i dati non sono ordinati non c’è altra possibilità di
ricerca, bisogna scorrere i dati uno alla volta … e se i
dati sono molti è chiaro che i tempi si allungano (con
progressione lineare)
Ma se i dati sono ordinati c’è un’alternativa molto
efficiente al posto della ricerca sequenziale, la ricerca
binaria!
prof. Massimiliano Redolfi – PAJC
6
PAJC
Ricerca binaria
Supponiamo di voler cercare il numero 4 all’interno di un array
ordinato:
1
2
3
4
5
6
7
8
9
L’idea è semplice: confrontiamo il valore centrale dell’array rispetto
alla chiave se è maggiore allora ripeteremo la ricerca nella prima
metà altrimenti nella seconda. A questo punto basta ripetere il
procedimento ricorsivamente sino a quando non si trova
l’elemento cercato oppure non ci sono più elementi da cercare.
prof. Massimiliano Redolfi – PAJC
7
PAJC
Ricerca binaria
Cerco il numero 4:
1
2
3
4
5
6
7
8
9
Poiché 5 è maggiore di 4 cerco nella prima metà:
1
2
3
4
Poiché 2 è minore di 4 cerco nella seconda metà:
3
4
4
Trovato! Ogni volta divido per 2 lo spazio su cui agisco à
Il numero di confronti è al più pari a log2 n
prof. Massimiliano Redolfi – PAJC
8
PAJC
Ricerca binaria
int search_bin(char *items, int count, char key)
Supposto key = 4…
mid
low
high
{
int low=0, high=count-1, mid;
while(low <= high) {
mid = (low + high) / 2;
1 2 3 4 5 6 7 8 9
low mid
high
if(key < items[mid]) high = mid-1;
else if (key > items[mid]) low = mid+1;
else return mid; /* Trovato!! */
}
return -1; /* nessuna corrispondenza */
1 2 3 4 5 6 7 8 9
mid
lowhigh
}
1 2 3 4 5 6 7 8 9
mid
Ricordarsi che funziona
SOLO SU ARRAY ORDINATI!
low
high
1 2 3 4 5 6 7 8 9
prof. Massimiliano Redolfi – PAJC
9
PAJC
Code, Stack,
Liste ed Alberi
prof. Massimiliano Redolfi – PAJC
10
PAJC
Rappresentazione astratta dei dati
Un programma è composto da due parti fondamentali:
• gli algoritmi
• i dati
Abbiamo visto che i dati possono essere strutturati in
vario modo dai tipi semplici (int, char, …) ad insiemi
omogenei (gli array) a tipi di dati complessi come le
strutture.
prof. Massimiliano Redolfi – PAJC
11
PAJC
Rappresentazione astratta dei dati
I dati possono essere rappresentati da due punti di vista distinti:
-
a livello macchina: in cui ci si concentra sulla rappresentazione
fisica del dato (numero di bit, MIPS, …)
-
a livello astratto: in cui ci si concentra sugli aspetti funzionali
legati al dato, tralasciando i dettagli fisici (si ha già un esempio
di questo nel passaggio da int a float…)
Quando si progetta un sistema, un software è buona cosa
immaginare i dati come oggetti astratti, senza preoccuparsi
particolarmente dei dettagli fisici che subentreranno solo in un
secondo momento, quando si dovranno implementare
effettivamente le funzionalità del sistema.
prof. Massimiliano Redolfi – PAJC
12
PAJC
Rappresentazione astratta dei dati
Sino a questo momento ci siamo occupati di dati via
astrazione
?
strutture
float
via sempre più astratti ma sempre legati ad una
caratterizzazione fisica del dato (anche le
strutture non sono altro che raggruppamenti di
tipi ben associabili ad elementi fisici).
Il livello successivo trascende questi aspetti
astraendo ulteriormente il concetto di dato e
comprendendo in questo le funzionalità stesse
int
che permettono di accedere ad un elemento,
ovvero le routine di inserimento, estrazione,
ricerca … del dato stesso.
prof. Massimiliano Redolfi – PAJC
13
PAJC
Rappresentazione astratta dei dati
motori
di
astrazione
elaborazione
strutture
float
In questo senso il livello di astrazione che
affrontiamo oggi include la logica stessa di
accesso al dato in modo indipendente dal
tipo trattato.
Analizzeremo alcuni tra i principali di motori per
l’elaborazione dei dati:
1. le code
int
2. gli stack
3. le liste concatenate
prof. Massimiliano Redolfi – PAJC
14
PAJC
Code
prof. Massimiliano Redolfi – PAJC
15
PAJC
Code
Una coda è un elenco lineare di informazioni in cui gli accessi
avvengono secondo un ordinamento di tipo FIFO (first-in, firstout).
Questo significa che il primo oggetto inserito è anche il primo che
verrà estratto.
dato inserito
E
D
C
B
A
dato estratto
E’ la tipica situazione della coda in posta od al supermercato chi
prima arriva prima viene servito…
prof. Massimiliano Redolfi – PAJC
16
PAJC
Code
Le operazioni ammesse su una coda sono due: c_ins, c_ret per
aggiungere e recuperare un oggetto rispettivamente.
Notiamo che non ci sono funzioni ad accesso diretto ad una
coda.
Azione
Contenuto della coda
c_ins(A)
A
c_ins(B)
AB
c_ret(), ottiene A
B
c_ins(C)
BC
c_ins(D)
BCD
c_ret(), ottiene B
CD
c_ret(), ottiene C
D
prof. Massimiliano Redolfi – PAJC
17
PAJC
Code
Si noti che, come per gli altri motori di elaborazione dei dati, poco ci
importa di che cosa siano i dati trattati, quello che ci interessa è
come i dati vengono gestiti.
Utilità delle code:
-
buffer
-
elenchi di task
-
ritardi
-
…
Com’è implementabile una coda?
prof. Massimiliano Redolfi – PAJC
18
PAJC
Code
insert
retrive
Possiamo vederla come un array di una certa lunghezza (il numero
massimo di elementi gestibili dalla coda) e due puntatori o indici:
uno punta alla posizione di inserimento, l’altra a quella di
estrazione
prof. Massimiliano Redolfi – PAJC
19
PAJC
Code
insert
stato
iniziale
retrive
insert
c_ins(A)
A
retrive
insert
c_ins(B)
A
B
retrive
prof. Massimiliano Redolfi – PAJC
20
PAJC
Code
insert
c_ret()
B
retrive
insert
c_ins(C)
B
C
retrive
insert
c_ins(D)
B
C
D
retrive
prof. Massimiliano Redolfi – PAJC
21
PAJC
Code
insert
c_ret()
C
D
retrive
insert
c_ret()
D
retrive
insert
c_ret()
retrive
prof. Massimiliano Redolfi – PAJC
22
PAJC
Code: un semplice esempio
#include <stdio.h>
Utilizziamo nell’esempio come
buffer un array di char globale
chiamato coda che contiene al
più MAX_EL elementi.
#define MAX_EL 255
char coda[MAX_EL];
int ipos = 0, rpos = 0;
ipos e rpos sono rispettivamente
gli indici in cui inserire ed
estrarre i dati
void c_ins(char ch)
{
if(ipos >= MAX_EL){
printf(“Coda piena!\n”);
return;
}
coda[ipos] = ch;
ipos++;
}
prof. Massimiliano Redolfi – PAJC
23
PAJC
Code: un semplice esempio
ch c_ret(void)
int main(void)
{
{
if(rpos >= ipos){
printf(“Coda vuota!\n”);
c_ins(‘A’);
c_ins(‘B’);
return ‘\0’;
printf(“\nret: %c”, c_ret());
}
c_ins(‘C’);
char ch = coda[rpos];
c_ins(‘D’);
rpos++;
return ch;
printf(“\nret: %c”, c_ret());
printf(“\nret: %c”, c_ret());
}
printf(“\nret: %c”, c_ret());
}
prof. Massimiliano Redolfi – PAJC
24
PAJC
Code circolari
Che fare quando si raggiunge la fine della coda (del buffer)?
O ci si ferma oppure si ricomincia dall’inizio (memorizzando i dati
nelle celle che nel frattempo si sono liberate).
Quando scrittura e lettura proseguono in questo modo, dalla fine
della coda all’inizio si parla di code circolari.
Come si modifica il sistema precedente per gestire le code circolari?
(si devono modificare solo le funzioni c_ins e c_ret)
prof. Massimiliano Redolfi – PAJC
25
PAJC
Code circolari: un semplice esempio
void c_ins(char ch)
{
// la coda è piena quando un inserimento cancellerebbe un
// elemento non ancora letto, cioè quando ipos è uguale a rpos-1
// oppure ipos è alla fine dell’array e rpos all’inizio
if( (ipos+1 == rpos) || (ipos+1 == MAX_EL && rpos == 0) ) {
print(“Coda piena!\n”);
return;
}
coda[ipos++] = ch;
if(ipos >= MAX_EL) ipos = 0; // riprendi dall’inizio
}
prof. Massimiliano Redolfi – PAJC
26
PAJC
Code circolari: un semplice esempio
char c_ret()
{
// la coda è vuota se i due indici coincidono
if(rpos == ipos) {
printf(“Coda vuota\n”);
return ‘\0’;
}
char ch = coda[rpos++];
if(rpos >= MAX_EL) rpos = 0; // riprendi dall’inizio
return ch;
}
prof. Massimiliano Redolfi – PAJC
27
PAJC
Stack
prof. Massimiliano Redolfi – PAJC
28
PAJC
Stack
Uno stack ha un funzionamento opposto a quello della coda in
quanto gli gli accessi avvengono secondo un meccanismo di tipo
LIFO (last-in, first-out).
Questo significa che il primo oggetto inserito è l’ultimo che verrà
estratto.
Si può immaginare una pila (stack) di piatti, i piatti vengono
aggiunti e tolti dalla sommità della pila quindi l’ultimo piatto
appoggiato sulla pila sarà anche il primo ad essere estratto.
prof. Massimiliano Redolfi – PAJC
29
PAJC
Stack
Le operazioni ammesse su uno stack sono due: c_ins, c_ret per
aggiungere e recuperare un oggetto rispettivamente.
Notiamo che non ci sono funzioni ad accesso diretto ad uno
stack.
Azione
Contenuto dello stack
c_ins(A)
A
c_ins(B)
BA
c_ret(), ottiene B
A
c_ins(C)
CA
c_ins(D)
DCA
c_ret(), ottiene D
CA
c_ret(), ottiene C
A
prof. Massimiliano Redolfi – PAJC
inserisci
estrai
30
PAJC
Stack
Per convenzione le funzioni di inserimento ed estrazione dei dati da
uno stack sono dette push e pop rispettivamente.
Un esempio classico di gestione della memoria di tipo LIFO è
rappresentato dallo stack di sistema in cui vengono memorizzate
le variabili locali…
Com’è implementabile uno stack?
prof. Massimiliano Redolfi – PAJC
31
PAJC
Stack: un semplice esempio
#include <stdio.h>
#define MAX_EL 255
int stack[MAX_EL];
int tos = 0;
void push(int i)
{
Utilizziamo nell’esempio come
buffer un array di int globale
chiamato stack che contiene al
più MAX_EL elementi.
Ci basta un solo indice che indica
la cima dello stack (tos)
if(tos >= MAX_EL){
printf(“Stack pieno!\n”);
return;
}
stack[tos++] = i;
}
prof. Massimiliano Redolfi – PAJC
32
PAJC
Stack: un semplice esempio
int pop(void)
int main(void)
{
{
if(--tos < 0){
printf(“Stack vuoto!\n”);
push(‘A’);
push(‘B’);
return tos = 0;
printf(“\nret: %c”, pop());
}
push(‘C’);
push(‘D’);
return stack[tos];
printf(“\nret: %c”, pop());
printf(“\nret: %c”, pop());
}
printf(“\nret: %c”, pop());
}
prof. Massimiliano Redolfi – PAJC
33
PAJC
Liste concatenate
prof. Massimiliano Redolfi – PAJC
34
PAJC
Liste concatenate
Code e stack richiedono che la lettura del dato implichi l’eliminazione
dello stesso dalla memoria della coda, o dello stack. I dati inoltre
sono memorizzati in celle di memoria contigue secondo una
dimensione prefissata.
Le liste danno invece la possibilità all’utente di accedere ad un dato
in esse contenute senza rimuoverlo automaticamente. Inoltre
mantengono le informazioni in modo diverso senza la necessità
di preallocare un buffer.
Una lista concatenata può essere letta in modo più flessibile in
quanto ogni record di informazione contiene un
collegamento, un puntatore, al record successivo come in
una sorta di catena.
prof. Massimiliano Redolfi – PAJC
35
PAJC
Liste concatenate
Esistono due tipi di liste concatenate:
-
liste concatenate semplici: ogni oggetto contiene un puntatore
all’oggetto successivo
-
liste concatenate doppie: ogni oggetto contiene due puntatori,
uno all’oggetto successivo l’altro a quello precedente
prof. Massimiliano Redolfi – PAJC
36
PAJC
Liste a concatenamento semplice
Liste a concatenamento semplice:
informazioni
informazioni
informazioni
puntatore
puntatore
0
Notiamo che il dato proprio della lista (informazione) è solo una
parte dell’oggetto che compone l’elemento della lista che deve
prevedere anche il puntatore all’elemento successivo.
In genere l’oggetto gestito dalla lista può essere visto come una
struttura composta da un elemento il cui tipo dipende
dall’informazione gestita più un puntatore.
prof. Massimiliano Redolfi – PAJC
37
PAJC
Liste a concatenamento semplice
Supponiamo di realizzare una lista per una rubrica.
L’informazione è costituita da una struttura address tipo:
struct address {
char nome[50];
informazioni
puntatore
char via[100];
char telefono[15];
};
L’oggetto complessivo trattato dalla lista sarà:
struct list_item {
struct address info; // è l’informazione vera e propria
struct list_item *next;
};
prof. Massimiliano Redolfi – PAJC
38
PAJC
Liste a concatenamento semplice
Inserire un oggetto alla fine della lista:
Prima
start
info
info
info
NUOVO
0
Dopo
start
info
info
info
NUOVO
0
prof. Massimiliano Redolfi – PAJC
39
PAJC
Liste a concatenamento semplice
Chiamiamo la funzione list_add, questa dovrà avere in input l’elemento da
aggiungere più un puntatore all’ultimo elemento della lista (in modo da
agganciare i due oggetti).
struct list_item { struct address info; struct list_item *next; };
void list_add(struct list_item *new_item, struct list_item **last_item)
{
if(!*last_item) *last_item = i; // è il primo elemento della lista
else (*last_item)->next = i;
i->next = NULL;
*last_item = i;
Si noti che viene passato un puntatore
all’ultimo elemento della lista in modo da
poter modificare il valore dello stesso.
}
prof. Massimiliano Redolfi – PAJC
40
PAJC
Liste a concatenamento semplice
Risulta particolarmente semplice visualizzare il contenuto di una lista, è
sufficiente scorrerla dal primo all’ultimo elemento:
void list_show(struct list_item *first_item)
{
struct list_item *p = first_item;
int n = 0;
while(p) {
printf(“\n[%d]: %s”, ++n, p->info.nome);
p = p->next;
}
}
prof. Massimiliano Redolfi – PAJC
41
PAJC
Liste a concatenamento semplice
Liste a concatenamento semplice:
start
informazioni
informazioni
informazioni
puntatore
puntatore
0
Oltre alle operazioni di inserimento e visualizzazione possiamo avere però
altre situazioni:
-
inserire oggetti in una posizione iniziale o mediana
-
eliminare oggetti all’inizio, alla fine o in posizione mediana
Vediamo come si svolge concettualmente la cosa (per un esempio di codice
si veda l’esercitazione libretto_03)
prof. Massimiliano Redolfi – PAJC
42
PAJC
Liste a concatenamento semplice
Inserire un oggetto all’inizio:
Prima
NUOVO
start
informazioni
informazioni
informazioni
puntatore
puntatore
0
info
info
info
Dopo
start
NUOVO
0
prof. Massimiliano Redolfi – PAJC
43
PAJC
Liste a concatenamento semplice
Inserire un oggetto in una posizione qualsiasi:
Prima
NUOVO
start
info
info
info
0
Dopo
start
info
NUOVO
info
info
0
prof. Massimiliano Redolfi – PAJC
44
PAJC
Liste a concatenamento semplice
Eliminare un oggetto all’inizio:
Prima
start
info
info
info
0
Dopo
cancellato
start
info
info
0
prof. Massimiliano Redolfi – PAJC
45
PAJC
Liste a concatenamento semplice
Eliminare un oggetto alla fine:
Prima
start
info
info
info
0
Dopo
start
prof. Massimiliano Redolfi – PAJC
info
info
cancellato
0
0
46
PAJC
Liste a concatenamento semplice
Eliminare un oggetto mediano:
Prima
start
info
info
info
0
Dopo
start
info
cancellato
info
0
prof. Massimiliano Redolfi – PAJC
47
PAJC
Liste a concatenamento doppio
Liste a concatenamento doppio: ogni elemento è legato al
successivo ed al precedente
start
informazioni
informazioni
0
informazioni
0
Chiaramente valgono tutte le osservazioni precedenti solo che ora i
puntatori devono essere aggiornati a coppie…
prof. Massimiliano Redolfi – PAJC
48
PAJC
Liste a concatenamento semplice
Riprendendo l’esempio l’oggetto trattato dalla lista a
info
concatenamento doppio sarà:
prev
next
struct list_item {
struct address info; // è l’informazione vera e propria
struct list_item *next;
struct list_item *previous;
};
prof. Massimiliano Redolfi – PAJC
49