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