Traduzione dal Pascal al C di alcuni programmi di: “Fondamenti di programmazione dei calcolatori elettron Batini, Aiello, Lenzerini, Marchetti, Miola INDICE Argomento Ricorsione Matrici Liste Programma Pagina Pagina del testo Fattoriale 2 87 Rappresentazione compatta 3 107, 108, 109 Rappresentazione sequenziale 6 115-117 Rappresentazione collegata con array 9 126-127 Rappresentazione collegata con puntatori 12 128-131 Pile Rappresentazione sequenziale 17 149 Rappresentazione collegata con puntatori 19 150 Code Rappresentazione collegata con puntatori 21 152-153 Alberi binari Rappresentazione collegata con puntatori 23 162-166 Correttezza Triangolo errato 27 242 Ricerca binaria 28 248 Complessità Polinomio: quadratico e lineare 29 291, 292 Ordinamento Selection sort 31 296-297 Bubble sort 32 299 Merge sort 34 303, 306 Ricorsione Fattoriale: Funzione ricorsiva per il calcolo del fattoriale di un numero #include <stdio.h> int fattoriale(int n) { if (n==0) return 1; else return n*fattoriale(n-1); } main() { int n; /* lettura di n */ scanf("%d",&n); /* display del fattoriale */ printf("%d!=%d\n",n,fattoriale(n)); } Funzioni In C non esistono procedure ma solo funzioni che devono essere dichiarate <dich.funzione> static [ ] [<tipo>] <identificatore> ([<lista-parametri>]) <blocco C> es: int fat Le funzioni static sono visibili solo nel file in cui sono dichiarate. Il valore della funzione viene restituito return tramite e non l’istruzione con un’assegnazione all’identificatore della funzione come per il Pascal. La forma EBNF è: <istruz.-return> return [ <espressione> ; ] es: return n*fattoriale(n-1); If-then-else, operatori relazionali e booleani La sintassi dell’istruzione del C è: if <istruzione-if> if ( <espressione> ) <istruzione 1>; else <istruzion es: if (n==0) return 1; L’espressione può essere composta tramite operatori relazionali: == uguale, != diverso, > maggiore, < minore, maggiore >= o uguale, <= minore o uguale e operatori logici: && AND, || OR, ! NOT,& AND bit a bit, | OR bit a bit, ^ XOR bit a bit. Direttiva #include<stdio.h> Permette di usare le funzioni di I/O scanf e printf • int scanf( const char *s, X1, X2, ... Xn)1: legge da standard input n variabili. La stringa s formatta l’input, X1…Xn sono i puntatori alle es: scanf(“%d”,&n); legge un valore intero (“%d”) e lo assegna alla • int printf( const char *s, X1, X2, … Xn): stampa il valore di n variab secondo la formattazione in s. es: printf("%d!=%d\n",n,fattoriale(n)) per n=5 stampa: 5!=120. 1 Per il significato degli operatori * e & vedere a pag. 8 2 Matrici Rappresentazione compatta: La rappresentazione compatta di una matr memorizzare matrici Queste sparse. hanno la gran parte degli elementi uguali prefissato detto valore predominante per cui risulta conveniente memorizzare solo diversi da tale valore. A tale scopo si usa un array di elementi c informazioni: l’indice di riga, di colonna ed il valore. Il primo elemen 1 in Pascal) contiene il numero di righe della matrice, il numero predominante. Le componenti dell’array non utilizzate vengono riempit colonna non appartenenti alla matrice. Di seguito viene mostrata l’imple dato matrice compatta e delle funzioni accedi e memorizza. Nel main si e esemplificative a tali funzioni. #include <stdio.h> #define NMAX_COMPATTA 10 /* NMAX_COMPATTA-2 È il numero massimo degli elementi diversi dall'elemento predominante */ /* definizione del tipo matrice compatta */ typedef struct elem_mat_compatta { int riga, colonna; int valore; }; typedef struct elem_mat_compatta tipo_mat_compatta[NMAX_COMPATTA]; /* realizza l'operazione accedi su una matrice rappresentata mediante la matrice compatta c a tre informazioni. i È la riga, j la colonna */ int accedi(tipo_mat_compatta c,int i,int j) { int h; h=1; while (c[h].riga<i) h=h+1; while (c[h].riga==i && c[h].colonna<j) h=h+1; if (c[h].riga==i && c[h].colonna==j) return c[h].valore; else return c[0].valore; } /* realizza l'operazione memorizza su una matrice rappresentata mediante la matrice compatta c a tre informazioni. i È la riga, j la colonna e val il valore da memorizzare */ void memorizza (tipo_mat_compatta c,int i, int j, int val) { int h,q; h=1; Continua while (c[h].riga<i) h=h+1; 3 while (c[h].riga==i && c[h].colonna<j) h=h+1; /* controllo sul valore val da memorizzare */ if (val==c[0].valore) { if (c[h].riga==i && c[h].colonna==j) /* elimina elemento di c in posizione h */ for(q=h+1;q<NMAX_COMPATTA;q++) { c[q-1].riga=c[q].riga; c[q-1].colonna=c[q].colonna; c[q-1].valore=c[q].valore; } } else if (c[h].riga==i && c[h].colonna==j) c[h].valore=val; else /*se c'È posto memorizza il nuovo elemento in c*/ if (c[NMAX_COMPATTA-2].riga<=c[0].riga) printf("memorizzazione impossibile\n"); else { for(q=NMAX_COMPATTA-2;q>=h+1;q--) { c[q].riga=c[q-1].riga; c[q].colonna=c[q-1].colonna; c[q].valore=c[q-1].valore; } c[h].riga=i; c[h].colonna=j; c[h].valore=val; } } main() { tipo_mat_compatta mat; int l; /* inizializzazione prima riga dell'array con righe, colonne e valore predominante */ mat[0].colonna=3; mat[0].riga=3; mat[0].valore=0; /* inizializzazione dei restanti elementi ad una terna che non corrisponde a nessuna componente della matrice es:10 */ for(l=1;l<NMAX_COMPATTA;l++) { mat[l].riga=10; mat[l].colonna=10; mat[l].valore=0; Continua } 4 /* memorizzazione valori */ memorizza(mat,2,2,5); memorizza(mat,1,1,3); memorizza(mat,1,2,7); memorizza(mat,1,3,9); /* display array */ printf("display array:\n"); for(l=0;l<NMAX_COMPATTA;l++) printf("%d %d %d %d\n",l,mat[l].riga,mat[l].colonna,mat[l].valore); /* display di alcuni valori della matrice */ printf("elem 2,2 %d\n",accedi(mat,2,2)); printf("elem 1,1 %d\n",accedi(mat,1,1)); printf("elem 2,1 %d\n",accedi(mat,2,1)); } Definizioni di costanti in C Uno dei metodi in C per definire delle costanti#define è utilizzare la direttiva . Nel programma si usa tale direttiva per definire la costante NMAX_COMPATTA. Altri esemp #define PI 3.14, #define NOME “sabrina”, #define a_capo ‘\n’. Definizione di tipi in C Come in Pascal anche in C è possibile definire dei tipi di dati. typedef. In C si Semplificando e rimandando ad un manuale C per la forma EBNF della dichia si può dire che se in Pascal scriviamo ad esempio “type matricola = integ C è “typedef int matricola;”. Record e strutture struct. Il tipo record in Pascal ha il suo analogo C nel tipo struttura per cui s Le differenze nella sintassi tra C e Pascal possono essere notate confro tipo della matrice compatta all’inizio di questo programma e la dichiaraz testo. Funzioni void La funzione memorizza in realtà svolge il ruolo di una procedura Pascal ovve nessun valore. In tutti i casi in cui è necessario un simile comportament parola chiave void prima dell’identificatore della funzione. (es: void me Cicli For In questo programma vengono utilizzati vari cicli for la cui sintassi in <istr.-for> for [<expr1>]; ( [<expr2>]; [<expr3>]) <istruzione> <expr1>= insieme di assegnazioni separate da virgole per inizializzare la del ciclo. <expr2>=il ciclo viene eseguito finchè il valore di <expr2> è diverso da <expr3>=è l’espressione che implementa la variazione delle variabili di c es: for( l=0; l<NMAX_COMPATTA; l++) imposta l=0 ed esegue il ciclo incrementando l di 1 (l++) ad ogni iterazi l<NMAX_COMPATTA. 5 Liste Rappresentazione sequenziale: Una lista può essere rappresentata mediante componente ospita un elemento della lista. Dato che la dimensione della l dinamico, sono necessarie due variabili (primo per mantenere e lunghezza) sempre aggiornati confini della lista. Di seguito è mostrata l’implemementazione in C della sequenziale della lista e delle funzioni cons, car , canoniche cdr e null. #include<stdio.h> #define NMAX_LISTA 3 /* Numero massimo degli elementi della lista */ /* definizione del tipo lista mediante array */ typedef int tipo_atomi; typedef struct modello_lista { tipo_atomi elementi[NMAX_LISTA]; int primo,lunghezza; } tipo_lista; int null(tipo_lista lis) { if (lis.lunghezza==0) return(1); else return(0); } /* Effettua l'operazione car sulla lista lis. Per la lista si usa la rappresentazione sequenziale mediante array */ tipo_atomi car(tipo_lista lis) { if (null(lis)) { printf("operazione non eseguibile\n"); return -1; } else return(lis.elementi[lis.primo]); } /* Effettua l'operazione cons su una lista rappresentata con array e È l'elemento da inserire. La lista È ritornata in lis */ void cons(tipo_atomi e,tipo_lista *lis) { if ((*lis).lunghezza==NMAX_LISTA) printf("insufficiente dimensione dell'array\n"); else { Continua 6 if (null(*lis)) (*lis).primo=0; else if ((*lis).primo==0) (*lis).primo=NMAX_LISTA-1; else (*lis).primo=(*lis).primo-1; (*lis).elementi[(*lis).primo]=e; (*lis).lunghezza=(*lis).lunghezza+1; } } /* Effettua l'operazione cdr su una lista rappresentata con array. La lista È ritornata in lis */ void cdr(tipo_lista *lis) { if (null(*lis)) printf("operazione non eseguibile\n"); else { if ((*lis).lunghezza==1) (*lis).primo=0; else (*lis).primo=((*lis).primo+1) % NMAX_LISTA; (*lis).lunghezza=(*lis).lunghezza-1; } } main() { tipo_lista lis; lis.lunghezza=0; /* inserimento elementi nella lista */ cons(10,&lis); /* lis=(10) */ cons(20,&lis); /* lis=(20,10) */ cons(30,&lis); /* lis=(30,20,10) */ /* eliminazione elemento dalla lista */ printf("%d\n",car(lis)); /* car(lis)=30 cdr(&lis); /* lis=(20,10) printf("%d\n",car(lis)); /* car(lis)=20 cdr(&lis); /* lis=(10) printf("%d\n",car(lis)); /* car(lis)=10 } */ */ */ */ */ 7 Gli operatori & e * in C L’operatore &, applicato ad una variabile, restituisce il puntatore (l’in variabile allocata. Ad esempio, la funzione scanf vista nel primo programma: scanf(“%d”, &n); richiede la lettura di un intero nella variabile n; in questo caso è nece l’indirizzo della locazione di memoria in cui memorizzare l’intero (&n) L’operatore * , applicato ad una variabile di tipo puntatore, restituisce Passaggio di parametri “per variabile” in C I parametri formali di una funzione C possono essere soltanto parametri v parametri variabile/riferimento. Lo strumento utilizzato in C per ovviare puntatore. Ogni volta che una variabile deve essere modificata all’intern passato ad essa il puntatore a tale variabile. La funzione opera sulla va puntatore. L’unica eccezione a questa regola è rappresentata dagli array considerato un parametro variabile. Questa regola di programmazione è evidente nel programma sopra descritto Nella funzione car(tipo_lista lis) la lista è passata per valore infatti questa funzione non modifica la lis semplicemente il primo valore. In questo caso il parametro formale è una Le altre funzioni void cons(tipo_atomi e,tipo_lista *lis) void cdr(tipo_lista *lis) invece rispettivamente inseriscono ed eliminano un elemento dalla lista e modificata. In questo caso il passaggio di parametri deve essere per vari formale è un puntatore ad una variabile di tipo tipo_lista (tipo_lista *l Per quanto riguarda, ad esempio, la funzione cdr questa viene chiamata (n parametro un puntatore alla variabile lis tramite l’operatore &: cdr(&lis); All’interno della funzione si accede alla variabile puntata tramite l’ope es:(*lis).lunghezza=(*lis).lunghezza–1; 8 Rappresentazione collegata con array: La rappresentazione collegata media nell’associare ad ogni elemento della lista una componente dell’array, co - il valore del corrispondente elemento della lista; - il riferimento all’elemento successivo, cioè il valore dell’indice quale esso è memorizzato. Gli elementi sono memorizzati in modo qualunque e il loro ordinamento nel seguendo i riferimenti. L’ultimo elemento della lista ha come riferimento componenti dell’array in cui non sono memorizzati elementi della lista so i nuovi elementi. Questi vengono raccolti in modo collegato nello stesso libera. La lista libera rappresenta un serbatoio da cui prelevare component riversare le componenti che non sono più utilizzate per la lista. Di seguito è mostrata l’implementazione in C del tipo lista secondo la ra della funzione di inizializzazione della lista cons, libera car , cdr e e delle null. funzioni Nel Main vengono eseguite alcune chiamate esemplificative a tali funzioni. #include<stdio.h> #define NMAX_LISTA 10 /* numero massimo elementi della lista */ /* definizione tipo lista */ typedef int tipo_atomi; typedef int tipo_riferimento; typedef struct modello_lista { tipo_atomi dato; tipo_riferimento prox; }; typedef struct modello_lista tipo_array_lista[NMAX_LISTA]; int null(tipo_riferimento lis) { if (lis==NULL) return(1); else return(0); } /* Inizializza la lista libera memorizzata in "elementi" il cui riferimento iniziale È "iniz_ll"; l'inizializzazione viene effettuata collegando tra loro tutte le componenti dell'array nell'ordine corrispondente al valore dell'indice */ void inizializza_lista_libera(tipo_riferimento *iniz_ll,tipo_array_lista elementi) { tipo_riferimento i; for(i=0;i<NMAX_LISTA;i++) elementi[i].prox=i+1; *iniz_ll=1; elementi[NMAX_LISTA-1].prox=NULL; } Continua 9 /* Effettua l'operazione car sulla lista rappresentata mediante array */ tipo_atomi car(tipo_array_lista elementi, tipo_riferimento lis) { return elementi[lis].dato; } /* Effettua l'operazione cons su una lista rappresentata in modo collegato mediante array; elementi È l'array e lis il riferimento iniziale; iniz_ll È il riferimento iniziale della lista libera; l'effetto dell'operazione È quello di modificare l'array in cui È memorizzata la lista affinchÈ esso rappresenti la lista risultante */ void cons(tipo_atomi e, tipo_array_lista elementi, tipo_riferimento *lis, tipo_riferimento *iniz_ll) { tipo_riferimento temp; if (*iniz_ll==NULL) printf("dimensione insufficiente dell'array\n"); else { /* il nuovo elemento viene memorizzato prelevando una componente (la prima) della lista libera */ temp=*iniz_ll; *iniz_ll=elementi[*iniz_ll].prox; elementi[temp].dato=e; elementi[temp].prox=*lis; *lis=temp; } } /* effettua l'operazione cdr su una lista rappresentata in modo collegato mediante array; elementi È l'array e lis il riferimento iniziale; iniz_ll È il riferimento iniziale della lista libera; l'effetto dell'operazione È quello di modificare l'array in cui È memorizzata la lista affinchÈ esso rappresenti la lista risultante */ void cdr(tipo_riferimento *lis, tipo_riferimento *iniz_ll, tipo_array_lista elementi) { int temp; if (null(*lis)) printf("operazione non applicabile\n"); else { Continua /* si elimina il primo elemento della lista 10 e si rilascia alla lista libera la componente non pi— utilizzata */ temp=*lis; *lis=elementi[*lis].prox; elementi[temp].prox=*iniz_ll; *iniz_ll=temp; } } main() { tipo_riferimento lis=NULL; tipo_riferimento iniz_ll=NULL; tipo_array_lista elementi; /* inizializzazione lista libera */ inizializza_lista_libera(&iniz_ll,elementi); /* inserimento elementi */ cons(5,elementi,&lis,&iniz_ll); cons(15,elementi,&lis,&iniz_ll); cons(25,elementi,&lis,&iniz_ll); cons(35,elementi,&lis,&iniz_ll); /* eliminazione elementi */ printf("%d\n",car(elementi,lis)); cdr(&lis,&iniz_ll,elementi); printf("%d\n",car(elementi,lis)); cdr(&lis,&iniz_ll,elementi); printf("%d\n",car(elementi,lis)); cdr(&lis,&iniz_ll,elementi); printf("%d\n",car(elementi,lis)); } /* /* /* /* lis=(5) lis=(5,15) lis=(5,15,25) lis=(5,15,25,35) */ */ */ */ /* lis=(5,15,25) */ /* lis=(5,15) */ /* lis=(5) */ 11 Rappresentazione collegata con puntatori: Il tipo puntatore è un tipo di rappresentano indirizzi di locazioni di memoria. Mediante l’uso del tipo (struct) si può realizzare in modo efficace la rappresentazione collegata corrispondere ad ogni elemento della lista una istanza del tipo struct co informazione e da un campo puntatore alla istanza struct successiva. Ques rappresentazione più efficiente perché non è necessario imporre a priori dimensione della lista. Lo spazio di memoria occupato è infatti proporzio elementi della lista stessa. Di seguito sono mostrate due implementazioni della rappresentazione colle prima rappresenta la traduzione in C dell’implementazione che il libro pr seconda è una versione, forse più naturale in C, in non cui sono le funzioni void ma cons restituiscono la lista modificata dall’operazione. Versione 1: operazioni cons e cdr implementate con funzioni void #include<stdio.h> /* definizione del tipo lista */ typedef struct modello_atomo { int val; struct modello_atomo *next; } tipo_atomo; typedef tipo_atomo *tipo_lista; int null(tipo_lista lis) { if (lis==NULL) return(1); else return(0); } /* Effettua l'operazione car su una lista rappresentata da record e puntatori. lis È il puntatore al primo elemento della lista */ int car(tipo_lista lis) { if (null(lis)) { printf("operazione non eseguibile\n"); return -1; } else return(lis->val); } /* Effettua l'operazione cons su una lista rappresentata da record e puntatori. val È l'elemento da inserire nella Continua lista. La lista È restituita in lis */ 12 void cons(int val,tipo_lista *lis) { tipo_lista temp; temp=(tipo_lista)malloc(sizeof(tipo_atomo)); temp->val=val; temp->next=*lis; *lis=temp; } /* Effettua l'operazione cdr su una lista rappresentata da record e puntatori. La lista È restituita in lis. */ void cdr(tipo_lista *lis) { tipo_lista temp; if (null(*lis)) printf("operazione non eseguibile"); else { temp=*lis; *lis=(*lis)->next; free(temp); } } main() { tipo_lista app,l; l=NULL; /* Inserimento elementi nella lista */ cons(10,&l); /* l=(10) */ cons(20,&l); /* l=(20,10) */ cons(30,&l); /* l=(30,20,10) */ /* display degli elementi della lista */ app=l; while(app != NULL) { printf("%d\n",app->val); app=app->next; } /* eliminazione degli elementi dalla printf("%d\n",car(l)); /* car(l)=30 cdr(&l); /* l=(20,10) printf("%d\n",car(l)); /* car(l)=20 cdr(&l); /* l=(10) printf("%d\n",car(l)); /* car(l)=10 } lista */ */ */ */ */ */ 13 Versione 2: operazioni cons e cdr implementate con funzioni che restituiscono la lista modificata #include<stdio.h> /* Definizione del tipo lista */ typedef struct modello_atomo { int val; struct modello_atomo *next; } tipo_atomo; typedef tipo_atomo *tipo_lista; int null(tipo_lista lis) { if (lis==NULL) return(1); else return(0); } /* Effettua l'operazione car su una lista rappresentata da record e puntatori. lis È il puntatore al primo elemento della lista */ int car(tipo_lista lis) { if (null(lis)) { printf("operazione non eseguibile\n"); return -1; } else return(lis->val); } /* Effettua l'operazione cons su una lista rappresentata da record e puntatori. lis È il puntatore al primo elemento della lista */ tipo_lista cons(int val,tipo_lista lis) { tipo_lista temp; temp=(tipo_lista)malloc(sizeof(tipo_atomo)); temp->val=val; temp->next=lis; lis=temp; return lis; } Continua 14 /* Effettua l'operazione cdr su una lista rappresentata da record e puntatori. lis È il puntatore al primo elemento della lista */ tipo_lista cdr(tipo_lista lis) { tipo_lista temp; if (null(lis)) printf("operazione non eseguibile"); else { temp=lis; lis=lis->next; free(temp); } return lis; } main() { tipo_lista app,l; l=NULL; /* inserimento elementi nella lista */ l=cons(10,l); /* l=(10) */ l=cons(20,l); /* l=(20,10) */ l=cons(30,l); /* l=(30,20,10) */ /* display degli elementi inseriti */ app=l; while(app != NULL) { printf("%d\n",app->val); app=app->next; } /* eliminazione degli elementi dalla lista */ printf("%d\n",car(l)); /* car(l)=30 */ l=cdr(l); /* l=(20,10) */ printf("%d\n",car(l)); /* car(l)=20 */ l=cdr(l); /* l=(10) */ printf("%d\n",car(l)); /* car(l)=10 */ } 15 Puntatori: C e Pascal Le operazioni usualmente disponibili su una variabile p di tipo punta 1. l’accesso alla locazione il cui indirizzo è memorizzato in p. es: *p in C p^ in Pascal 2. la richiesta di una nuova locazione di memoria. es: new(p); in Pascal p=(tipo_lista)malloc(sizeof(tipo_atomo)); in C la funzione malloc ha la stesso scopo della funzione new, è necessar dimensione della porzione di memoria che si intende allocare. Nell’e sizeof, applicato all’identificatore di tipo tipo_atomo, restituisce byte occupati da una istanza della struttura tipo_atomo. La funzione malloc restituisce un puntatore di tipo indefinito; per effettuare una conversione di tipo (casting). Questo si ottiene inse chiamata alla malloc l’espressione (<tipo>). Nel nostro caso p=(tipo 3. il rilascio della locazione di memoria il cui indirizzo è memorizzat es: dispose(p); in Pascal free(p); in C Per la gestione delle variabili puntatore in C oltre agli operatori usare anche l’operatore . Se p è un puntatore (*p).val=p val. Un'altra differenza è il nome della costante che rappresenta il puntam nil mentre in C è NULL. 16 Pile Rappresentazione sequenziale: Una pila è un tipo astratto che consente di multiinsieme di elementi gestito con disciplina LIFO (“Last In First Out” sequenziale si utilizza una variabile elementi di ).tipo Se Narray è la ( dimensione dell’array l’elemento affiorante è memorizzato in posizione N-1, il penultimo in pos al primo elemento inserito, che è memorizzato in posizione 0. In un’ulter (posizione_top ) si memorizza l’indice dell’elemento affiorante. Di seguito è mostrata l’implementazione in C del tipo pila secondo la rap delle funzioni push, pop e test_pila_vuota. Nel Main vengono eseguite alcune c esemplificative a tali funzioni. #include<stdio.h> #define NMAX_ARRAY_PILA 10 pila */ #define PILA_VUOTA -1 /* numero massimo degli elementi della /* definizione tipo pila */ typedef int tipo_elementi; typedef struct modello_pila { tipo_elementi elementi[NMAX_ARRAY_PILA]; int posizione_top; } tipo_pila; void assegna_vuota(tipo_pila *p) { (*p).posizione_top=PILA_VUOTA; } int test_pila_vuota(tipo_pila p) { if (p.posizione_top==NMAX_ARRAY_PILA-1) return -1; else return 0; } /* effettua l'operazione push sulla pila p rappresentata mediante array. e È l'elemento da inserire nella pila */ void push(tipo_pila *p,tipo_elementi e) { if (test_pila_vuota(*p)) printf("insufficiente dimensione dell'array\n"); else { (*p).posizione_top=(*p).posizione_top+1; (*p).elementi[(*p).posizione_top]=e; } Continua 17 } /* effettua l'operazione pop sulla pila rappresentata mediante array. p È la pila */ void pop(tipo_pila *p) { if ((*p).posizione_top==PILA_VUOTA) printf("operazione non applicabile"); else (*p).posizione_top=(*p).posizione_top-1; } main() { tipo_pila p; /* assegnazione pila vuota alla pila */ assegna_vuota(&p); /* inserimento valori */ push(&p,5); push(&p,35); push(&p,45); push(&p,57); /* eliminazione valori */ pop(&p); /* elimina 57 */ pop(&p); /* elimina 45 */ pop(&p); /* elimina 35 */ pop(&p); /* elimina 5 */ pop(&p); /* operazione non applicabile */ } 18 Rappresentazione collegata con La puntatori: rappresentazione collegata della pila p implementata con puntatori e strutture. Un modo semplice di realizzare la struttura collegata con puntatori è quello di effettuare l’inserimento e testa alla lista. In questo modo, il puntatore iniziale della lista punte affiorante, se la pila non è vuota; in caso contrario, il suo valore sarà Di seguito è mostrata l’implementazione in C del tipo pila secondo la rap delle funzioni push, pop e test_pila_vuota. Nel Main vengono eseguite alcune c esemplificative a tali funzioni. #include<stdio.h> /* definizione del tipo pila */ typedef int tipo_elementi; typedef struct modello_record { tipo_elementi elemento; struct modello_record *next; } record_pila; typedef record_pila *punt_pila; void assegna_vuota(punt_pila *p) { (*p)=NULL; } int test_pila_vuota(punt_pila p) { if (p==NULL) return 1; else return 0; } /* effettua l'operazione pop su una pila rappresentata mediante record e puntatori. p È il puntatore alla pila */ void pop(punt_pila *p) { punt_pila q; if (test_pila_vuota(*p)) printf("operazione non applicabile\n"); else { q=(*p); (*p)=(*p)->next; free(q); } } /* effettua l'operazione di push su una pila rappresentata mediante Continua 19 record e puntatori. p È il puntatore alla pila e È l'elemento da inserire */ void push(punt_pila *p,tipo_elementi e) { punt_pila temp; temp=(punt_pila)malloc(sizeof(record_pila)); temp->elemento=e; temp->next=(*p); (*p)=temp; } main() { punt_pila p; /* assegna alla pila la pila vuota */ assegna_vuota(&p); /* inserimento valori */ push(&p,5); push(&p,35); push(&p,45); push(&p,57); /* eliminazione valori */ pop(&p); /* elimina 57 */ pop(&p); /* elimina 45 */ pop(&p); /* elimina 35 */ pop(&p); /* elimina 5 */ pop(&p); /* operazione non applicabile */ } 20 Code Rappresentazione collegata con Una puntatori: coda è un tipo astratto che consente d rappresentare un multiinsieme di elementi gestito con disciplina FIFO (“F Nella rappresentazione collegata con puntatori si fa generalmente uso di puntatore. La prima primo)( contiene il puntatore all’elemento in testa alla cod (ultimo) contiene il puntatore all’elemento che si trova alla fine della vuota sarà indicata dal valore NULL per entrambe queste variabili. Di seguito è mostrata l’implementazione in C del tipo coda secondo la rap delle funzioni in_coda , out_coda , test_coda_vuota. Nel Main vengono eseguite alcune esemplificative a tali funzioni. #include<stdio.h> /* definizione tipo dato coda */ typedef int tipo_elementi; typedef struct modello_record { tipo_elementi elemento; struct modello_record *next; } record_coda; typedef record_coda *punt_coda; int test_coda_vuota(punt_coda p) { if (p==NULL) return -1; else return 0; } /* Inserisce l'elemento e in una coda; la coda È rappresentata mediante record e puntatori; p È il puntatore al primo elemento della coda, u all'ultimo */ void in_coda(punt_coda *p,punt_coda *u,tipo_elementi e) { if (test_coda_vuota(*p)) { *p=(punt_coda)malloc(sizeof(record_coda)); *u=*p; } else { (*u)->next=(punt_coda)malloc(sizeof(record_coda)); *u=(*u)->next; } (*u)->elemento=e; (*u)->next=NULL; Continua 21 } /* Elimina un elemento dalla coda; la coda È rappresentata mediante record e puntatori; p È il puntatore al primo elemento della coda, u all'ultimo */ void out_coda(punt_coda *p,punt_coda *u) { punt_coda aux; if (test_coda_vuota(*p)) printf("coda vuota: operazione non applicabile\n"); else { aux=*p; (*p)=(*p)->next; if (*p==NULL) *u=NULL; free(aux); } } main() { punt_coda p,u; p=NULL; u=NULL; /* inserimento elementi in coda */ in_coda(&p,&u,5); /* coda=(5) in_coda(&p,&u,24); /* coda=(5,24) in_coda(&p,&u,35); /* coda=(5,24,35) in_coda(&p,&u,53); /* coda=(5,24,35,53) */ */ */ */ /* eliminazione elementi in coda */ out_coda(&p,&u); /* coda=(24,35,53) */ out_coda(&p,&u); /* coda=(35,53) */ out_coda(&p,&u); /* coda=(53) */ out_coda(&p,&u); /* coda=() */ out_coda(&p,&u); /* operazione non applicabile */ } 22 Alberi binari Rappresentazione collegata con Gli puntatori: alberi binari sono alberi in cui ogni massimo due figli. Sui figli di ogni nodo è definito un ordinamento: in g prima nell’ordinamento è detto figlio sinistro, mentre l’altro è detto fi rappresentazione collegata con puntatori il tipo struct che si utilizza p nodo viene definito con tre campi: uno per l’informazione associata al no sottoalbero sinistro ed uno per il puntatore al sottoalbero destro. Il va puntatore indica che il corrispondente sottoalbero è vuoto. Di seguito è in C del tipo albero binario secondo la rappresentazione descritta, dell test_albero_vuoto e costruisci. Quest’ultima funzione costruisce un albero bin albero in notazione parentetica inserito in input. Vengono implementate a visita in preordine dell’albero. La prima versione è ricorsiva la seconda struttura dati pila. Nel Main vengono eseguite alcune chiamate esemplific #include<stdio.h> typedef struct modello_nodo { int info; struct modello_nodo *sin; struct modello_nodo *des; } tipo_nodo; typedef tipo_nodo *punt_albero; int test_albero_vuoto(punt_albero alb) { if (alb==NULL) return 1; else return 0; } /* Realizza l'operazione sinistro su un albero binario rappresentato mediante record e puntatori; alb È il puntatore iniziale dell'albero */ punt_albero *sinistro(punt_albero alb) { if (test_albero_vuoto(alb)) { printf("operazione non applicabile\n"); return NULL; } else { return &(alb->sin); } } Continua 23 /* Realizza l'operazione destro su un albero binario rappresentato mediante record e puntatori; alb È il puntatore iniziale dell'albero. */ punt_albero *destro(punt_albero alb) { if (test_albero_vuoto(alb)) { printf("operazione non applicabile\n"); return NULL; } else { return &(alb->des); } } /* Costruisce la rappresentazione con puntatori e record di un albero binario di cui si legge da ingresso la rappresentazione parenteticail puntatore alla radice dell'albero costruito viene restituito con la variabile alb. */ void costruisci(punt_albero *alb) { char ch; scanf("%c",&ch); scanf("%c",&ch); if (ch==')') /* l'albero da costruire È vuoto */ *alb=NULL; else { /* si crea il nodo corrispondente alla radice */ *alb=(punt_albero)malloc(sizeof(tipo_nodo)); (*alb)->info=ch; /* si costruisce il sottoalbero sinistro */ costruisci(sinistro(*alb)); /* si costruisce il sottoalbero destro */ costruisci(destro(*alb)); scanf("%c",&ch); } } /* Funzione ricorsiva che effettua la visita in preordine di un albero binario per stampare i valori dei nodi; alb È la radice dell'albero binario. */ Continua 24 void preordine(punt_albero alb) { if (alb!=NULL) { /* analizza la radice */ printf("%c\n",(*alb).info); /* analizza il sottoalbero sinistro */ preordine(alb->sin); /* analizza il sottoalbero destro */ preordine(alb->des); } } /* Definizione del tipo di dato pila e delle relative funzioni di gestione. Necessari per la versione iterativa della visita in preordine dell'albero binario. */ typedef punt_albero tipo_elementi; typedef struct modello_record { tipo_elementi elemento; struct modello_record *next; } record_pila; typedef record_pila *punt_pila; void assegna_vuota(punt_pila *p) { (*p)=NULL; } int test_pila_vuota(punt_pila p) { if (p==NULL) return 1; else return 0; } void pop(punt_pila *p, punt_albero *alb) { punt_pila q; if (test_pila_vuota(*p)) printf("operazione non applicabile\n"); else { q=(*p); (*alb)=(*p)->elemento; (*p)=(*p)->next; free(q); } } Continua 25 void push(punt_pila *p,tipo_elementi e) { punt_pila temp; temp=(punt_pila)malloc(sizeof(record_pila)); temp->elemento=e; temp->next=(*p); (*p)=temp; } /* Funzione iterativa che effettua la visita in preordine di un albero binario per stampare i valori dei nodi; alb È la radice dell'albero binario. */ void preordine2(punt_albero *alb) { /* Assegna a pila il valore pari a pila vuota */ punt_pila pila; assegna_vuota(&pila); /* Se l'albero non È vuoto, memorizza nella pila il puntatore alla radice, in modo che essa sia il primo nodo ad essere visitato */ if (*alb!=NULL) push(&pila,*alb); while (!test_pila_vuota(pila)) { /* L'elemento affiorante della pila È il puntatore alla radice del prossimo sottoalbero da visitare, cioÈ È il puntatore al prossimo nodo dell'albero da visitare */ pop(&pila,alb); printf("%c\n",(*alb)->info); /* Si inseriscono nella pila i puntatori al figlio destro (prima) e al figlio sinistro (dopo), solo se sono diversi da NULL; l'ordine È dovuto al fatto che il figlio sinistro È il prossimo da analizzare , e quindi deve essere l'ultimo ad essere inserito nella pila */ if ((*alb)->des!=NULL) push(&pila,(*alb)->des); if ((*alb)->sin!=NULL) push(&pila,(*alb)->sin); } } main() { punt_albero a; Continua 26 a=NULL; /* costruzione dell'albero */ costruisci(&a); /* display dei nodi: visita preordine ricorsiva */ preordine(a); /* display dei nodi: visita preordine iterativa */ preordine2(&a); } Correttezza Triangolo errato: Programma errato per il problema del tipo di triangolo. #include<stdio.h> main() { int primo,secondo,terzo; int uguali; scanf("%d %d %d",&primo,&secondo,&terzo); uguali=0; if (primo==secondo) uguali=uguali+1; if (primo==terzo) uguali=uguali+2; if (secondo==terzo) uguali=uguali+1; if (uguali==0) printf("scaleno\n"); if (uguali==1) printf("isoscele\n"); else printf("equilatero\n"); } 27 Ricerca binaria: La ricerca binaria è un metodo di ricerca di un valore in u valori che ha una complessità pari a log N (la base del logaritmo è 2). S cui fare la ricerca siano disposti in un array di dimensione N. L’idea ch binaria è la seguente: si accede all’elemento mediano (indice=N/2) dell’a valore che si sta cercando. Se l’elemento è quello cercato allora la rice Se l’elemento mediano ha una chiave maggiore di quello che si sta cercand prosegue con lo stesso metodo nella parte dell’array con indice inferiore mediano. Viceversa, se l’elemento mediano ha una chiave inferiore la rice dell’array con indice superiore a quello dell’elemento mediano. Di seguito l’implementazione in C di una funzione di ricerca binaria. #include<stdio.h> /* la funzione prende come parametro un valore intero val, un array di elementi ordinati e il numero degli elementi dell'array e restituisce 1 se val È contenuto nel vettore 0 altrimenti. Viene utilizzato un algoritmo di ricerca binaria */ int ric_bin(int val,int vet[],int n) { int inf,sup,med,trovato; inf=1; sup=n; trovato=0; while (sup-inf>=0 && !trovato) { med=(inf+sup)/2; if (vet[med]==val) trovato=1; if (vet[med]<val) inf=med+1; if (vet[med]>val) sup=med-1; } return trovato; } main() { /* valorizzazione array */ int vet[10]={2,2,3,4,5,8,9,45,67,99}; /* ricerca binaria su array */ if (ric_bin(14,vet,10)) printf("elemento trovato"); } 28 Complessità Polinomio quadratico e lineare Programma : per la valutazione di un polinomio di g punto. Vengono implementate due funzioni: la prima è quadratica (compless è lineare (complessità O(n)). #include<stdio.h> #define N 4 /* N È il grado del polinomio */ /* Effettua il calcolo del polinomio di grado N di cui gli N+1 coefficienti sono memorizzati nell'array a. L'ascissa su cui eseguire il calcolo viene letto da input. L'algoritmo usato È quadratico. */ float pol_1(int a[]) { int i,j; float x,y,val; /* x contiene il valore del punto in cui si vuole valutare il polinomio, y il valore di una potenza di x, val il valore del polinomio nel punto x */ val=a[0]; scanf("%f",&x); for(i=1;i<=N;i++) { y=1; for(j=1;j<=i;j++) y=y*x; val=val+(a[i]*y); } return val; } /* Effettua il calcolo del polinomio di grado N di cui gli N+1 coefficienti sono memorizzati nell'array a. L'ascissa su cui eseguire il calcolo viene letto da input. L'algoritmo usato È lineare. */ float pol_2(int a[]) { int i,j; float x,y,val; /* x contiene il valore del punto in cui si vuole valutare il polinomio, y il valore di una potenza di x, val il valore del polinomio nel punto x */ Continua val=a[0]; 29 scanf("%f",&x); y=1; for(i=1;i<=N;i++) { y=y*x; val=val+(a[i]*y); } return val; } main() { /* definizione coefficienti */ int vet[N+1]={2,3,46,6,3}; /* display risultati */ printf("programma quadratico: %f\n",pol_1(vet)); printf("programma lineare: %f\n",pol_2(vet)); } 30 Ordinamento Selection Sort: L’algoritmo di ordinamento per selezione esegue N iterazioni dell’array). Nell’iterazione i-esima si scandiscono gli elementi dell’arr seleziona l’elemento minimo e si scambia con l’elemento i-esimo. Di segui #include<stdio.h> #define N 5 /* N È la dimensione massima dell'array */ /* definizione del tipo elemento dell'array */ typedef char tipo_informazione; typedef struct modello_elem { tipo_informazione inf; int chiave; } elem; void scambia(elem *a,elem *b) { elem app; app=*b; *b=*a; *a=app; } /* Effettua l'ordinamento selection sort sull'array z di n elementi */ void ordinamento_per_selezione(elem z[],int n) { int i,j,imin; for(i=0;i<=n-1;i++) { /* ricerca della componente minima tra z[i] e z[n] */ imin=i; for(j=i+1;j<=n;j++) if (z[j].chiave<z[imin].chiave) imin=j; /* scambia z[imin] e z[i] */ scambia(&z[i],&z[imin]); } } main() { int k=0; elem vet[N]; /* riempimento array */ Continua 31 vet[0].chiave=2; vet[1].chiave=45; vet[2].chiave=8; vet[3].chiave=99; vet[4].chiave=67; vet[0].inf='a'; vet[1].inf='c'; vet[2].inf='b'; vet[3].inf='e'; vet[4].inf='d'; /* ordinamento array */ ordinamento_per_selezione(vet,N-1); /* array risultato */ for(k=0;k<N;k++) printf("%c\n",vet[k].inf); } Bubble Sort: L’ordinamento a bolle si basa sul principio che in un array ord due elementi adiacenti z[i] e z[i+1] abbiamo che il primo è minore o ugua confronta ripetutamente coppie adiacenti che vengono scambiate se non ri Se k è l’indice dell’ultimo elemento dell’array, la prima iterazione conf z[k].chiave < z[k-1].chiave allora scambia z[k] e z[k-1]. All’iterazione confronto viene fatto tra z[k-1].chiave e z[k-2].chiave e così via fino a z[0].chiave. Alla fine della prima scansione dell’array in z[0] c’è l’ele occupa quindi la sua posizione definitiva. Non è necessario nelle scansio a z[0] ma basta fermarsi a z[1]. Generalizzando si può dire che la scansi dall’elemento dell’array in posizione k ed arriva all’elemento in posizio scansione dell’array non viene effettuato nemmeno uno scambio significa c e che è inutile proseguire con le scansioni successive. Di seguito l’impl #include<stdio.h> #define N 5 /* N È il numero degli elementi dell'array da ordinare */ /* Definizione del elemento dell'array. L'ordinamento verrà eseguito rispetto al campo chiave. */ typedef char tipo_informazione; typedef struct modello_elem { tipo_informazione inf; int chiave; } elem; void scambia(elem *a,elem *b) { elem app; app=*b; *b=*a; *a=app; } /* Funzione di ordinamento a bolle (Bubble Sort). Continua 32 vet È un array di elementi, n gli elementi dell'array. */ void ordinamento_a_bolle(elem vet[], int n) { int fine,i,j; fine=0; i=0; while (!fine && i<n) { fine=1; i++; for(j=n-1;j>i;j--) { if (vet[j].chiave<vet[j-1].chiave) { /* scambio valori vet[j] e vet[j-1] */ scambia(&vet[j],&vet[j-1]); /* aggiornamento variabile fine */ fine=0; } } } } main() { int k=0; elem vet[N]; /* riempimento array */ vet[0].chiave=2; vet[0].inf='a'; vet[1].chiave=45; vet[1].inf='c'; vet[2].chiave=8; vet[2].inf='b'; vet[3].chiave=99; vet[3].inf='e'; vet[4].chiave=67; vet[4].inf='d'; /* ordinamento array */ ordinamento_a_bolle(vet,N); /* display array ordinato */ for(k=0;k<N;k++) printf("%c\n",vet[k].inf); } 33 Mergesort: Algoritmo di fusione L’algoritmo Mergesort si basa sull’operazione di fusione di due array or interi x e y ordinati in ordine crescente, con m componenti ciascuno, si array z ordinato costituito dai 2m elementi degli array x e y. Per fare c la scansione degli array x e y ed un indice k per l’inserimento nell’arra i=0, j=0, k=0 ad ogni iterazione si confrontano x[i] e y[j]. I casi possi x[i]<=y[j]: si pone z[k]=x[i], si incrementano i e k x[i]>y[j]: si pone z[k]=y[j], si incrementano j e k Quando si completa la scansione di uno dei due array x o y si pongono in dell’altro array. Di seguito è mostrata l’implementazione in C dell’algor #include<stdio.h> #define N 5 /* N È il numero di elementi degli array */ /* Funzione per la fusione (merge) di due array. Gli array x e y hanno N componenti ciascuno, z È l'array risultato, dim È la dimensione dell'array risultato */ void fusione(int x[],int y[],int z[],int *dim) { int i,j,k; /* la variabile i serve per scorrere l'array x, la variabile j l'array y e la variabile k l'array z */ i=0;j=0;k=0; while (i<=N-1 && j<=N-1) { if (x[i]<=y[j]) { z[k]=x[i];i=i+1; } else { z[k]=y[j];j=j+1; } k=k+1; } /* a questo punto È stata completata la scansione di almeno uno dei due array */ if (i<=N-1) do /* si completa z con elementi da x */ { z[k]=x[i];i=i+1;k=k+1; } while (i<=N-1); else Continua 34 do { z[k]=y[j];j=j+1;k=k+1; } while (j<=N-1); *dim=k; } main() { int k=0; int dim_fusione=0; /* riempimento array */ int vet1[N]={2,3,9,18,20}; int vet2[N]={4,7,67,88,89}; int vet_fusione[2*N]; /* merge dei due array in vet_fusione */ fusione(vet1,vet2,vet_fusione,&dim_fusione); /* display dell'array risultato */ for(k=0;k<=dim_fusione-1;k++) printf("%d\n",vet_fusione[k]); } Mergesort: Algoritmo Mergesort L’algoritmo è intrinsecamente ricorsivo e può essere così descritto: se l un confronto ed eventualmente uno scambio, si ottiene l’array ordinato. S elementi lo si divide in due array aventi ciascuno la metà degli elementi usando lo stesso metodo, e, quindi , si realizza la fusione dei due array ordinato. La funzione di fusione utilizzata dal programma di mergesort è quella descritta sopra. In questo caso infatti la funzione di fusione ope come parametri di ingresso tre indici inf, med e sup. Si ha che: - le chiavi comprese tra inf e med sono ordinate - le chiavi comprese tra med+1 e sup sono ordinate - dopo l’esecuzione della funzione fusione le chiavi tra inf e sup dev Di seguito è mostrata l’implementazione in C dell’algoritmo di Mergesort modificata. #include<stdio.h> #define N 5 /* dimensione dell'array */ /* definizione elementi array */ typedef char tipo_informazione; typedef struct modello_elem { tipo_informazione inf; int chiave; } elem; Continua 35 void scambia(elem *a,elem *b) { elem app; app=*b; *b=*a; *a=app; } /* esegue la fusione dei due sotto array 1) da vet[i] a vet[k] estremi inclusi 2) da vet[k+1] a vet[j] estremi inclusi il risultato È il sottoarray che v… da vet[i] a vet[j] estremi inclusi */ void merge(int i,int k,int j,elem vet[]) { elem vres[N]; int x=0; int p1=i; int p2=k+1; int pres=0; while (p1<=k && p2<=j) { if (vet[p1].chiave<=vet[p2].chiave) { vres[pres]=vet[p1]; p1=p1+1; } else { vres[pres]=vet[p2]; p2=p2+1; } pres=pres+1; } if (p1<=k) do { vres[pres]=vet[p1]; p1=p1+1; pres=pres+1; } while(p1<=k); else do { vres[pres]=vet[p2]; p2=p2+1; pres=pres+1; Continua 36 } while(p2<=j); for(x=0;x<=j-i;x++) { vet[x+i]=vres[x]; } } /* esegue l'ordinamento mergesort sul vettore z limitatamente al sottoarray che v… da z[i] a z[j] estremi inclusi. n È la dimensione dell'array */ void msort(int i,int j,elem z[],int n) { int k,app; if (i+1==j) if (z[i].chiave>z[j].chiave) scambia(&z[i],&z[j]); if (i+1<j) { k=(i+j)/2; msort(i,k,z,n); msort(k+1,j,z,n); merge(i,k,j,z); } } main() { int k=0; elem vet[N]; /* riempimento array */ vet[0].chiave=2; vet[0].inf='a'; vet[1].chiave=45; vet[1].inf='c'; vet[2].chiave=8; vet[2].inf='b'; vet[3].chiave=99; vet[3].inf='e'; vet[4].chiave=67; vet[4].inf='d'; /* ordinamento array */ msort(0,N-1,vet,N); /* display array risultato */ for(k=0;k<N;k++) printf("%c\n",vet[k].inf); } 37 Riferimenti 1. “Fondamenti di programmazione dei calcolatori elettronici”: C.Batini, L.Carlucci Aiello, M.Lenzerini, A.Marchetti Spaccamela, A Franco Angeli 2. “Dal linguaggio Pascal al linguaggio C” G.Di Battista, F.Vargiu Masson 38