Sei sulla pagina 1di 140

Appunti di 02MNONZ - Algoritmi e Programmazione 1

Studente: MACI Samuele Docente: CAMURATI Paolo Enrico


Politecnico di Torino Preside III Facoltà - Torino
Anno Accademico 2011/2012 paolo.camurati@polito.it
samuele.maci@studenti.polito.it

Docente: CABODI Giampiero Docente: NOCCO Sergio


Prof. Associato Confermato Collaboratori coordinati e continuativi
giampiero.cabodi@polito.it Collaborazione scientifica per ricerche
sergio.nocco@polito.it

Ultima revisione: 16 marzo 2012

1
Il presente quaderno di appunti e stato redatto completamente con l’applicativo TEXnicCenter
Indice

I Teoria 3
1 Gli Algortmi 5
1.1 Problemi Decisionali . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.1.1 Problemi trattabili/intrattabili . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.2 Ricerche su i vettori . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.2.1 Ricerca lineare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.2.2 Ricerca binaria o dicotomica . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.3 Algoritmi di ordinamento . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
1.3.1 Classificazione . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
1.3.2 Insertion Sort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
1.3.3 Bubble Sort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
1.3.4 Selection Sort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
1.3.5 Counting Sort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
1.4 Analisi della complessità . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
1.4.1 Classificazione degli algoritmi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
1.4.2 Analisi asintotica di capo peggiore . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
1.4.3 Notazione asintotica . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
1.5 Online Connectivity . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
1.5.1 Quick Find . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
1.5.2 Quick Union . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
1.6 Matematica discreta: grafi e alberi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
1.6.1 Grafo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
1.6.2 Incidenza e adiacenza . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
1.6.3 Grado di un vertice . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
1.6.4 Cammini e raggiungibilità . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
1.6.5 Connessione nei grafi non orientati . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
1.6.6 Connessione nei grafi orientati . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
1.6.7 Grafi densi o sparsi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
1.6.8 Grafo pesato . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
1.6.9 Alberi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
1.7 La ricorsione . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
1.7.1 Paradigma Divide et Impera . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
1.7.2 Analisi di complessità di alcuni algoritmi . . . . . . . . . . . . . . . . . . . . . . . 25
1.7.3 Torri di Hanoi, gioco matematico risolto con il dividi et impera . . . . . . . . . . . 26
1.7.4 Backtracking . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
1.8 ADT: Heap, code a priorità . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
1.8.1 Procedura di insert . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
1.8.2 Procedura di heapify . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
1.8.3 Procedura di BuildHeap . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
1.8.4 HeapSort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
1.9 ADT: Tabella di simboli (Symbol Table) . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
1.9.1 Operazioni di un ADT SystemTable . . . . . . . . . . . . . . . . . . . . . . . . . . 30
1.9.2 Strutture dati di un ADT SystemTable . . . . . . . . . . . . . . . . . . . . . . . . 30
1.9.3 Ricerca in un ADT SystemTable . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
1.10 Alberi binari di ricerca (BST) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
1.10.1 Operazioni in un BST . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
1.10.2 Complessità in un BST . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
1.10.3 Operazioni utili . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
1.10.4 Implementazione . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33

I
II Programmazione 35
2 Problem Solving elementare 37
2.1 Problem Solving elementare su dati scalari . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
2.1.1 Problem Solving e algoritmi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
2.1.2 Strategie di soluzione . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
2.1.3 Classificazione dei problemi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38

3 Puntatori e allocazione dinamica 45


3.1 Tipo di dato puntatore . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
3.1.1 Puntatore come riferimento . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
3.2 Definizione e operazione sui puntatori . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
3.2.1 Definizione di un puntatore . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
3.2.2 Variabili e operazioni sui puntatori . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
3.2.3 Confronto tra puntatori . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
3.2.4 Aritmetica dei puntatori . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
3.3 Implementazioni di strlen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
3.4 Pasaggio parametri by reference . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
3.4.1 Vettore come parametro di una funzione . . . . . . . . . . . . . . . . . . . . . . . . 48
3.5 Puntatori a strutture . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
3.5.1 Strutture ricorsive . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
3.6 Allocazione dinamica della memoria . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
3.6.1 Malloc . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
3.7 Strutture Astratte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
3.7.1 Liste . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51

III Laboratorio 53
4 Esercitazione 1 55
4.1 Esercizio n. 1: manipolazione di una matrice - I . . . . . . . . . . . . . . . . . . . . . . . . 55
4.2 Esercizio n. 2 - Manipolazione di una matrice - II . . . . . . . . . . . . . . . . . . . . . . . 57
4.3 Esercizio n. 3: riformattazione di un testo . . . . . . . . . . . . . . . . . . . . . . . . . . . 60

5 Esercitazione 2 63
5.1 Esercizio n. 1: decompressione di un testo . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
5.2 Esercizio n. 2: stringhe periodiche. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
5.3 Esercizio n. 3: voli aerei . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65

6 Esercitazione 3 69
6.1 Esercizio n. 1: confronto tra algoritmi di ordinamento . . . . . . . . . . . . . . . . . . . . 69
6.2 Esercizio n. 2: ordinamento di stringhe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71

7 Esercitazione 4 75
7.1 Esercizio n. 1: occorrenze di parole . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
7.2 Esercizio n. 2: indice analitico . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
7.3 Esercizio n. 3: prodotto di matrici . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79

8 Esercitazione 5 83
8.1 Esercizio n. 1: Generazione di numeri binari. . . . . . . . . . . . . . . . . . . . . . . . . . 83
8.2 Esercizio n. 2: Sviluppo di un sistema del totocalcio. . . . . . . . . . . . . . . . . . . . . . 84
8.3 Esercizio n. 3: calcolo del determinante di una matrice. . . . . . . . . . . . . . . . . . . . 85

9 Esercitazione 6 89
9.1 Esercizio n. 1: Punti del piano. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
9.2 Esercizio n. 2: Ricerche dicotomiche. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
9.3 Esercizio n. 3: Confronto tra algoritmi di ordinamento. . . . . . . . . . . . . . . . . . . . 97

10 Esercitazione 7 103
10.1 Esercizio n. 1: gestione di strutture FIFO. . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
10.2 Esercizio n. 2: gestione di strutture LIFO. . . . . . . . . . . . . . . . . . . . . . . . . . . . 109

II
11 Esercitazione 8 117
11.1 Esercizio n. 1: gestione di una coda prioritaria - I . . . . . . . . . . . . . . . . . . . . . . . 117
11.2 Esercizio n. 2: gestione di una coda prioritaria - II . . . . . . . . . . . . . . . . . . . . . . 126

III
IV
Elenco dei Sorgenti

1.1 Ricerca lineare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6


1.2 Ricerca binaria . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.3 InsertionSort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
1.4 BubbleSort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
1.5 BubbleSort ottimizzato con flag . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
1.6 SelectionSort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
1.7 CountingSort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
1.8 Quick Find . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
1.9 Quick Union . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
1.10 Fattoriale ricorsivo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
1.11 Fibonacci ricorsivo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
1.12 Massimo comun divisore ricorsivo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
1.13 Valutazione di espressioni notazione polacca . . . . . . . . . . . . . . . . . . . . . . . . . . 21
1.14 Ricerca binaria ricorsiva . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
1.15 Massimo di un vettore ricorsivo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
1.16 Mergesort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
1.17 Quick Sort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
1.18 Torri di Hanoi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
1.19 Procedura di insert in un heap . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
1.20 Procedura di heapify in un heap . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
2.1 Calcolo della ridotta n-esima di una serie armonica . . . . . . . . . . . . . . . . . . . . . . 38
2.2 Visualizzazione della codifica binaria di un intero . . . . . . . . . . . . . . . . . . . . . . . 39
2.3 Conversione da base b0 a base b1 (iterativo) . . . . . . . . . . . . . . . . . . . . . . . . . . 39
2.4 Codifica di un file . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
2.5 Grafico di una parabola su una interfaccia a carattere . . . . . . . . . . . . . . . . . . . . 41
2.6 Riformattazione testi − mediante sottostringhe . . . . . . . . . . . . . . . . . . . . . . . . 42
2.7 Verifica ordine alfabetico file . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
3.1 Esempio di uso di void* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
3.2 Aritmetica dei puntatori . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
3.3 strlen - I . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
3.4 strlen - II . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
3.5 strlen - III . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
3.6 strlen - IV . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
3.7 Vettore come parametro di una funzione - I . . . . . . . . . . . . . . . . . . . . . . . . . . 48
3.8 Vettore come parametro di una funzione - II . . . . . . . . . . . . . . . . . . . . . . . . . . 48
3.9 strcmp - I . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
3.10 strcmp - II . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
3.11 strncmp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
3.12 strstr . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
3.13 Esempio Struttura ricorsiva . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
3.14 Esempio di Giuseppe Flavio . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
3.15 Prototipo malloc . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
4.1 Manipolazione di una matrice - I . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
4.2 Manipolazione di una matrice - II . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
4.3 Manipolazione di una matrice - III . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
4.4 Riformattazione di un testo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
5.1 Decompressione di un testo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
5.2 Stringhe periodiche . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
5.3 Voli aerei . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
6.1 Confronto degli algoritmi di ordinamento . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
6.2 Ordinamento di stringhe con allocazione statica . . . . . . . . . . . . . . . . . . . . . . . . 72

V
6.3 Ordinamento di stringhe con allocazione dinamica . . . . . . . . . . . . . . . . . . . . . . 73
7.1 Occorrenza di parole . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
7.2 Occorrenze parole con indice analitico . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
7.3 Prodotto di matrici . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
8.1 Generazione di numeri binari . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
8.2 Sviluppo di un sistema del totocalcio . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
8.3 Determinante di una matrice . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
9.1 Punti del piano (client.c) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
9.2 Punti del piano (point.h) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
9.3 Punti del piano (point.c) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
9.4 Ricerche dicotomiche (client.c) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
9.5 Ricerche dicotomiche (item.h) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
9.6 Ricerche dicotomiche (item.c) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
9.7 Confronto tra algoritmi di ordinamento (client.c) . . . . . . . . . . . . . . . . . . . . . . . 97
9.8 Confronto tra algoritmi di ordinamento (item.h) . . . . . . . . . . . . . . . . . . . . . . . 98
9.9 Confronto tra algoritmi di ordinamento (item.c) . . . . . . . . . . . . . . . . . . . . . . . . 99
9.10 Confronto tra algoritmi di ordinamento (sort.h) . . . . . . . . . . . . . . . . . . . . . . . . 99
9.11 Confronto tra algoritmi di ordinamento (sort.c) . . . . . . . . . . . . . . . . . . . . . . . . 100
10.1 Queue (client.c) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
10.2 Queue (item.h) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106
10.3 Queue (item.c) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106
10.4 Queue (queue.h) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106
10.5 Queue (queue.c) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
10.6 Queue (queue.h) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
10.7 Queue (queue.c) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
10.8 Stack (client.c) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
10.9 Stack (item.h) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112
10.10Stack (item.c) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
10.11Stack (stack.h) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
10.12Stack (stack.c) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
10.13Stack (stack.h) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115
10.14Stack (stack.c) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115
11.1 Coda prioritaria - I (client.c) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117
11.2 Coda prioritaria - I (job.h) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119
11.3 Coda prioritaria - I (job.c) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119
11.4 Coda prioritaria - I (ITEM.h) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120
11.5 Coda prioritaria - I (ITEM.c) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
11.6 Coda prioritaria - I (HEAP.h) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
11.7 Coda prioritaria - I (HEAP.c) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
11.8 Coda prioritaria - I (PriorityQueue.h) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123
11.9 Coda prioritaria - I (PriorityQueue.c) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123
11.10Coda prioritaria - I (LIST.h) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123
11.11Coda prioritaria - I (LIST.c) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124
11.12Coda prioritaria - I (PriorityQueue.h) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125
11.13Coda prioritaria - I (PriorityQueue.c) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125
11.14Coda prioritaria - II (client.c) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126
11.15Coda prioritaria - II (Hour.h) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129
11.16Coda prioritaria - II (Hour.c) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129
11.17Coda prioritaria - II (job.h) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130
11.18Coda prioritaria - II (job.c) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131
Presentazione

Il corso è tenuto dal Prof. Paolo Enrico Camurati, Prof. Giampiero Cabodi e dal Prof. Sergio Nocco.
I laboratori inizieranno nella settimana del 10 ottobre 2011 e sono nelle date:
Squadra 1 Giovedı̀ dalle ore 14:30 alle ore 16:00
Squadra 2 Giovedı̀ dalle ore 16:00 alle ore 15:30
Squadra 3 Venerdı̀ dalle ore 11:30 alle ore 13:00
Squadra 4 Venerdı̀ dalle ore 13:00 alle ore 14:30
I testi consigliati sono:
• Sedgewick Robert, Algoritmi in C, Pearson, ISBN 8871921518
• Deitel & Deitel, Corso completo di programmazione in C, Apogeo, ISBN 8850326335
L’esame consiste in due parti, scritto e orale. Lo scritto comprenderà una parte teorica (12 punti) e una
parte di programmazione (18 punti). Durante lo scritto sarà possibile consultare manuali di C come il
Deitel & Deitel oppure il Kernighan & Ritchie, inoltre, al termine dello scritto bisogna portar copia del
programa per correggerlo e inviarlo corretto al docente responsabile del corso. Il voto dell’esame orale
non farà media in quanto sarà già comprensivo della valutazione dello scritto.
Per consulenze non sono previsti degli orari esatti, ma basta mandare una mail e sarà fissato un appun-
tamento.

1
2
Parte I

Teoria

3
Capitolo 1

Gli Algortmi

Un algoritmo è come una ricetta, è composto da una sequenza finita di istruzioni elementari.
L’algoritmo ha l’obiettivo di risolvere un problema partendo dai dati di input, ottenendo quindi i dati di
output.
Ogni singola istruzione ha un suo significato, che non può variare in seguito.
Un algoritmo, per sua definizione, deve avere un limite superiore, è certo quindi che un algoritmo abbia
una terminazione (anche se non in tempi accettabili).
Si usano gli algoritmi per essere portati all’interno di elaboratori elettronici, in modo da poter aumentare
la velocità e quindi poter anche aumentare la mole di dati da poter trattare in tempi ragionevoli.
Un super-computer comunque non può sostituire un buon algoritmo.

1.1 Problemi Decisionali


I problemi decisionali sono dei problemi che ammettono una risposta binaria, una risposta del tipo SI/-
NO.
Vi sono anche dei problemi di ottimizzazione, problemi in cui ogni soluzione è una funzione di costo e
bisogna fornire solo la risposta la cui funzione di costo è minima.
Un problema si dice decidibile se esiste un algoritmo che li risolve.
Vi sono anche problemi definiti come indecidibili se non vi è un algoritmo che li risolve.
Turing, nel 1937, concettualizzo il problema della terminazione di un algoritmo, egli sostiene che non
si può sapere se con un algoritmo arbitrario e dei dati arbitrari l’algoritmo termina. (ad esempio la
Congettura di Goldbach).

1.1.1 Problemi trattabili/intrattabili


I problemi risolvibili possono essere:

• trattabili, è possibile limitare superiormente l’algoritmo se esiste un algoritmo di tipo polinomiale


(con complessità polinomiale).
Un algoritmo polinomiale è un algoritmo che, operando su n dati, data una costante c > 0, termini
in un numero massimo di passi a nc .
Teoricamente sono trattabili algoritmi algoritmi polinomiali con n1 00 passi, praticamente gli algorit-
mi polinomiali con c > 4 non sono risolubili in tempi ragionevoli. I problemi che hanno un’algoritmo
di tipo polinomiale si dicono di classe P.

• Vi sono problemi dedicibili definiti come intrattabili, in quanto sono risolubili con algoritmi polino-
miali. Esempi possono essere quelli con complessità esponenziale (come le Torri di Hanoi).
Esistono problemi per cui si conosce il solo algoritmo esponenziale, ma non è mai stato dimostrato
che non esiste un algoritmo polinomiale.

• Si chiama classe NP la classe di problemi che hanno una verifica di tipo polinomiale ma una risolu-
zione di tipo non polinomiale (potrebbe anche esistere un algoritmo di tipo polinomiale ma ancora
non è stato dimostrato che non esiste).
P ⊆ N P per definizione, ma è possibile che P coincide con N P , è probabile che vi siano problemi
N P che non sono P .

5
Esiste un sottoinsieme di N P denominato come NP-Completo, tali che con una serie di trasforma-
zioni si risolvono in modo polinomaile.
Durante il corso si tratteranno solo problemi di classe P .

1.2 Ricerche su i vettori


1.2.1 Ricerca lineare
Vi è un metodo generale, la ricerca sequenziale/lineare. Tale algoritmo ha un array di dimenzione n, e lo
scansioniamo dall’indice 0 all’indice n − 1.
La ricerca termina in due condizioni:

• se l’elemento viene trovato viene ritornato l’indice i ∈ [0, n − 1]

• se l’elemento non viene trovato viene ritornato un elemento sentinella, che non può essere un indice
i∈/ [0, n − 1] (generalmente −1).


 caso migliore 1 accesso
successo caso peggiore n accessi
n Bisogna carattarizzare l’algoritmo con la sua complessità
caso medio 2 accessi

insuccesso n accessi
nel caso peggiore (una stima conservativa).

1 int ri ce rc aS eq uen zi al e ( int v [] , int l , int r , int k )
// l = estremo sinistro , r = estremo destro , k = chiave
3 {

int i = l ;
5 while (i < r && k != v [ i ])
i ++;
7 if ( i == r )
return -1;
9 return i ;
}
 

Listing 1.1: Ricerca lineare

1.2.2 Ricerca binaria o dicotomica


Si puo effettuare solo su array ordinati per una chiave, che deve essere quella sulla quale si effettua la
ricerca.
Si confronta k, l’elemento da cercare, con l’elemento centrale dell’array, se l’elemento concide si termina,
altrimenti se la chiave è superiore all’elemento centrale si effettua la ricerca dicotomica nel sottovettore
destro, mentre se la chiave è inferiore all’elemento centrale si effettua la ricerca dicotomica nel sottovettore
sinistro. Si termina nel caso in cui l’eleento viene trovato, o nel caso l’array su cui si effettua la ricerca
contiene solo un elemento e non è l’elemento ricercato. Questo algoritmo divide l problema in metà ad
ogni passo, quindi si deduce che quest’algoritmo è di tipo logaritmico.

int ricerca_binaria ( int v [] , int l , int r , int k )
2 {

int c ;
4 while (c <= b )
{
6 c = ( a + b ) /2;
if ( v [ c ]== k )
8 return c ;
else if ( v [ c ] < k )
10 a = c +1;
else
12 b = c -1;
}
14 return -1;
}

6
 

Listing 1.2: Ricerca binaria

1.3 Algoritmi di ordinamento


Sono algoritmi che preso un vettore come parametro di input lo ordinano, secondo una determinata
relazione d’ordine. In generale gli elementi che cercheremo di ordinare saranno dei record/strutture di
dati, nei quali dovrà essere definita una chiave di ordinamento e potrebbero essere definiti dei nuovi dati
satellite (non influenti sull’ordinamento).

1.3.1 Classificazione
Gli algoritmi di ordinamento possono essere classificati secondo metodo diversi:
ordinamento interno se i dati da ordinare sono tutti in memoria centrale, ha accesso diretto ai dati
ordinamento esterno si i dati da ordinare sono, anche non tutti, in memoria esterna (memoria di
massa), ha accesso sequenziale ai dati
ordinamento in loco se per l’ordinamento oltre al vettore saranno necessarie una quantità finita di
memoria che è indipendente dalla dimensione del vettore
ordinamento non in loco se per l’ordinamento saranno necessarie delle locazioni di memoria e tale
quantità è dipendente dalla dimensione del vettore
ordinamento stabile se in fase di ordinamento se sono presenti più elementi con medesima chiave essi
saranno ordinati ma resteranno con lo stesso ordine con cui erano nel vettore
in base alla complessità :
• O(n2 ) sono gli algoritmi di ordinamento più semplici, iterativi e basati sul confronto
esempio di algoritmi di questa classe è Insersion Sort, Selection Sort, Exchange/Bubble Sort
3
• O(n 2 ) un esempio di questo tipo è lo ShellSort (non verrà considerato nel seguito del corso)
• O(n · log(n)) sono gli algoritmi di ordinamento più complessi, ricorsivi e basati sul confronto
esempio di algoritmi di questa classe è Merge Sort, Quick Sort e Heap Sort
• O(n) sono algoritmi di ordinamento non basati sul confronto, sono applicabili solo con delle
ipotesi molto restrittive, sono basati sul calcolo
esempio di algoritmi di questa classe è Counting Sort, Radix Sort, Bin/Bucket Sort
Gli algoritmo di ordinamento con complessità O(n·log(n)) sono gli algoritmi di ordinamento migliori
nel caso in cui sia necessario il confronto, mentre la migliore complessità si ha con complessita O(n)
ma sono poco utilizzabili perchè richiedono delle ipotesi troppo restrittive.
Dimostrazione 1 (Limite inferiore di complessità negli algoritmi basati sul confronto) Un al-
goritmo di ordinamento basato sul confronto ha come operazione elementare il confronto tra ai e aj , sarà
necessario quindi decidere se ai < aj e ai ≥ aj .
Se volessi rappresentare le decisioni in modo grafico si avrebbe la realizzazione di un albero delle decisioni
(che è un albero binario). Se volessi trovare l’ordinamento dei seguenti elementi a1 , a2 , a3 (supponendo
di conoscere le relazioni tra gli elementi) avrei la realizzazione del seguente albero decisionale

a1:a2
<= >

a2:a3 a1:a3
<= > <= >

a1:a2:a3 a1:a3 a2:a1:a3 a2:a3

<= > <= >

a1:a3:a2 a3:a1:a2 a2:a3:a1 a3:a2:a1

7
Nel caso di n dati da ordinare tutte le possibili condizioni di uscita sono tutte le permutazioni che si
possono realizzare con n elementi, cioè n!. Se contassimo il numero di confronti che ci sono tra la radice
e la foglia (permutazione corretta) tale valore è sempre uguale al numero di archi che separano la radice
dalla foglia.
Poichè un albero decisionale è un albero binario, si può supporre che l’albero decisionale sia un albero
binario completo nel quale tutte le permutazioni si possono trovare solo sulle foglie. Detta h l’altezza
massima dell’arco si possono avere al massimo 2n foglie, ma abbiamo supposto che le permutazioni devono
essere sulle foglie, pertanto sarà necessario avere un numero di foglie pari ad almento n!. Si ha quindi

n! ≤ 2h

Utilizzando ora la maggiorazione di Stirling


 n n
n! > n → +∞
e

Si ha quindi
 n n h n n i
2h ≥ n! > ⇒ log2 (2n ) > log2
e e
n
h > n · log2 = n · log2 n − n · log2 e
e
Poichè n · log2 n − n · log2 e ∼ n · log2 n per n → +∞ allora h > n · log2 n. Si ha quindi che la complessità
h sarà sempre maggiore a n · log n, quindi è limitata inferiormente

T (n) = Ω(n · log n)

Si tralascia la base del logaritmo, in quanto il cambio di base necessita solo di una moltiplicazione per
una costante e quindi ininfluente al caso asintotico.

1.3.2 Insertion Sort


Si ha in input un vettore potenzialmente senza relazione d’ordine (ovviamente contenente elementi che
possono avere una certa relazione d’ordine).
Si porta in output il vettore ordinato secondo una determinata relazione d’ordine (si ottiene permutango
gli elementi di partenza).
Il funzionamento è abbastanza semplice e rudimentale. Si introduce nel vettore il primo elemento, si
introduce il secondo inserendolo nella giusta relazione d’ordine rispetto agli altri elementi (eventualmente
traslando tutti gli elementi già presenti). Questo algoritmo ha nel caso migliore n operazioni mentre nel
caso peggiore n2 operazioni.
Questo algoritmo ha un approccio incrementale, in quanto il sottovettore ordinato si espande.
Si termina l’algoritmo quando il sottovettore ordinato ha dimensione uguale a quella del vettore.

1 void InsertionSort ( int A [] , int n )
{
3 int i , j , x ;
for ( i =1; i < n ; i ++)
5 {
x = A [ i ];
7 j = i - 1;
while ( j >= 0 && x < A [ j ])
9 {
A [ j +1] = A [ j ];
11 j - -;
}
13 A [ j +1] = x ;
}
15 }
 

Listing 1.3: InsertionSort

8
1.3.3 Bubble Sort
É un ordinamento che privilegia la posizionazione corretta veloce dei valori alti.
Come struttura dati di ingresso necessita un vettore.
Tale algoritmo ha un approccio incrementale, pertanto il vettore concettualmente sarà suddiviso in due
sottovettori:

• sottovettore sinistro, che contiene gli elementi ancora da ordinare, inizialmente coincide con il
vettore in ingresso

• sottovettore destro, contiene gli elementi già ordinati, inizialmente è vuoto

Si ha la terminazione dell’algoritmo nel caso in cui il sottovettore destro coincide con il vettore di ingresso,
dato ciò che è stato fin’ora esplicitato sarebbe necessario effettuare confronti per n − 1 elementi, dove
n è la dimensione del vettore, è possible anche cercare di ottimizzarlo facendo evitare dei controlli se
sono certamente inutili, una buona ottimizzazione si può avere aggiungendo un flag che mi indica se ci
sono stati scambi; se alla fine dell’iterazione non sono avvenuti scambi allora significa che il vettore è già
ordinato e quindi si può terminare precocemente l’ordinamento.

1 void bubble_sort ( int A [] , int n )
{
3 int i , j , temp ;
for ( i =0; i <n -1; i ++)
5 {
for ( j =0; j <n -1 - i ; j ++)
7 {
if ( A [ j ] > A [ j +1])
9 {
temp = A [ j ];
11 A [ j ] = A [ j +1];
A [ j +1] = temp ;
13 }
}
15 }
}
 

Listing 1.4: BubbleSort


void opt_bubble_sort ( int A [] , int n )
2 {

int i , j , temp , fine ;


4 fine = 0;
for ( i =0; i <n -1 && ! fine ; i ++)
6 {
fine =1;
8 for ( j =0; j <n -1 - i ; j ++)
{
10 if ( A [ j ] > A [ j +1])
{
12 temp = A [ j ];
A [ j ] = A [ j +1];
14 A [ j +1] = temp ;
fine =0;
16 }
}
18 }
}
 

Listing 1.5: BubbleSort ottimizzato con flag

La versione ottimizzata potra dei miglioramenti nel caso medio, ma la complessità asintotica di caso
peggiore resta invariata.

9
Analisi asintotica L’algoritmo è composto da confronti, di costo unitario, e da due cicli:
• ciclo esterno eseguito (n − 1) volte
• ciclo interno viene eseguito, all’i-esima iterazione, (n − 1 − i) volte
Pertanto
n2
T (n) = (n − 1) + (n − 2) + . . . + 2 + 1 = = O(n2 )
2

Caratteristiche è un algoritmo di ordinamento stabile e in loco.

1.3.4 Selection Sort


L’algoritmo consiste nella ricerca del minimo valore presente nel vettore e posizionarlo nella prima posi-
zione, cercare il secondo minimo e metterlo nella seconda posizione e cosı̀ via, il procedimento va eseguito
n volte se n è la dimensione del vettore.
Come struttura dati di ingresso necessita un vettore. Tale algoritmo ha un approccio incrementale,
pertanto il vettore concettualmente sarà suddiviso in due sottovettori:
• sottovettore sinistro, che contiene gli elementi ordinati, inizialmente è vuoto
• sottovettore destro, contiene gli elementi da ordinare, inizialmente coincide con il vettore in ingresso
Si ha la terminazione dell’algoritmo nel caso in cui il sottovettore sinistro coincide con il vettore di
ingresso.

1 void selection_sort ( int A [] , int l , int r )
{
3 int i , j , temp , min ;
for ( i = l ; i < r ; i ++)
5 {
min = i ;
7 for ( j = i +1; j <= r ; j ++)
{
9 if ( A [ j ] < A [ min ])
min = j ;
11 }
temp = A [ i ];
13 A [ i ] = A [ min ];
A [ min ] = temp ;
15 }
}
 

Listing 1.6: SelectionSort

Analisi asintotica L’algoritmo è composto da confronti, di costo unitario, e da due cicli:


• ciclo esterno eseguito (n − 1) volte
• ciclo interno viene eseguito, all’i-esima iterazione, (n − 1 − i) volte
Pertanto
n2
T (n) = (n − 1) + (n − 2) + . . . + 2 + 1 = = O(n2 )
2

Caratteristiche è un algoritmo di ordinamento stabile e in loco.

1.3.5 Counting Sort


É un algoritmo di ordinamento non basato sul confronto ma basato sul calcolo.
Intuitivamente l’algoritmo cerca di determinare il quanti elementi vanno messi prima di un determinato
elemento, pertanto verrà assegnata direttamente all’elemento la posizione corretta (per poter avere alla
fine l’ordinamento).
Tale algoritmo è molto utile in presenza di chiavi ripetute, in quanto altrimenti si avrebbe un uso eccessivo
della memoria (sarebbe utile anche che le chiavi fossero vicine per evitare che ci sia memoria inutilizzata).
Occorre innanzitutto conoscere l’ampiezza dell’intervallo entro il quale va fatto l’ordinamento, occorre
cioè conoscere l’elemento minimo e l’elemento massimo.

10

/**
2 * A è il vettore da ordinare , n è la sua dimensione
* */
4 int min , max , i ;
min = max = A [0];
6 for ( i =1; i < n ; i ++)

{
8 if ( A [ i ] < min )
min = A [ i ];
10 else if ( A [ i ] > max )
max = A [ i ];
12 }
 

Ora è necessario realizzare il vettore delle occorrenze semplici (un vettore che nell’indice contiene il valore
della chiave e come contenuto contiene il numero delle sue occorrenze). Sarà necessario inizializzarlo
inizialmente a 0.

/**
2 * il vettore occ sarà allocato in qualche modo e avrà dimensione pari
* a max - min +1
4 * */

int i ;
6 for ( i =0; i <= max - min ; i ++)

occ [ i ]=0;
8 for ( i =0; i < n ; i ++)

occ [ A [ i ] - min ]++;


 

Ora è necesario calcolare il vettore delle occorrenze multiple, cioè il vettore delle occorrenze dove in
posizione i-esima vi è il numero di occorrenze di tutti gli elementi precedenti

1 /**

* il vettore occ sarà allocato in qualche modo e avrà dimensione pari


3 * a max - min +1
* */
5 int i;
for ( i =1; i < n ; i ++)
7 occ [ i ]+= occ [i -1];
 

Per ottenere finalmente il vettore di uscita si percorre il vettore di ingresso in senso inverso (da n − 1 a
0).

1 /**

* il vettore out sarà allocato in qualche modo e avrà dimensione pari


a n
3 * */

int i ;
5 for ( i =n -1; i >=0; i - -)

out [( occ [ A [ i ]] - -) - min ]= A [ i ];


 


int * counting_sort ( int A [] , int n )
2 {

int i , min , max , * occ , * out ;


4 min = max = A [0];
for ( i =1; i < n ; i ++)
6 {
if ( A [ i ] < min )
8 min = A [ i ];
else if ( A [ i ] > max )
10 max = A [ i ];
}
12 occ = ( int *) calloc ( max - min +1 , sizeof ( int ) ) ;
// l ’ uso di calloc ci assicura di avere la memoria inizializzata a 0

11
14 out = ( int *) malloc ( sizeof ( int ) * n ) ;
for ( i =0; i < n ; i ++)
16 occ [ A [ i ] - min ]++;
for ( i =1; i < n ; i ++)
18 occ [ i ]+= occ [i -1];
for ( i =n -1; i >=0; i - -)
20 out [( occ [ A [ i ]] - -) - min ]= A [ i ];
return out ;
22 }
 

Listing 1.7: CountingSort

1.4 Analisi della complessità


Nella stesura di un buon algoritmo bisogna anche cercare di prevedere la memoria1 e il tempo che viene
utilizzato dall’algoritmo.
La previsione del tempo non consiste nell’immaginare il tempo impiegato in secondi, ma giudicare il tempo
necessario proporzionale al numero di passi e/o operazioni che bisogna svolgere. Per tale previsione è
necessario sapere la dimensione del problema, i dati specifici non sono influenti in quanto si fanno sempre
previsioni conservative.
S(n) → spazio occupato in memoria
T (n) → tempo di esecuzione

1.4.1 Classificazione degli algoritmi


Gli algoritmi si classificano in:

1 costante
log n logaritmico
n lineare
n · log n linearitmico
n2 quadratico
n3 cubico
2n esponenziale

1.4.2 Analisi asintotica di capo peggiore


Si effettua una stima del limite superiore di T (n). Sarebbe necessario effettuare una stima per dimensioni
molto grandi (n → +∞), inoltre si sceglie il caso peggiore in quanto non sarà mai possibile trovare casi
peggiori a quello che è stato esaminato nell’analisi asintotica (stima conservativa).
Si cerca di avere una complessità minima possibile in quanto un buon algoritmo può compensare un
hardware poco prestante.

Esempio 1 (Analisi asintotica nella ricerca lineare) Poichè in una ricerca il caso peggiore si ha
quando non viene trovata la chiave. Poichè abbiamo esaminato precedentemente tale tipo di ricerca e
si è osservato che si effettuano al massimo n passi, pertanto l’algoritmo è cresce linearmente con la
dimensione dei dati.

Esempio 2 (Analisi asintotica nella ricerca dicotomica) Analisi:

• All’inizio della ricerca il vettore ha n elementi


n
• Alla prima iterazione il vettore si riduce a contenere circa 2 elementi
n
• Alla seconda iterazione il vettore si riduce a contenere circa 4 elementi

• ...
n
• Alla i-esima iterazione il vettore si riduce a contenere circa 2i elementi
1 ci si interessa principalmente del consumo di tempo, in quanto la memoria la si può immaginare pressocche infinita

(potendo aggiungere a piacimento memorie esterne come hard disk)

12
L’algoritmo termina nel caso peggiore se la dimensione del vettore è di 1 solo elemento.
n
= 1 ⇒ n = 2i ⇒ i = log2 n
2i
L’algoritmo ha complessità logaritmica.

Esempio 3 (Analisi asintotica dell’ InsersionSort) Il ciclo esterno viene eseguito n − 1 volte. Nel
caso peggiore il ciclo interno scandisce tutto il vettore ordinato (il ciclo è eseguito al massimo n − 1 volte).
Pertanto
n
X n · (n − 1)
T (n) = 1 + 2 + 3 + . . . = i=
i=1
2

É possibile quindi poter affermare che l’InsersionSort abbia complessità quadratica.

1.4.3 Notazione asintotica


Vi sono tre notazioni utilizzate, esse esprimono diverse proprietà.

Definizione 1 (Notazione asintotica O) Si dice che T (n) appartiene a O(g) si a partire da un certo
valore n0 in poi g > T (n).

∃c > 0, ∃n0 > 0/∀n ≥ n0 0 ≤ T (n) ≤ c · g(n)

Definizione 2 (Notazione asintotica Ω) Tale notazione implica che esiste un limite inferiore alla
complessita asintotica di caso peggiore, ciò consente di dimostrare se un algoritmo è il migliore o meno.
Se si riesce a dimostrare che un algoritmo ha limite inferiore significa che non può esistere un altro
algoritmo con complessità migliore.

Se T (n) = am · nm + am−1 · nm−1 + . . . + a0 ⇒ T (n) ∈ Ω(nm )

Definizione 3 (Notazione asintotica Ω) Si usa tale notazione implica che esiste una funzione che
schiaccia sia sopra che sotto, ovviamente con due coefficienti numerici, e ci da informazioni molto più
accurate sull’andamento esatte di T (n).

T (n) ∈ Θ ⇒ ∃c1 , c2 > 0, ∃n0 > 0/∀n ≥ n0 0 ≤ c1 · g(n) ≤ T (n) ≤ c2 · g(n)

1.5 Online Connectivity


Significa che ricevo in ingresso na coppia di interi (p, q), p e q rappresentano i nodi mentre (p, q) rappre-
senta la connessione tra p e q (filo di connessione).
La relazione è commutativa, cioè se p è connesso con q allora anche q è connesso con p, ed è anche
transitiva, cioè se p è connesso con q e q è connesso con t allora p è connesso con t.
Si avrà in output la lista delle connessioni incognite in precedenza.
Si avrà una struttura di dati a grafo, che conterrà i nodi e gli archi (rappresentano nel caso specifico la
connessione).
Le relazioni di connessione possono rappresentare:

• reti di calcolatori (si può ottimizzare la rete riducendo il numero di connessioni ridondanti)

• reti elettriche, dove p e q sono i punti di contatto e (p, q) è il filo.

Immaginiamo di avere una rete con 10 nodi numerati da 0 a 9, con degli archi che rappresentano le
connessioni tra due nodi.
Implicitamente il verso non si esprime, realizzando cosı̀ un arco bidirezionale.
Si cerca di evitare l’uso di tutte le connessioni non necessarie (cioè già note in precedenza).
L’ipotesi fondamentale è che non esiste una struttura dati esistente (e completa a priori), ma di volta in
volta che si aggiunge un collegamento si aggiorna la struttura (l’OnlineConnectivity realizza la struttura
al volo).
Bisognerà istituire due operazioni astrette:

find trova l’insieme a cui appartiene l’oggetto

union unisce due insiemi distinti

13
Se voglio inserire (p, q) devo verificare che f ind(p) 6= f ind(q) in tal condizione bisogna unire gli insiemi
utilizzando union(f ind(p), f ind(q)).
Per implementare tale struttura dati occorre una struttura dati di vettore (struttura presente in tutti i
linguaggi di programmazione).
Occorre decidere se avere:

• find veloce e union lenta (Quick Find)

• find lenta e una union veloce (Quick Union)

1.5.1 Quick Find


Inizialmente si ha un vettore (id) di dimensione pari al numero di nodi.
Inizialmente ∀i ∈ [0, n] ∩ N, id[i] = i (ogni nodo è solo connesso con se stesso).
Se p e q sono connessi allora id[p] = id[q]
leggi la coppia (p, q)
se la coppia non è connessa scandisci il vettore cambiando tutti gli elementi che valgono p in q.

Analisi asintotica Con tale strategia operativa si ha

• find: semplice riferimento a una cella del vettore O(1)

• union: scansione del vettore per cambiare gli ellementi che valgono p in q, O(n)

Complessivamente il numero di operazioni è legato a num_coppie * dim_vettore


Il caso peggiore si ha se si effettuano solo delle union.

# include < stdio .h >
2 # define N 10000
main ()
4 {

int i , t , p , q , id [ N ];
6 for ( i =0; i < N ; i ++)
id [ i ] = i ;
8 printf ( " Input pair p q : " ) ;
while ( scanf ( " % d % d " , &p , & q ) ==2)
10 {
if ( id [ p ] == id [ q ])
12 printf ( " pair % d % d already connected \ n " , p , q ) ;
else
14 {
for ( t = id [ p ] , i = 0; i < N ; i ++)
16 if ( id [ i ] == t )
id [ i ] = id [ q ];
18 printf ( " pair % d % d not yet connected \ n " , p , q ) ;
}
20 printf ( " Input pair p q : " ) ;
}
22 system ( " pause " ) ;
}
 

Listing 1.8: Quick Find

1.5.2 Quick Union


Utilizza una strategia che favorisce la union.
In questa stratecia si utilizza una catena (per accedere al rappresentante del gruppo occorre percorrere
la catena finchè non si arriva al capogruppo, cioè quello che riferisce a se stesso). In questa strategia:

• ogni oggetto punta all’oggetto a cui è connesso o a se stesso.


Indicheremo con (id[i])* = id[id[id...id[i]..]]

• inizialmente tutti gli elementi puntano a se stessi, cioè id[i]=i

14
Si forma una catena di riferimenti (tale catena termina quando id[i]=i.
leggi la coppia (p, q)
calcola la testa della catena di p e di q, cioè (id[p])∗ e (id[q])∗. se (id[p])∗ è diverso da (id[q])∗ allora
aggiungi il nuovo riferimento id[(id[p])*] = (id[p])*

Analisi asintotica Con tale strategia operativa si ha

• find: percorre una catena che al massimo può essere lunga quanto il vettore, O(n)

• union: assegnazione, O(1)

Complessivamente il numero di operazioni è legato a num_coppie * leh_massima


Il caso peggiore si ha se si effettuano solo delle find.

# include < stdio .h >
2 # define N 10000
main ()
4 {

int i , j , p , q , id [ N ];
6 for ( i =0; i < N ; i ++)
id [ i ] = i ;
8 printf ( " Input pair p q : " ) ;
while ( scanf ( " % d % d " , &p , & q ) ==2)
10 {
for ( i = p ; i != id [ i ]; i = id [ i ]) ;
12 for ( j = q ; j != id [ j ]; j = id [ j ]) ;
if ( i == j )
14 printf ( " pair % d % d already connected \ n " , p , q ) ;
else
16 {
id [ i ] = j ;
18 printf ( " pair % d % d not yet connected \ n " , p , q ) ;
}
20 printf ( " Input pair p q : " ) ;
}
22 system ( " pause " ) ;
}
 

Listing 1.9: Quick Union

1.6 Matematica discreta: grafi e alberi


Si introdurra un discorso fatto in modo informale secondo una serie di formalismi e di definizioni.

1.6.1 Grafo
Informalmente un grafico è stato usato nella trattazione della Online Connectivity.
Il grafo è utilizzato in una grandissima quantità di algoritmi ed è una struttura dati fondamentale per
l’informatica.

Definizione 4 (Grafo) Si definisce grafo una coppia di insiemi:

• l’insieme dei vertici, V

• l’insieme degli archi o entità che introducono una relazione binaria tra due vertici, E

G = (V, E)

Definizione 5 (Grafo non orientato) Si definisce grafo non orientato un grafo nel quale se (u, v) ∈ E
allora esiste una relazione tra u e v e anche tra v e u

15
A B C

D E F
Figura 1.1: Esempio di grafo non orientato

Esempio 4
G = (v, e)

v = {A, B, C, D, E, F }

e = {(A, B), (B, D), (B, E), (C, F )}

Definizione 6 (Grafo orientato) Si definisce grafo orientato un grafo nel quale se (u, v) ∈ E allora
esiste una relazione tra u e v allora non è detto che esiste una relazione tra v e u

A B C

D E F
Figura 1.2: Esempio di grafo orientato

Esempio 5
G = (v, e)

v = {A, B, C, D, E, F }

e = {(A, B), (B, B), (B, D), (B, E), (D, A), (D, E), (E, D), (F, C)}

Formalmente un grafo si evidenzia dalle loro entità.


Se il grafo è non orientato
{u, v} ∈ E e u, v ∈ V
Se il grafo è orientato
(u, v) ∈ E e u, v ∈ V
Nell’uso abituale però ci si limita a definire il tipo di grafo e definire le entità utilizzando sempre parentesi
tonde, bisogna fare attenzione al fatto che non si possono definire cappi all’interno di grafi non orientati.

1.6.2 Incidenza e adiacenza


Un arco (a, b) si dice che:

• insiste/incide sul vertice a

• insiste/incide sul vertice b

• insiste/incide sui vertici a e b

I vertici a e b sono adiacenti se sono messi in relazione da un’entità, cioè se

a adiacente b ⇔ (a, b) ∈ E

16
1.6.3 Grado di un vertice
Definizione 7 (Grado di un vertice) Si definisce grado di un vertice il numero di entità incidenti del
nodo.
degree(A)

Nel caso in cui si ha un grafo orientato occorre distinguere il numero di entità entranti e uscenti dal nodo.
Si definisce quindi

in degree(A) out degree(A) degree(A) = in degree(A) + out degree(A)

1.6.4 Cammini e raggiungibilità


Due nodi si dicono raggiungibili se esiste un percorso che collega a con b, magari passando anche attraverso
altri nodi.
Un cammino esiste se esiste una serie di vertici adiacenti di cui il primo è quello di partenza, l’ultimo è
quello di arrivo e per ogni coppia intermedia esiste un arco che li lega (cioè sono adiacenti).

Cammino p : u →p u0 G = (V, E)

cioè
∃(v0 , . . . , vk ) : u = v0 , u0 = vk , ∀i ∈ [1, k] ∩ N, (vi−1 , vi ) ∈ E
Si definisce k la lunghezza del cammino, numero di archi che compongono il cammino.
Si dice che u0 è raggiungibile da u se ∃p : u →p u0 .
Un cammino si dice semplice se ogni nodo, del percorso p, è visitato solo una volta.
Vi sono dei cammini particolari, tali cammini sono i cicli e hanno la caratteristica di iniziare e finire nello
stesso nodo (il ciclo più corto si ha in presenza di un cappio).
Un grafico che non presenta dei cicli si dice aciclico.

Esempio 6 d è raggiungibile da a?
G = (V, E)
V = {a, b, c, d}
E = {(a, b), (b, b), (b, c), (b, d), (c, d), (d, c), (d, a)}
p : a →p d : (a, b), (b, c), (c, d)
Si osserva che p è semplice ed è di lunghezza 3.

1.6.5 Connessione nei grafi non orientati


Definizione 8 (Grafo connesso) Si definisce grafo connesso un grafo nel quale esiste un percorso tra
due nodi qualsiasi dello stesso.
∀vi , vj ∈ V, vi 6= vj ∃p : vi →p vj

Definizione 9 (Componente connessa) Si definisce componente connessa il sottografo connesso mas-


simale.

É importante specificare che sia il sottografo massimale, cioè non esiste un sottografo connesso che
contenga la componente connessa.
I vertici appartenenti alla componente connessi sono tutti mutuamente raggiungibili.

Esempio 7
G = (V, E)
V = {a, b, c, d}
E = {(a, b), (a, c), (a, d), (b, c), (b, d), (c, d)}
{a, c} è un sottografo connesso. {a, b, c} è un sottografo connesso, ma {a, b} ⊂ {a, b, c}. {a, b, c, d} è un
sottografo connesso, ma {a, b, c} ⊂ {a, b, c, d}. Poichè {a, b, c, d} = V allora {a, b, c, d} è la componente
connessa.

In presenza di più componenti connesse è come se si avesse una sorta di sottorete.

17
1.6.6 Connessione nei grafi orientati
Definizione 10 (Grafo fortemente connesso) Si definisce grafo fortemente connesso un grafo nel
quale esiste un percorso tra due nodi qualsiasi dello stesso, in entrambi i sensi di percorrenza.

∀vi , vj ∈ V, vi 6= vj ∃p : vi →p vj e ∃p0 : vj →p0 vi

Definizione 11 (Componente fortemente connessa) Si definisce componente fortemente connessa


il sottografo fortemente connesso massimale.

Esempio 8
G = (V, E)
V = {a, b, c, d, e, f, g, h}
E = {(a, b), (b, c), (b, e), (b, f ), (c, d), (c, g), (d, c), (d, h), (e, a), (e, f ), (f, g), (g, f ), (g, h)}
In G sono presenti quattro componenti connesse:

• C1 = {a, b, e}

• C2 = {c, d}

• C3 = {f, g}

• C4 = {h}

Se un vertice, nel caso specifico h, non è mutuamente connesso a nessun altro vertice allora esso
rappresenta un’unica componente fortemente connsessa.

1.6.7 Grafi densi o sparsi


Tale definizione si basa sul rapporto tra le cardinalità2 tra l’insieme dei vertici e l’insieme delle entità.

Definizione 12 (Grafo denso) Un grafo si dice denso se la cardinalità di E è circa uguale al quadrato
della cardinalità di V .
|E| ' |V |2

Definizione 13 (Grafo sparso) Un grafo si dice sparso se la cardinalità di E è circa uguale al quadrato
della cardinalità di V .
|E|  |V |2

Definizione 14 (Grafo completo) Un grafo si dice completo ogni vertice vertice è collegato con tutti
gli altri nodi.
Un grafo completo è il grafo più denso che si può realizzare.
Un grafo completo ha
 
|V | |V |! |V | · (|V | − 1)
|E| = = = nel caso di grafo non orientato
2 2! · (|V | − 2)! 2
 
|V | |V |!
|E| = 2 · =2· = |V | · (|V | − 1) nel caso di grafo orientato
2 2! · (|V | − 2)!

1.6.8 Grafo pesato


Il grafo è una struttura basilare per poter rappresentare moltissimi modelli.
Un grafo potrebbe, ad esempio, essere il modello di una autostrada, ma conoscere il numero di tratte
che bisogna percorrere per arrivare da una città ad un’altre non è certamente sufficiente, sarebbe meglio
conoscere i chilometri totali che bisogna percorrere. A questo proposito si può aggiungere ad ogni arco
un’informazione (il peso) che indica nell’esempio proposto il numero di chilometri della tratta.

Definizione 15 (Grafo pesato) Si definisce grafo pesato un grafo nel quale esiste una funzione peso

w(u, v) = peso dell0 arco

Generalmente il peso è un numero che è relazionato al tipo di informazioni che deve dare tale valore.
2 la cardinalità indica il numero di elementi contenuti dall’insieme, la cardinalità di A si indica con |A|

18
1.6.9 Alberi
Definizione 16 (Albero non radicato) Si definisce albero non radicato un grafo non orientato, con-
nesso e aciclico.
Definizione 17 (Albero, foreste) Si definisce foresta un grafo non orientato e aciclico.

Proprietà
G = (V, E) |E| numero degli archi |V | numero dei nodi
Se G è un albero non radicato allora
• tutte le coppie dei nodi sono connesse da un cammino semplice e unico
• rimuovendo un qualsiasi arco di G comporta la disconnessione del grafo
• si ha |E| = |V | − 1, poichè G è connesso ed è aciclico
• aggiungendo un nuovo arco tra gli archi presenti allora ottiene un ciclo
• esiste r ∈ E detto radice
• relazione di parentela
– y è un antenato di x se y ∈ p con p : x →p r
– y è un antenato proprio di x, se y è un antenato di x e x 6= y
– x è padre/figlio di y se x e y sono adiacenti
– r non ha padri
– x (foglia) non ha figli
Definizione 18 (Foglia) Si definisce foglia un nodo che non ha nodi adiacenti (nodi diversi dal nodo
padre).
Definizione 19 (Grado di un albero) Si definisce grado di un albero il numero minimo dei figli am-
messi da un qualsiasi nodo dell’albero.
Definizione 20 (Profondità di un nodo) Si definisce profondità di un nodo la lunghezza del cammino
che congiunge il nodo alla radice dell’albero.
Definizione 21 (Altezza di un albero) Si definisce altezza di un albero la lunghezza massima del
cammino che congiunge un nodo alla radice.
Altezza(A) = max profondità(E)
x∈E

Casi particolari
Una classe particolare di alberi sono gli alberi binari, cioè gli alberi con grado due.
Definizione 22 (Albero binario) Si definisce albero binario, con definizione ricorsiva, un albero con
grado due composto da:
• radice
• sottoalbero sinistro (albero binario)
• sottoalbero destro (albero binario)
Definizione 23 (Albero binario completo) Si definisce albero binario completo un albero binario in
cui tutti i percorsi radice-foglia hanno la stessa lunghezza, h e ogni nodo (eccetto le foglie ha grado due).
Detta h l’altezza dell’albero allora
• l’albero ha 2h foglie
h
X
• l’albero ha 2i = 2h+1 − 1 nodi
i=0

Definizione 24 (Albero bilanciato) Si definisce albero bilanciato un albero in cui tutti i cammini
radice foglia hanno la stessa lunghezza.
Se un albero è completo allora è bilanciato.
Definizione 25 (Albero quasi bilanciato) Si definisce albero quasi bilanciato un albero in cui tutti i
cammini radice foglia hanno circa la stessa lunghezza, può differire di al massimo 1.

19
1.7 La ricorsione
Definizione 26 (Procedura ricorsiva) Si definisce ricorsiva una procedura che è definita mediante la
chiamata diretta o indiretta (cioè con la chiamata ad un altra procedura che richiama la prima procedura)
alla procedura stessa.
Con la ricorsione si ha la partizione di un problema grande in un numero finito di problemi della stessa
natura3 , ma più semplici (si riduce la dimensione del problema).
La ricorsione consente di avere soluzioni più compatte ed eleganti.
Nel caso di procedure ricorsive è sempre necessario, per garantirsi la terminazione della stessa, una
condizione di terminazione cioè una soluzione ad un problema banale (che non può essere ulteriormente
separato).
Esempio 9 (Fattoriale) 
n · (n − 1)! n≥1
n! =
1 n=0

1 long fattoriale ( int n )
{
3 if ( n ==0)
return 1;
5 else
return n * fattoriale (n -1) ;
7 }
 

Listing 1.10: Fattoriale ricorsivo

Esempio 10 (Serie di Fibonacci ricorsva)



F IBn−1 + F IBn−2 n > 1
F IBn =
n n≤

1 int fibonacci ( int n )
{
3 if (n >1)
return fibonacci (n -1) + fibonacci (n -2) ;
5 else
return n ;
7 }
 

Listing 1.11: Fibonacci ricorsivo

Esempio 11 (Massimo comun divisore (algoritmo di Euclide) ricorsvo)



gcd(n, m mod n) n 6= 0
gcd(m, n) =
m n=0

1 int gcd ( int m , int n )
{
3 if ( n ==0)
return m ;
5 return gcd (n , m % n ) ;
}
 

Listing 1.12: Massimo comun divisore ricorsivo

Esempio 12 (Valutazione di espressioni in forma prefissa ricorsva) Bisogna istituire una gram-
matica delle forme prefisse (notazione polacca). Una espressione in forma prefissa è definita nel seguente
modo:<op> <exp> <exp>
Si semplifica la forma prefissa utilizzando come operatori i soli operatori di somma e moltiplicazione.
<exp> = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
<op> = + | *
Ma un’espressione può essere definita in modo ricorsivo <exp> = <exp> | <op> <exp> <exp>
Utilizzando la forma prefissa non sono più necessarie le parentesi
3 paradigma del Divide et Impera

20
Esempio: (5 + 12) ∗ 3 ≡ ∗ + 5 12 3

Si salva l’espressione in un vettore e si esegue la funzione eval(..., ...)



int v [];
2 int i;
// v e i sono variabili globali
4 int eval ()
{
6 int x =0;
while ( a [ i ]== ’ ’)
8 i ++;
if ( a [ i ]== ’+ ’)
10 {
i ++;
12 return eval () + eval () ;
}
14 else if ( a [ i ]== ’* ’)
{
16 i ++;
return eval () * eval () ;
18 }
while ( a [ i ] >= ’0 ’ && a [ i ] <= ’9 ’)
20 x = 10* x + a [ i ++] - ’0 ’;
return x ;
22 }
 

Listing 1.13: Valutazione di espressioni notazione polacca

Esempio 13 (Ricerca binaria ricorsiva) Ha la stessa definizione della ricerca binaria, ma ne varia
la chiamata alla funzione, rendendola ricorsiva.

int dicotomica ( int vet [] , int l , int r , int k )
2 {

if (r < l )
4 return -1;
else if ( vet [( l + r ) /2]== el )
6 return ( l + r ) /2;
else if ( vet [( l + r ) /2] > el )
8 return dicotomica ( vet , l , ( l + r ) /2 -1 , k ) ;
else
10 return dicotomica ( vet , ( l + r ) /2+1 , r , k ) ;
}
 

Listing 1.14: Ricerca binaria ricorsiva

Esempio 14 (Massimo in un vettore) Se il vettore ha dimensione 1 allora ho il massimo, mentre


se ha dimensione superiore a 1 divido il vettore in due sottovettori e cerco il massimo in entrambi i
sottovettori.

1 int max ( int a [] , int l , int r )
{
3 int u , v ;
int m = ( l + r ) /2 ;
5 if ( l == r )
return a [ l ];
7 u = max (a , l , m ) ;
v = max (a , m +1 , r ) ;
9 if ( u > v )
return u ;

21
11 else
return v ;
13 }
 

Listing 1.15: Massimo di un vettore ricorsivo

1.7.1 Paradigma Divide et Impera


Tale paradigma di programmazione prevede la divisione di un problema in altri sotto problemi indipen-
denti più piccoli(divide), la risoluzione dei sotto problemi (impera) e la ricombinazione delle risultati
parziali. Per questo tipo di algoritmi si adotta quasi sempre un’implementazione ricorsiva.
Tutti i problemi su cui si adatta bene il paradigma divide et impera hanno lo schema seguente:
Risolvi(problema):
Se il problema è elementare:
return Soluzione = RisolviBanale(problema);
Altrimenti:
Sottoproblemi (1 ÷ a) = Dividi(Problema);
Per ciascun sottoproblema:
Sottosoluzione i = Risolvi(sottoproblema i);
return Soluzione = combina(sottosoluzione i ÷ a);

Equazione delle ricorrenze


Per l’analisi asintotica di caso peggiore si utilizza spesso l’equazione delle ricorrenze
 n
 T (n) = D(n) + a · T
 + C(n) n > c
b

T (n) = Θ(1) n≤c

Analisi asintotica Ricerca binaria

D(n) = Θ(1) ricerca valore medio

C(n) = Θ(1) non si ricombina


a=1
b=2

Analisi asintotica Ricerca Massimo

D(n) = Θ(1) ricerca valore medio

C(n) = Θ(1) non si ricombina


a=2
b=2

Moltiplicazione di due interi

D(n) = Θ(1) ricerca valore medio

C(n) = Θ(n) moltiplicazioni dei gruppi


Nella versione base a = 4, b = 2, mentre nella versione ottimizzata a = 3, b = 2.

Limiti del Dividi et Impera


Poichè per ipotesi tutti i sotto problemi sono indipendenti se si vuol forzare l’uso di tale paradigma allora
si ha uno spreco di risorse.
Se per natura del problema i sottoproblemi sono indipendenti allora il divide et impera funziona molto
bene.

22
Algoritmi ricorsivi di ordinamento

Si tratteranno sostanzialmente il Merge Sort e il Quick Sort.

Merge Sort L’algoritmo prevede di dividere, rispetto il centro, il vettore in due sottovettori
si esegue il mergesorto sul sottovettore destro e sinistro (terminazione se p==r || p>r cioè è ordinato)
Ricombinazione, si fondono i due sottovettori ordinati in un vettore ordinato.

void mergeSort ( int A [] , int l , int r )
2 {

int q ;
4 if (l < r )
{
6 q = ( l + r ) /2;
mergeSort (A , l , q ) ;
8 mergeSort (A , q +1 , r ) ;
Merge (A , l , q , r ) ;
10 }
}
12 void Merge ( int A [] , int l , int q , int r )
{
14 int i , j , k , * B ;
B = ( int *) malloc ( r * sizeof ( int ) ) ;
16 if ( B == NULL )
return ;
18 for ( i =l , j = q +1 , k = l ; i <= q && j <= r ; )
{
20 if ( A [ i ] < A [ j ])
B [ k ++] = A [ i ++];
22 else
B [ k ++] = A [ j ++];
24 }
for ( ; i <= q ; )
26 B [ k ++] = A [ i ++];
for ( ; j <= r ; )
28 B [ k ++] = A [ j ++];
for ( k = l ; k <= r ; k ++)
30 A [ k ] = B [ k ];
}
 

Listing 1.16: Mergesort

Analisi asintotica di caso peggiore Si ipotizza per comodità n = 2k (lo si può porre se si considera
n ≥ 2k )
Dividi: calcola la metà di un vettore D(n) = Θ(1)
Risolvi: risolve i due sottoproblemi di dimensione n2 (a = b = 2)
Test di terminazione semplice Θ(1)
Ricombinazione: basata sul merge C(n) = Θ(n) La dimensione del problema è n = 2k (per comodità,
ma non è vincolante).
All’i-esimo passo, poichè si divide a metà la dimensione del problema, si avrà che la dimensione è 2ni .
La condizione di terminazione è che il problmema ha dimensione 1,
n
= 1 ⇒ i = log2 n
2i

Il costo della fusione è X 


O dim(s) + dim(d) = O(n)

Quindi il merge sort ha complessità


T (n) = O(n · log n)

23
Quick Sort É un algoritmo di ordinamento in loco, non stabile che risulta sperimentalmente più rapido
del mergesort.
In realtà il quick sort è un algoritmo di complessità quadratica nel caso peggiore, mentre nel caso medio
e migliore ha complessita linearitmica. Si utilizza tale algoritmo di ordinamento in quanto nel caso medio
è più veloce del mergesort, non richiede l’allocazione di un vettore aggiuntivo e il caso peggiore (vettore
ordinato in modo crescente o decrescente) è facilmente eliminabile.

Divisione Si partiziona il vettore A[l, ..., r] in due sottovettori:


dato un pivot x

• SX A[l, ..., q] contiene tutti gli elementi minori o uguali a x


4
• DX A[q+1, ..., q] contiene tutti gli elementi maggiori o uguali a x

la scelta del metodo di scelta del pivot è arbitraria. Si sceglierà comunque di avere una regola fissa per
la scelte del pivot, cioè si prende il primo elemento del vettore.

Partition Pivot x = A[l]


Si individuano gli elementi A[i] e A[j] “fuori posto” effettuando un ciclo discendente per j fino a trovare
un elemento minore del pivot e un ciclo ascendente per i fino a trovare un elemento maggiore uguale del
pivot.
Si scambiano gli elementi A[i] e A[j]
Ripeti tale procedura finchè i<j
Si osserva che tale strategia ha complessità T (n) = Θ(n). Si esegue ricorsivamente la partition su
A[l, ..., q] e su A[q+1, ..., r]

void quickSort ( int A [] , int l , int r )
2 {

int q ;
4 if (l < r )
{
6 q = partition (A , l , r ) ;
quickSort (A , l , q ) ;
8 quickSort (A , q +1 , r ) ;
}
10 }

int partition ( int A [] , int l , int r )


12 {

int x , i , j , temp ;
14 x = A [ l ];
i = l -1;
16 j = r +1;
while (i < j )
18 {
while ( A [ - - j ] >= x )
20 ;
while ( A [++ i ] < x )
22 ;
if (i < j )
24 {
temp = A [ i ];
26 A [ i ] = A [ j ];
A [ j ] = temp ;
28 }
}
30 return j ;
}
 

Listing 1.17: Quick Sort

4 si ha anbiguità in quanto è a discrezione del programmatore il posizionamento del pivot nel sottovettore destro o sinistri

24
Ricombinazione non presente, in quanto si ordina direttamente sul vettore di partenza.

Analisi asintotica di caso peggiore L’efficienza dell’algoritmo è dipendente dal bilanciamento delle
partizioni, più le partizioni sono bilanciate tanto più è efficiente.
Il caso peggiore si ha in caso di parizionamento completamente sbilanciato, cioè nel caso in cui si ha un
sottovettore di 1 elemento e un sottovettore di n − 1 elementi.

Equazione delle ricorrenze nel caso peggiore


n
T (n) = a · T + D(n) + C(n)
b

T (n) = T (n − 1) + n + 1 n > 1
T (n) = 1 n=1
Tale equazione la si risolve attraverso l’unfolding, cioè lo sviluppo
n
X n · (n + 1)
T (n) = T (n−1)+(n) = T (n−2)+(n−1)+(n) = T (n−3)+(n−2)+(n−1)+(n) = . . . = i=
i=1
2

T (n) = O(n2 )
Si osserva che nel caso peggiore si ha una complessità quadratica, ma tale caso si può escludere con grande
semplicità.

Caso migliore si ha nel perfetto bilanciamento della partizione, si ha una situazione esattamente
equivalente al mergesort quindi
T (n) = O(n · log n)

Caso medio ci si trova nel caso medio praticamente sempre purchè non si sia nelle condizioni di
caso peggiore. In tale situazione l’algoritmo ha

T (n) = O(n · log n)

Esempio 15 (Quick sort nel caso medio) Sipponiamo che a ogni partition si ha un sottovettore di
9 1
10 · n (sx) e un altro sottovettore di 10 · n (dx).
Discendendo l’albero della ricorsione si osserva che il percorso estremo sinistro è “lungo” log 10
9
(n), mentre
l’albero estremo destro è “lungo” log10 (n) e per ogni passo si eseguono n operazioni per la partition.
Poichè
log 10
9
(n) ≈ log10 (n) n → +∞
allora ha complessità T (n) = O(n · log n)
La scelta del pivot non cambia la complessità, ma può influenzare le costanti che sono presenti all’in-
terno del T (n) reale, tale scelte può essere ottimizzata in base ai campi applicativi, attraverso euristiche
particolari o semplici.

1.7.2 Analisi di complessità di alcuni algoritmi


Ricerca binaria
Un problema di dimensione n viene diviso in un unico problema di dimensione n2 , non si ha ricombinazione
dei risultati.
D(n) = Θ(1) C(n) = Θ(1) a=1 b=2
T n2 + 1 + 1 n > 1 normalizzando T n2 + 1 n > 1
   
T (n) = = T (n) =
1 n=1 1 n=1
L’equazione alle ricorrenze la si può risolvere attraverso l’unfording.
n n
T =T +1
2 4
n n
T =T +1
4 8

25
n
T (n) = 1 + 1 + T
8
Il caso terminale si ha quando n = 1, supponendo n una potenza di 2 (per comodità, ma questo non
cambia il risultato)
log2 n
X
T (n) = i = log2 n + 1
i=0

T (n) = O(log n)

Massimo in un vettore
n
Il problema è scomposto in due problemi di dimensione 2, non si ha ricombinazione.

D(n) = Θ(1) C(n) = Θ(1) a=2 b=2

n
 
2·T 2 +1 n>1
T (n) =
1 n=1
n n
T =2·T +1
2 4
n n
T =2·T +1
4 8
n n
T =1+2+2·T
2 8
Il caso terminale si ha quando n = 1, quindi

log2 n
X 2log2 n+1 − 1
T (n) = 2i = = 2log2 n+1 − 1 = 2 · n − 1
i=0
2−1

T (n) = O(n)

La ricerca del massimo effettuata in modo iterativo ha la stessa complessità dell’algoritmo analizzato, ma
effettuando una sola scansione allora effettua al massimo n passi, quindi poichè ha delle costanti minori
è migliore la ricerca del vettore in modo iterativo.

1.7.3 Torri di Hanoi, gioco matematico risolto con il dividi et impera


Si parte da una configurazione iniziale composta 3 pioli e da 3 dischi aventi diametro decrescente posi-
zionati sul primo piolo.
Si vuol arrivare alla configurazione finale in cui i dischi sono posti sul terzo piolo.
Vi sono solo due regole:

1. Si può accedere solo al disco in cima

2. Su un qualsiasi disco vi possono essere solo dischi con diametro minore.

Risoluzione

• Si spostano n − 1 dischi sul piolo ausiliare, piolo che non è quello di partenza ne quello di arrivo.

• Si sposta l’n-esimo disco dal piolo iniziale a quello finale.

• Si spostano n − 1 dischi dal piolo ausiliare a quello finale.

Si ha terminazione se n = 1, cioè ho uno spostamento elementare.

26
Analisi di complessità
La divisione è un’operazione elementare D(n) = Θ(1)
La ricomposizione è un’operazione elementare C(n) = Θ(1)
La risoluzione è:
• se n > 1 divide il problema in due sottoproblemi di dimensione n − 1
• se n = 1 si ha la condizione di terminazione

2 · T (n − 1) + 1 n>1
T (n) =
1 n=1
T (n − 1) = 1 + 2 · T (n − 2)
T (n − 2) = 1 + 2 · T (n − 3)
n+1
X
T (n) = 1 + 2 + 4 + 8 · T (n − 3) = 2i = 2n − 1
i=0
n
T (n) = O (2 )
Si osserva che questo gioco matematico è certamente decidibile ma con n sufficientemente grande il
problema è intrattabile.

1 void Hanoi ( int n , int src , int dest )
{
3 int aux ;
aux = 3 - ( src + dest ) ;
5 if ( n == 1)
{
7 printf ( " src % d -> dest % d \ n " , src , dest ) ;
return ;
9 }
Hanoi (n -1 , src , aux ) ;
11 printf ( " src % d -> dest % d \ n " , src , dest ) ;
Hanoi (n -1 , aux , dest ) ;
13 }
 

Listing 1.18: Torri di Hanoi

1.7.4 Backtracking
Attraverso il divide et impera si suppone di essere in grado di arrivare alla soluzione terminale.
Nel caso in cui questo non sia vero occorre essere in grado di percorrere esaustivamente lo spazio delle
soluzioni, tale procedura è nota come backtracking.

1.8 ADT: Heap, code a priorità


Un ADT è una struttura dati astratta (Abstract Data Type).

Definizione 27 (Heap) Si definisce un heap un albero binario con le seguenti proprietà


strutturali un heap è un albero binario semi completo, tutti i livelli sono completi tranne eventualmente
l’ultimo che è riempito da sinistra verso destra
funzionali per ogni vertice la chiave del padre è superiore alla chiave dell’elemento considerato.

∀i 6= r key(parent(i)) ≥ key(i)

di conseguenza la chiave nella radice è la chiave massima della struttura.


La definizione di un dato astratto include la realizzazione di una “libreria” che rende disponibili ad un
client delle operazioni per la corretta gestione di tale struttura, in tal modo non è necessario che il client
conosca l’implementazione del dato heap.
Saranno implementate essenzialmente le seguenti funzioni:

27
• Heapify(A, i), funzione che introduce i all’interno dell’heap

• Buildheap(A), funzione che trasforma una struttura dati, ad esempio un vettore, in un heap

• Heapsort(A), funzione di ordinamento ottimizzata per questa tipologia di dato, tale algoritmo sarà
un algoritmo ottimo [O(n · log(n))]

Una coda senza priorità è una struttura di tipo FIFO5 ; mentre una coda prioritaria, come la coda a un
pronto soccorso, è facilmente strutturabile attraverso l’uso di un heap, per tale coda è necessario definire
le seguenti funzioni

• insert(S, x)

• maximum(S), Θ(1)

• extract_maximum(S), O(log(n))

Un heap può essere implementato attraverso una struttura dinamica che ha puntatori all’elemento padre,
figlio destro e figlio sinistro; ma nell’uso pratico si preferisce adottare un vettore allocato dinamicamente;
ma par mantenere le relazioni padre, figli è necessario o avere tre vettori conteneti tutti gli indici oppure
si può avere un grande risparmio della memoria organizzando in un modo specifico la memoria.
Il figlio sinistro di i sarà identificato da LEFT(i) = 2 * i + 1,
il figlio destro di i sarà identificato da RIGHT(i)= 2 * i + 2
il padre di i sarà identificato da PARENT(i)= (i-1)/2
Tale operazioni sono di complessità costante, in quanto sono una somma o delle moltiplicazioni/divisioni
per due, semplici shift a sinistra o destra.
Per questa implementazione è necessario “sovradimensionare” il vetttore, si osserverà che allocando un
livello in eccesso si può sprecare al massimo metà del vettore. Per tale implementazione sarà anche
necessario fornire una funzione che restituisca il numero di elementi precedentemente inseriti nell’heap
heapsize(A).

1.8.1 Procedura di insert


Si aggiunge una foglia all’albero (nell’ultimo livello più a sinistra), nell’implementazione con vettore si
avrà indice pari a heapsize(A), si risale dalla foglia, fino al più alla radice, confrontando la chiave da
inserire con la chiave del padre per far in modo che venga rispettata la proprietà funzionale, se la chiave
da inserire è superiore alla chiave del padre si fa scendere il padre.
La procedura di insert ha una complessità pari al massimo alla lunghezza del cammino foglia radice,
quindi sarà O(log(n))

# define LEFT ( i ) (( i ) *2+1)
2 # define RIGHT ( i ) (( i ) *2+2)
# define PARENT ( i ) ((( i ) -1) /2)
4 void insert ( int A [] , int x , int * heapsize )
{
6 int i ;
* heapsize ++;
8 i = * heapsize ;
while (i >1 && A [ PARENT ( i ) ] < x )
10 {
A [ i ] = A [ PARENT ( i ) ];
12 i = PARENT ( i ) ;
}
14 A[i] = x;
}
 

Listing 1.19: Procedura di insert in un heap

1.8.2 Procedura di heapify


Trasforma i in un heap considerano che LEFT(i) e RIGHT(i) sia un heap. Si assegna ad A[i] il massimo
tra i nodi i, LEFT(i) e RIGHT(i), nel caso il massimo non è i si pone i al posto del nodo radice di
LEFT(i) e RIGHT(i) e si esegue la procedura di heapify LEFT(i) o RIGHT(i).
5 FIFO è equivalente a dire First In First Out, primo inserito primo estratto

28

void swap ( int A [] , int first , int second )
2 {

int t ;
4 t = A [ first ];
A [ first ] = A [ second ];
6 A [ second ] = t ;
}
8 void heapify ( int A [] , int i , int heapsize )
{
10 int l , r , largest ;
l = LEFT ( i ) ;
12 r = RIGHT ( i ) ;
if (l < heapsize && A [ l ] > A [ i ])
14 largest = l ;
else
16 largest = i ;
if (r < heapsize && A [ r ] > A [ largest ])
18 largest = r ;
if ( largest != i )
20 {
swap (A , i , largest ) ;
22 heapify (A , largest , heapsize ) ;
}
24 }
 

Listing 1.20: Procedura di heapify in un heap

1.8.3 Procedura di BuildHeap


La costruzione di un heap può essere eseguita in due modi distinti:

1. inserimento sequenzialmente i dati in una struttura inizialmente vuota, procedura di insert

2. dato un vettore (generalmente non è un heap), lo si trasforma in un vettore (buildheap)


In questo caso non si può usare la heapify in quanto la struttura non ha Left e Right che siano
heap. Poichè per definizione le foglie sono un heap si parte dalle foglie e si esegue heapify fino alla
radice.
La procedura di heapify trasforma un albero binario in un heap:

• le foglie sono un heap


• applicazione di heapify a partire dal padre dell’ultima foglia

Questa procedura ha T (n) = O(n), non lo si può dimostrare intuitivamente ma lo si può fare
analiticamente.

1.8.4 HeapSort
L’ordinamento di un heap è eseguito con i seguenti passi:

1. Trasforma un vettore in un heap (buildheap)

2. Scambia il primo e l’ultimo elemento (swap(0, heapsize-1))

3. Ripristina le proprietà di un heap (heapify)

4. Riduce la dimensione dell’heap di 1 (heapsize--)

5. Ripeti le operazioni sopra finche non si esaurisce l’heap (heapsize==1)

29
1.9 ADT: Tabella di simboli (Symbol Table)
Definizione 28 (Symbol table) Una tabella di simboli è una struttura di dati formata da record con
chiave. Su questa struttura possono essere definite le seguenti operazioni di base:

inserimento di un nuovo record

ricerca di un record con chiave data

eliminazione di un record (non sempre necessaria)

Tale ADT è detto anche dizionario se all’utente è consentito solo fare ricerche.

1.9.1 Operazioni di un ADT SystemTable


inserimento di un nuovo elemento

ricerca di un record con chiave data

cancellazione di un elemento specificato

selezione del k-esimo elemento più piccolo

ordinamento della tabella in base alla chiave

unione di due tabelle

L’algoritmo di ordinamento è dipendente dall’implementazione dell’ADT.

1.9.2 Strutture dati di un ADT SystemTable


Ogni elemento della struttura contiene l’ITEM e un elemento di tipo key che conterrà la chiave (sul tipo
key saranno definite le operazioni di less e eq).
Un ADT SystemTable può essere implementato attraverso le seguenti strutture dati:

• Strutture lineari:

tabelle ad accesso diretto non usate poichè si ha un grande spreco di memoria


array ordinato e non
lista ordinata e non

• Strutture ad albero:

alberi binari di ricerca (BST)


alberi bilanciati che mantengono la complessità di caso peggiore sempre logaritmica (non è
ammesso che l’albero come caso limite sia una lista)
tabelle di hash

1.9.3 Ricerca in un ADT SystemTable


Ricerca indicizzata da una chiave
É effettuabile solo su una tabella ad accesso diretto.
Data la chiave k ∈ U = {0, 1, . . . , M − 1} (considerando tutte le chiavi intere e distinte) può essere usata
come indice di un array st[0, 1, . . . , N − 1] se si cerca key nell’ADT occorre semplicemente verificare
st[key] se è NULL allora non è presente altrimenti si è trovata la chiave.
Poichè l’array deve avere una dimensione finita si salva solo un sottoinsieme di tutte le possibili chiavi
(logicamente contiguo). Questo tipo di ricerca ha il vantaggio che le operazioni di inserimento, cancella-
zione e ricerca hanno costo costante T (n) = O(1); il grosso svantaggio di questo tipo di implementazione
consiste nel fatto che inizializzazione, selezione e ordinamento dipendono dalla dimensione del sottoinsie-
me (T (n) = O(M )), mentre si ha una occupazione di memoria proporzionale alla cardinalità dell’insieme
S(n) = O(|U |) = O(M ) inoltre si ha un enorme spreco di memoria se |K|  M (si nota subito che tale
implementazione non è applicabile nel caso in cui la dimensione dell’insieme è molto grande.

30
Ricerca sequenziale
Tale tipo di ricerca è applicabile solo su un array o una lista concatenata.

Array ordinato :

inserimento con spostamento, di una posizione, degli elementi più grandi (rispetto alla chiave)
ricerca mediante la scansione sequenziale, si termina se la chiave è stata trovata o la chiave corrente
è superiore della chiave da cercare

Array non ordinato :

inserimento in fondo
ricerca mediante la scansione sequenziale completa, si termina se la chiave è stata trovata è
terminata la struttura

Lista ordinata :

inserimento inserimento in ordine


ricerca mediante la scansione sequenziale, si termina se la chiave è stata trovata o la chiave corrente
è superiore della chiave da cercare

Lista non ordinata :

inserimento in testa
ricerca mediante la scansione sequenziale completa, si termina se la chiave è stata trovata è
terminata la struttura

Ricerca binaria
Mantenere l’ordinamento dell’arrai in fase di inserimento ha costo quadratico.
Tale ricerca è vantaggiosa in situazioni statiche:

• array ordinato solo all’inizio

• solo escluse operazioni di inserimento/cancellazione

L’efficienza è legata alla capacità di accesso diretto alle celle dell’array, pertanto è svantaggioso usare liste
concatenate.

1.10 Alberi binari di ricerca (BST)


Un albero binario qualsiasi può essere attraversato secondo tre strategie distinte:

Pre-order x, left(x), right(x)

In-order left(x), x, right(x)

Post-order left(x), right(x), x

L’attraversamento ha complessità T (n) = Θ(n).


É possibile calcolare i due parametri fondamenteli di un albero binario attraverso un calcolo ricorsivo:

numero dei nodi = 1 + numeroDiN odi(lef t) + numeroDiN odi(right)

altezza = 1 + max (altezza(lef t), altezza(right))

Definizione 29 (BST) Si definisce BST (Binary Search Tree) un albero binario con la seguente pror-
pietà funzionale:

∀y ∈ lef t(x), key(y) ≤ key(x)
∀x ∈ BST :
∀z ∈ right(x), key(z) ≥ key(x)

31
1.10.1 Operazioni in un BST
Operazioni definite in questo tipo di struttura dati sono:
search Si effettua una ricerca ricorsiva di un nodo con chiave v.
Si percorre l’albero dalla radice, si termina se v è la chiave della radice dell’albero (search hit) oppure
se l’albero è vuoto (search miss); la ricorsione dal nodo x al sottoalbero sinistro se v < key(x) o al
sottoalbero destro se v > key(x).
minumum si segue fino al sottoalbero sinistro finchè esiste
maximum si segue fino al sottoalbero destro finchè esiste
predecessor il predecessore del nodo x è il nodo con la più grande chiave minore di x.
Se ∃lef t(x) predessor(x) = max(lef t(x)); mentre se 6 ∃lef t(x) predecessor(x) =primo antenato di
x il cui figlio destro è anche antenato di x
successor il successore del nodo x è il nodo con la più piccola chiave maggiore di x.
Se ∃right(x) successor(x) = min(right(x)); mentre se 6 ∃right(x) successor(x) =primo antenato di
x il cui figlio sinistro è anche antenato di x
insert l’insersione di un nodo z con chiave v implica anche il mantenimento delle proprietà funzionali.
Se il BST è vuoto si crea un nuovo BST con la chiave v posta nella radice, mentre se il BST non è
vuoto si opera ricorsivamente (inserendo nel sottoalbero sinistro/destro l’elemento) o iterativamente
(che ricerca la posizione in cui introdurre il nodo e dopo lo introduce).
sort attraversamento del BST in-order (per avere ordinamento crescente)
select selezione della k-esima chiave più piccola; per questa operazione sarebbe conveniente avere un
informazione aggiuntiva sull’albero che contiene il numero di nodi contenuti dal nodo (si potrebbe
anche linearizzare il BST ma si evita attraverso la supposizione precedente).
Se si ricerca la chiave minima si pone k = 0 (t è il numero di nodi radicati nel sottoalbero destro); se
t = k si ritorna la radice dell’albero, se t > k si ritorna la k-esima chiave più piccola del sottoalbero
sinitro, infine se t < k si ricerca la (k − t − 1)-esima chiave più piccolo del sottoalbero destro.
inserimento nella radice inserimento di una nuova chiave nella posizione corretta mediante la proce-
dura di insert e attraverso una serie di rotazioni dell’albero si fa risalire l’elemento lungo l’albero.
delete si elimina un elemento dal BST; se l’elemento è una foglia la si elimina semplicemente, mentre
se il nodo ha un solo sottoalbero si collega il padre con il figlio (si rimuove facilmente); mentre se
il nodo ha entrambi i due sottoalberi occorre prestare molta attenzione, cioè eliminata la radice
di un albero è necessario ricollegare i due sottoalberi (restati disgiunti) ponendo sulla radice il
predecessore o successore della radice (attraverso un’operazione di partition).

1.10.2 Complessità in un BST


In ogni BST le operazioni hanno sempre complessità T (n) = O(h), dove h è l’altezza dell’albero; nei casi
limite si ha che h = log2 (n) se l’albero è completamente bilanciato e h = n se l’albero è completamente
bilanciato pertanto
O(log2 (n)) ≤ T (n) ≤ O(n)
L’unica operazione che in un BST ha sempre complessità lineare è l’attraversamento.

1.10.3 Operazioni utili


Rotazioni
Tale tipo di operazioni modificano localmente la tipologia del BST; ovviamente tali operazioni possono
essere:
rotazione a destra Dato h il puntatore al nodo con chiave y, lef t(h) = x, right(h) = γ, lef t(x) = α e
right(x) = β si operano in modo elementare le seguenti operazioni

lef t(h) = right(x) right(x) = h

rotazione a sinistra Dato h il puntatore al nodo con chiave x, lef t(h) = α, right(h) = y, lef t(y) = β
e right(y) = γ si operano in modo elementare le seguenti operazioni

right(h) = lef t(x) lef t(x) = h

32
Partition
L’operazione consiste nel riorganizzare il BST ponendo nella radice la k-esima chiave più piccola nella
radice. Si può operare in modo ricorsivo, si pone inizialmente il nodo come radice di un albero: se
t (numero di nodi del sottoalbero sinistro) è maggiore di k si effettua la procedura di partition sul
sottoalbero sinistro con chiave k; mentre se t < k si effettua la procedura di partition sul sottoalbero
destra con chiave k = k − t − 1, al termine della ricorsione si ruota l’albero nella direzione opposta alla
direzione presa per la partition.

1.10.4 Implementazione
;;;;;;

33
34
Parte II

Programmazione

35
Capitolo 2

Problem Solving elementare

Prerequisiti del linguaggio C


Il corso di programmazione suppone note le seguenti nozioni, anche se comunque verranno approfondite
durante il corso.
• Tipi di dati primitivi (scalari)
• Operazioni di I/O
• Costrutti condizionali e iterativi
• Vettori e matrici
• Funzioni e passaggio dei parametri
• Stringhe
• File (testuali)
• Strutture
• Puntatori
• Gestione liea di comando
• Direttive al pre-compilatore

2.1 Problem Solving elementare su dati scalari


Si tratteranno problemi:
• numerici
• di cofifica/decodifica
• testuali
• di verifica e filtro di dati
• di ordinamento

2.1.1 Problem Solving e algoritmi


Con il nome di problem solving si indica la soluzione di problemi mediante algoritmi. Tale soluzione si
realizza mediante:
• codifica dei dati
• formalizzazione dell’algoritmo
• stesura dell’algoritmo nel linguaggio C
Per la scelta della struttura dati occorre ragionare sulla natura del problema e sul tipo di algoritmo scelto
(ad esempio i tipi e la quantità di dati necessari).

37
2.1.2 Strategie di soluzione
La maggioranza dei problemi risolti mediante programmi consiste nell’elaborazione di informazioni rice-
vute in input, per produrre risultati di output.
Per la stesura dell’algoritmo consiste nel:

• Individuazione dei dati (dei vari passaggi: input, output e dati intermedi)

• Formalizzazione delle operazioni necessarie per ottenere i risultati intermedi e quelli di output

• Cercare nella propria esperienza personale passaggi noti (un algoritmo è un progetto)

• Cercare metodi risolutivi (magari lavorando su carta) per risolvere il problema, nel caso di assenza
di altri strumenti

Per la scelta delle strutture dati oggorre ragionare su tutte le variabili necessarie durante lo svolgimento
di tutti i passaggi.

2.1.3 Classificazione dei problemi


Problemi numerici
Sono problemi di algebra, geometria, statistica, . . . e sono caratterizzati da:

• dati numerici

• valutazione di espressioni condizionali

• esecuzione di calcoli (magari iterativi)

Hanno il vantaggio di essere spesso già formalizzati in modo non ambiguo, mentre hanno lo svantaggio
che i numeri, all’interno del calcolatore, hanno lunghezza finita e spesso sono arrotondati.

Problemi numerici non iterativi Problemi nei quali occorre semplicemente effettuare una serie di
scelte.

Problemi numerici iterativi Come problemi su successioni/serie o poligoni di n lati. [Problema della
serie armonica] Si richiede di introdurre n. Se n ≤ 0 non si esegue nulla, altrimenti si calcola la ridotta
n-esima
n
X 1
i=1
i


# include < math .h >
2 # include < stdio .h >
in main ( void )
4 {

int n , i ;
6 float M ;
printf ( " Introduci il numero di termini (0 = fine ) \ n " ) ;
8 scanf ( " % d " , & n ) ;
while ( n > 0)
10 {
M = 0.0;
12 for ( i = 1; i <= n ; i ++)
M = M + 1.0/(( float ) i ) ;
14 printf ( " Risultato : % f " , M ) ;
printf ( " Introduci il numero di termini (0 = fine ) \ n " ) ;
16 scanf ( " % d " , & n ) ;
}
18 return 0;
}
 

Listing 2.1: Calcolo della ridotta n-esima di una serie armonica

38
Problemi di codifica/decodifica Problemi nel quale bisogna occuparsi direttamente della codifica:
• numerica (conversione tra basi, operazioni in determinate basi)
• non numerica (decodifica/riconoscimento di codici interni)

Codifiche numeriche In C i dati sono salvati in complemento a 2 o con lo standard IEEE-754


(internamente), mentre sono visualizzati in base 8/10/16 (esternamente).
Le operazioni sono gestite automaticamente in base 2.
Si realizzi una funzione che visualizzi la codifica in binario di un numero inserito.
Preso n > 0 visualizzare la codifica binaria.
Algoritmo Prima iterazione per generare, in p, la potenza più grande minore del numero, tale che 2p ≤ n.
Seconda iterazione per generare il bit di ogni iterazione:
finché p 6= 0:
se n ≥ p: bit = 1, n = n − p
altrimenti bit = 0

void binario ( int n )
2 {

int p ;
4 for ( p = 1; 2* p <= n ; p = p *2) ;
while ( p > 0)
6 {
if ( p <= n )
8 {
printf ( " 1 " ) ;
10 n = n-p;
}
12 else
printf ( " 0 " ) ;
14 p = p /2;
}
16 printf ( " \ n " ) ;
}
 

Listing 2.2: Visualizzazione della codifica binaria di un intero

Si intende realizzare un programma che acquisisca iterativamente numeri interi dalla base iniziale b0 e
li converta e stampi in base b1 . Bisogna terminare l’esecuzione in presenza di inserimento di cifre non
valide.

1 # include < stdio .h >
void converti_base ( int n , int base ) ;
3 int main ( void )
{
5 int b0 , b1 , n = 0 , cifra , fine = 0;
char c ;
7 // menù per l ’ inserimento di b0 , b1
while (! fine )
9 {
scanf ( " % c " , & c ) ;
11 if ( c == ’ ’ || c == ’\ n ’)
{
13 converti_base (n , b1 ) ;
n = 0;
15 }
else
17 {
cifra = c - ’0 ’;
19 if ( cifra >= 0 && cifra <= b0 )
b = b0 * n + cifra ;
21 else

39
fine = 1
23 }
}
25 return 0;
}
27 void converti_base ( int n , int base )
{
29 int i , p ;
for ( p = 1; p * base <= n ; p = p * base ) ;
31 while ( p > 0)
{
33 if ( p <= n )
{
35 printf ( " % d " , n / p ) ;
n = n%p;
37 }
else
39 printf ( " 0 " ) ;
p = p / base ;
41 }
printf ( " \ n " ) ;
43 }
 

Listing 2.3: Conversione da base b0 a base b1 (iterativo)

Codifica di caratteri Generalmente in C i caratteri sono salvati con standard ASCII.


Tali operazoni di codifica sono utili per cifrare, bit di parità, comprimere senza o con perdità, . . .. Si
intende codificare il contenuto di un file di testo, salvarlo su un altro file.
La cifratura consiste in:

• ogni numero n da 0 − 9 viene scritto in complemento a 9 (9 − n)

• ogni codice alfabetico minuscolo viene scambiato in maiuscolo e viene fatto il complemento a 0 z 0 .
ch ←0 A0 + (0 z 0 − ch)

• ogni codice alfabetico maiuscolo viene scambiato in minuscolo e viene fatto il complemento a 0 Z 0 .
ch ←0 a0 + (0 Z 0 − ch)

Algoritmo

• Acquisire i nomi dei file

• Iterazione per lettura, codifica, scrittura



1 # include < stdio .h >
# define MAXRIGA 30
3 int main ( void )
{
5 char ch , nomefile [ MAXRIGA ];
FILE * fin , * fout ;
7 printf ( " nome file in ingresso : " ) ;
scanf ( " % s " , nomefile ) ;
9 fin = fopen ( nomefile , " r " ) ;
if ( fin == NULL )
11 {
printf ( " File % s non trovato .\ n " , nomefile ) ;
13 return 1;
}
15 printf ( " nome file in uscita : " ) ;
scanf ( " % s " , nomefile ) ;
17 fout = fopen ( nomefile , " w " ) ;
if ( fout == NULL )
19 {

40
printf ( " File % s non aperto .\ n " , nomefile ) ;
21 return 1;
}
23 while (! feof ( fin ) )
{
25 fscanf ( fin , " % c " , & ch ) ;
if ( ch >= ’0 ’ && ch <= ’9 ’)
27 ch = ’0 ’ + ( ’9 ’ - ch ) ;
else if ( ch >= ’a ’ && ch <= ’z ’)
29 ch = ’Z ’ + ( ’z ’ - ch ) ;
else if ( ch >= ’A ’ && ch <= ’Z ’)
31 ch = ’z ’ + ( ’Z ’ - ch ) ;
fprintf ( fout , " % c " , ch ) ;
33 }
fclose ( fin ) ;
35 fflush ( fout ) ;
fclise ( fout ) ;
37 return 0;
}
 

Listing 2.4: Codifica di un file

Elaborazione testi carattere per carattere Sono problemi che fanno rferimento a problemi di
elaborazione testi (chiaramente non si possono usare font, colori, dimenzioni, . . . ). Sarebbe utile elaborare
carattere per carattere quando non ci sono già implementate nel linguaggio (ossia per casi particolari,
facendo attenzione ad aggiungere sempre il NULL-terminator 1 .

Costruzione di figure/grafici Si tratta di realizzare uno preudo-grafico, un grafico molto rudi-


mentale. Per ora, la visualizzazione di caratteri a video è sequenziale (esiste in alcune versioni del C la
possibilità di scrivere il carattere in un punto esatto, ma ciò non fa parte del C standard o ANSI-C. Data
una parabola del tipo
y = a · x2 + b · x + c
e si tracci il grafico su un sistema di riferimento cartesiano girato di 90◦ in senso orario.
Si scrivi un programma che:
• Acquisisca da tastiera a, b e c
• Acquisisca un numero intero n (numero di valori) e i valori degli estremi x0 , xn
• Acquisisca da tastiera i valori degli estremi ymin , ymax
• Suddivida l’intervallo [x0 , xn ], calcolare i valori di f (x)
Algoritmo:
• iterazione sui x0 , xn
• calcola il valore della funzione e la disegni

1 # include < stdio .h >
# include < math .h >
3 int main ( void )
{
5 float a ,b ,c ,x , passo , x0 , xn ,y , ymin , ymax ;
int i , j , n ;
7 printf ( " Coefficienti ( a b c ) : " ) ;
scanf ( " % f % f % f " ,&a ,& b ,& c ) ;
9 printf ( " Numero di intervalli : " ) ;
scanf ( " % d " ,& n ) ;
11 printf ( " Intervallo per ascisse : " ) ;
scanf ( " % f % f " ,& x0 ,& xn ) ;
1 carattere speciale che evidenzia al linguaggio C la fine di una stringa, in quanto le stringhe nel C sono semplici vettori

di caratteri

41
13 printf ( " Intervallo per ordinate : " ) ;
scanf ( " % f % f " ,& ymin ,& ymax ) ;
15 passo = ( xn - x0 ) / n ;
for ( i =0; i <= n ; i ++)
17 {
x = x0 + i * passo ;
19 y = a*x*x + b*x + c;
if (y < ymin || y > ymax ) continue ;
21 for ( j = round (y - ymin ) ; j >0; j - -)
printf ( " " ) ;
23 printf ( " *\ n " ) ;
}
25 }
 

Listing 2.5: Grafico di una parabola su una interfaccia a carattere

Elaborazione testi mediante stringhe Funziona se è necessario trovare delle sottostringhe. É


dato un file di testo, visto come un insieme di righe, scomponibili in sottostringhe (di al più 20 caratteri).
Si realizzi una funzione C che, letto il file, ne copi il contenuto in un altro file (i nomi dei file sono ricevuti
come parametri), dopo aver:
• ridotto le sequenze di più spazi ad un solo spazioRidotto le sequenze di più spazi ad un solo spazio
• inserito (in sostituzione di spazi) o eliminato caratteri a-capo (“\n”) in modo tale che ogni riga
abbia la massima lunghezza possibile minore oabbia la massima lunghezza possibile, minore o uguale
a lmax (terzo parametro della funzione)
É possibile operare sia a livello di caratteri che di stringhe. Una possibile soluzione per la gestione di
stringhe parte dal fatto che l’input mediante scanf(‘‘%s’’, ...), permette di isolare in modo automatico
stringhe isolate da spazi.

1 # include < stdio .h >
# include < string .h >
3 void formatta ( char * nin , char * nout , int lmax )
{
5 const int STRLEN =21;
FILE * fin = fopen ( nin , " r " ) ;
7 FILE * fout = fopen ( nout , " w " ) ;
char parola [ STRLEN ];
9 int l ;
l =0;
11 while ( fscanf ( fin , " % s " , parola ) >0)
{
13 if ( l +1+ strlen ( parola ) > lmax )
{
15 fprintf ( fout , " \ n % s " , parola ) ;
l = strlen ( parola ) ;
17 }
else
19 {
fprintf ( fout , " % s " , parola ) ;
21 l +=1+ strlen ( parola ) ;
}
23 }
fclose ( fin ) ;
25 fclose ( fout ) ;
}
27 int main ( void )
{
29 const int MAXLEN =31;
char nomein [ MAXLEN ] , nomeout [ MAXLEN ];
31 int l ;
printf ( " nome file in ingresso : " ) ;

42
33 scanf ( " % s " , nomein ) ;
printf ( " nome file in uscita : " ) ;
35 scanf ( " % s " , nomeout ) ;
printf ( " massima lunghezza riga : " ) ;
37 scanf ( " % d " , & l ) ;
formatta ( nomein , nomeout , l ) ;
39 }
 

Listing 2.6: Riformattazione testi − mediante sottostringhe

Problemi di verifica e di filtri Sono problemi in cui bisogna decidere se un insieme di dati/infor-
mazioni rispettano un determinato criterio di accettazione (si ha sempre una risposta SI/NO). Si possono
verificare dati singoli o più dati insieme.
Un problema di selezione è del tipo: prima verifico e poi decido.
Verificare una sequenza di dati significa decidere se la sequenza rispetta un determinato criterio di acce-
tazione, come introdurre parole in ordine alfabetico o se voglio tutte parole senza consonanti o problemi
simili.
Dato un file di testo contenente cognome e nome (su una riga di massimo 50 caratteri), si scriva una
funzione che ritorini 1 se è in ordine alfabetico altrimenti ritorini 0.

int verifica_ordine ( FILE * fp )
2 {

const int MAXC =50;


4 char riga0 [ MAXC +1] , riga1 [ MAXC +1];
fgets ( riga0 , MAXC , fp ) ;
6 while ( fgets ( riga1 , MAXC , fp ) != NULL )
{
8 if ( strcmp ( riga1 , riga0 ) <0)
return 0;
10 strcpy ( riga0 , riga1 ) ;
}
12 return 1;
}
 

Listing 2.7: Verifica ordine alfabetico file

43
44
Capitolo 3

Puntatori e allocazione dinamica

I puntatori sono dei tipi di dati che contengono l’indirizzo di una locazione di memoria. Sui puntatori
sono variabili su cui comunque è possibile effettuare delle operazioni (algebra dei puntatori) e consentono
l’allocazione dinamica della memoria (nel C i vettori hanno una dimensione fissa e decisa in fase di
compilazione).

3.1 Tipo di dato puntatore


é un identificatore di una variabile in C e fanno riferimento ad altri dati, inoltre essendo un dato è pos-
sibile manipolarlo.
Una variabile generalmente è identificata attraverso il suo nome (il nome del contenitore), ma in realtà il
C identifica una variabile attraverso il suo indirizzo (il C ha una tabella che converte i nomi agli indirizzi
e tale tabella è inaccessibile al programmatore e all’utente).
All’esecuzione di ogni programma si ha la fase di LOAD che porta in RAM tutti i dati, le variabili e le
istruzioni prendendoli dalla memoria di massa.

3.1.1 Puntatore come riferimento


Il puntatore è generalmente rappresentato attraverso una freccia a cui si associa il tipo indirizzato. Il
puntatore è uno strumento alternativo all’identificatoer per accedere a un dato, inoltre è manipolabile
(come già ripetuto precedentemente).

3.2 Definizione e operazione sui puntatori


Nel linguaggio C vi sono due operatori fondamentali per operare con i puntatori:
• *p, restituisce il dato presente nell’indirizzo contenuto dal puntatore p
• &a, restituisce l’indirizzo in memoria (un puntatore) della variabile a

3.2.1 Definizione di un puntatore


Per definire un puntatore richiede il riferimento a un tipo base presente nel linguaggio, è comunque
possibile realizzare puntatori a struct benchè tali struct devono essere già definite in precedenza. La
definizione è del tipo int* px definisce px una variabile puntatore di tipo intero, tale definizione può
seguire due scuole di pensiero differenti:
• int *px, px è un puntatore a int
• int* px, px è una variabile di tipo puntatore a intero
al momento della definizione *px non contiene ancora un indirizzo quindi sarebbe bene inizializzarlo a
NULL.

3.2.2 Variabili e operazioni sui puntatori


L’operatore & ritorna un puntatore al dato a cui viene applicato.
L’operatore * ritorna il dato puntato dal dato a cui si applica.

45
Assegnazione a variabili puntatori
p = &x oppure s=p (se p è un puntatore)

Assegnazione a dato puntato


*p = 3*(x+2); assegna ala variabile puntata da p il valore 3*(x+2).

Puntatore generico
Esiste in C anche la possibilità di definire un puntatore di tipo generico, void *, tale puntatore può,
rispettando tutte le restrizioni del C (poichè è fortemente tipizzato) essere convertito da puntatore di int
a puntatore di float, . . . .
Un puntatore di questo tipo può esere comodo per puntatori di cui non ci interessa conoscere il tipo o
per puntatori addetti al solo trasporto di un indirizzo.

...
2 int * px ;
char * s0 ;
4 void * generic ;
...
6 generic = px ;
...
8 s0 = generic ;
...
 

Listing 3.1: Esempio di uso di void*

3.2.3 Confronto tra puntatori


Il confronto tra puntatori si può fare nei seguenti modi:

• confrontando i due indirizzi, p1==p2

• confrontando i contenuti degli indirizzi puntati dal puntatore *p1==*p2

Ovviamente se il primo confronto è verificato allora lo è anche il secondo, la cosa non vale al contrario.

3.2.4 Aritmetica dei puntatori


Essendo in C i puntatori delle variabili, allora esse possono essere definite delle operazioni. Sono definite
le operazioni di somma e differenza, poichè operano su indirizzi aggiungere o sottrarre 1 significa andare
all’indirizzo precedente o all’indirizzo successivo.

...
2 int x [100] , * p =& x [50] , *q , * r ;
...
4 q = p +1; /** equivalente a q =& x [51] */
r = p -1; /** equivalente a q =& x [49] */
6 q ++: /** ora q punta a x [52] */
...
 

Listing 3.2: Aritmetica dei puntatori

L’aritmetica dei puntatori è consigliabile non effettuarla su void* in quando non essendo il tipo void* un
tipo puntatore che non contiene la dimensione di ogni singolo elemento puntato potrebbe causare errori
inattesi.

3.3 Implementazioni di strlen


É una funzione, presente nella libreria <string.h>, che ritorna la lunghezza della stringa passata come
parametro (si faccia attenzione a che la stringa sia terminata dal NULL-terminator).

46

int strlen ( char s [])
2 /** senza uso di puntatori , si usa solo un contatore */
{
4 int l =0;
while ( s [ l ]!= ’ \0 ’)
6 l ++;
return +;
8 }
 

Listing 3.3: strlen - I

int strlen ( char s [])
2 /** con puntatore e contatore */
{
4 int l =0;
char * p = & s [0]; // equivalente p = s ;
6 while (* p != ’ \0 ’)
{
8 p ++;
l ++;
10 }
return l ;
12 }
 

Listing 3.4: strlen - II

int strlen ( char s [])
2 /** con due puntatori e senza contatore */
{
4 char * p0 = s ;
char * p1 = s ;
6 while (* p1 != ’ \0 ’)
p1 ++;
8 return ( p1 - p0 ) ;
}
 

Listing 3.5: strlen - III

1 int strlen ( char s [])
/** un puntatore e senza contatore */
3 {

char * p = s ;
5 while (* p != ’ \0 ’)
p ++;
7 return (p - s ) ;
}
 

Listing 3.6: strlen - IV

3.4 Pasaggio parametri by reference


Il linguaggio C non consente il passaggio di parametri attuali per riferimento, consente solo di passarli by
value, cioè ne effettua una copia e lascia libera la funzione chiamata di operare sulla sua copia. Ma come
è possibile far lavorare la funzione sui dati originali? Come fa a passare vettori? Un semplice modo per
ottenere di fatto un passaggio by reference è quello di passare alla funzione chiamata il puntatore alla
variabile che si voleva passare, tale puntatore ovviamente sarà passato by value ma essendo che passa la
copia di un indirizzo quando la funzione fa accesso all’indirizzo modificherà ralmente i dati e non solo
una sua copia. Inoltre in C il nome di un vettore rappresenta in realtà l’indirizzo del primo elemento
dell’array, cioè &dati[0] ≡ dati

47
3.4.1 Vettore come parametro di una funzione
In un paramentro formale un vettore può corrispondere a un puntatore come parametro attuale.

void ordinaInt ( int v [] , int n ) ;
2 int main ( void )
{
4 int dati [100];
...
6 for ( i =0; i <100; i +=10;
ordinaInt (& dati [ i ] , 10) ;
8 ...
}
 

Listing 3.7: Vettore come parametro di una funzione - I

Ma la relazione vale anche al contrario, se come parametro formale c’è un puntatore posso passare come
parametro attuale un vettore.

1 void leggiInt ( int *p , int n ) ;
int main ( void )
3 {

int dati [100];


5 ...
leggiInt ( dati , 100) ;
7 ...
}
 

Listing 3.8: Vettore come parametro di una funzione - II

Si vuol realizzare una funzione che confronta due stringhe e che ritorni:
• 0 se le due stringhe sono uguali
• ¿0 se la prima stringa segue la seconda
• ¡0 se la prima stringa precede la seconda
É una funzione che ha lo stesso funzionamento della strcmp della libreria standard.

int strcmp ( char * s0 , char * s1 )
2 /** senza l ’ uso di alcuna variabile locale */
{
4 while ((* s0 ==* s1 ) && (* s0 != ’ \0 ’) )
{
6 s0 ++;
s1 ++;
8 }
return (* s0 -* s1 ) ;
10 }
 

Listing 3.9: strcmp - I

int strcmp ( char * s0 , char * s1 )
2 /** senza l ’ uso di alcuna variabile locale */
{
4 while ((* s0 ==* s1 ) && (* s0 != ’ \0 ’) )
{
6 s0 ++;
s1 ++;
8 }
return (* s0 -* s1 ) ;
10 }
 

Listing 3.10: strcmp - II

48

int strcmp ( char * s0 , char * s1 , int n )
2 /** strcmp solo su n caratteri */
{
4 int i =0;
while ( s0 [ i ]== s1 [ i ] && s0 [ i ]!= ’ \0 ’)
6 {
if (i < n )
8 i ++;
else
10 return 0;
}
12 return ( s0 [ i ] - s1 [ i ]) ;
}
 

Listing 3.11: strncmp

Si realizzi una funzione che ricerci una stringa all’interno di un’altra stringa (reimplementazione di
strstr della libreria). La funzione ritorna NULL se la sottostringa non è stata trovata, il puntatore
all’inizio della sottostringa se è stata trovata.

char * strstr ( char s [] , char cerca [])
2 {

int i , ns = strlen ( s ) , nc = strlen ( cerca ) ;


4 for ( i =0; i < ns - nc ; i ++)
if ( strncmp (& s [ i ] , cerca , nc ) ==0)
6 return & s [ i ]; // return s + i ;
return NULL ;
8 }
 

Listing 3.12: strstr

3.5 Puntatori a strutture


Per accedere a una struttura dati (struct) si fa esattamente come con le variabili semplici, attraverso il
nome o anche attraverso il suo puntatore.
Si noti che un puntatore in questo caso può puntare all’intera struttura, a un singolo campo della struttura
e può anche essere il campo di una struttura.

struct studente
2 {

char cognome [ MAX ] , nome [ MAX ];


4 int matricola ;
float media ;
6 };

...
8 struct sudente * p ;
float * p_media ;
10 ...

p = ...; /** in qualche modo metto in p l ’ indirizzo di una struct


studente */
12 p_media = &((* p ) . media ) ; /** prendo l ’ indirizzo di media all ’ interno
della struttra puntata da p */
 

Come si può vedere può diventare estremamente lungo e poco intuitivo riuscire a passare indirizzi di
strutture.

struct esame
2 {

int scritto , orale ;


4 };

struct studente

49
6 {
char cognome [ MAX ] , nome [ MAX ];
8 int matricola ;
struct esame * es ;
10 };
...
12 p = ...; /** in qualche modo metto in p l ’ indirizzo di una struct
studente */
 

Se io ora volesi porre il voto dell’esame scritto dello studente puntato da p dovrei fare: (*(*p).es).
scritto=voto, ma come credo sia evidente richiede una grande attenzione alla precedenza degli operatori
(occorre aggiungere in posti esatti asterischi e parentesi). Il C ha introdotto una nuova simbologia per
indicare i campi di una struttura puntata

(*(* p ) . es ) . scritto = p - > es - > scritto ;
 

è chiaro che le due scritture sono equivalenti, ma con la nuova simbologia risulta di molta più semplice
comprensione.

3.5.1 Strutture ricorsive


Nell’ambito dei linguaggi di programmazione si dice ricorsiva un’entità che faccia riferimento in modo
diretto e/o indiretto a ste stessa (a un’entità dello stesso tipo).
Per definire una struttura ricorsiva occorre che all’interno vi sia almento un puntatore che ha come tipo
base la struttura che si sta definendo. Tali strutture sono spesso usate per effettuare linked list.

1 struct studente
{
3 int cognome [ MAX ] , nome [ MAX ];
int matricola ;
5 struct studente * link ;
};
 

Listing 3.13: Esempio Struttura ricorsiva
All’interno della definizione della struttura non è ancora stato mai completamente definito il tipo struct
studente ma la dichiarazione precedente è lecita in quanto ogni puntatore ha una dimensione fissa, e
quindi riserva lo spazio necessario per il puntatore senza problemi. [di Giuseppe Flavio] SLIDE
Si hanno n oggetti disposti in cerchio (numerati da 1 a n). Ogni oggetto conosce solo la posizione
dell’oggetto successivo. Si parte dall’elemento 1 e si elimina un ogetto ogni M , ovviamente dopo aver
eliminato l’oggetto è necessario che l’oggetto precedente punti all’oggetto successivo.
Come struttura dati sarà necessario realizzare una lista o catena circolare, per realizzarla si crea una
struttura che contiene il numero e il puntatore alla struttura successiva.
Occorre effettuare una iterazione per selezionare/eliminare l’i-esimo oggetto.

# include < stdio .h >
2 # define MAX 100
struct oggetto
4 {

int num ;
6 struct oggetto * succ ;
};
8 int main ( void )
{
10 int i , N , M ;
struct oggetto *p , oggetti [ MAX ];
12 printf ( " N = " \ n " ) ;
scanf ( " % d " , & N ) ;
14 printf ( " N = " \ n " ) ;
scanf ( " % d " , & M ) ;
16 for (
}
 

Listing 3.14: Esempio di Giuseppe Flavio

50
3.6 Allocazione dinamica della memoria
L’obbiettivo fondamentele nell’allocazione dinamica è avere un contatto diretto con l’allocazione di por-
zioni di memoria, capire che la memoria allocata è sotto lo stretto controllo del programmatore (che dovrà
capire dove, quanto e come allocare la memoria).
Allocare una variabile significa associare alla variabile una porzione di memoria (in cui collocare i dati).
L’allocazione avviene in modo:
• permanente, per le variabili globali (definite fuori da ogni funzione)
• temporaneo, per le variabili locali e i parametri formali
• dinamico, per le variabili allocate dinamicamente dal programmatore
Il C fornice metodi per l’allocazione e deallocazione esplicita basata su puntatori.
Allocare la memoria significa richiedere al Sistema operativo un determinato quantitativo di memoria e
ottenerla (se c’è abbastanza memoria).
Deallocare la memoria significa restituire (rilascare) la memoria precedentemente ottenuta, al Sistema
Operativa, in modo che il SO la possi riusare. In fase di deallocazione è solo necessario passare al sistema
operativo il punto in cui inizia la memoria allocata dinamicamente.

3.6.1 Malloc
La funzione malloc nel C ritorna un puntatore di tipo void * che punta a una memoria contigua di n
byte, nel caso non sia disponibile tutta la memoria richiesta ritorna un puntatore NULL. La funzione ha
il seguente prototipo

void * malloc ( size_t size ) ;
 

Listing 3.15: Prototipo malloc

size indica il numero di byte da richiedere al sistema operativo. É conveniente chiedere in fase di richiesta
di memoria, fare un cast esplicito al tipo a cui viene effettuata l’allocazione.
Per evitare di dover fare conti manualmente, spesso errati, sulla quantità di memoria da richiedere si
esegue una stategia molto semplicativa: si indica con n il numero di celle di memoria da allocare e con
sizeof(type) richiama la funzione sizeof che vuole come parametro il tipo della variabile e ne ritorna
la memoria occupata da ciascuna allocazione.

Ad esempio: (int *)malloc(n*sizeof(int));


Per rilasciare la memoria si esegue semplicemente il richiamo di una funzione, free, che dealloca la
memoria associata al puntatore che è passato alla funzione.

3.7 Strutture Astratte


3.7.1 Liste
Le liste sono stutture astrette composte da elementi singoli puntati tra di loro attraverso degli specifici
puntatori, si tratteranno quindi nel caso di allocazione dinamica della struttura di strutture ricorsive.
Si hanno liste linkate semplici se ogni elemento conosce il puntatore all’elemento successivo, si hanno liste
doppio linkate se ogni elemento ha il puntatore all’elemento precedente e successivo.
L’utilizzo di questa struttura dati consente l’allocazione di ogni elemento senza “riallocare” un nuovo
vettore. Con questa strategia si perde l’accesso diretto agli elementi, si può comunque percorrere la li-
sta pertanto tutte le operazioni (eccetto alcune specifiche operazioni) avranno complessità lineare (O(n)).

Inserimento in mezzo di un nuovo elemento


Supponendo che x sia il puntatore all’elemento dopo il quale vuol essere inserito il nuovo elemento t si
eseguono due semplici operazioni per aggiornare i puntatori. É necessario prestare attenzione che nel
caso di scansione per determinare x fermarsi all’elemento precedente della nuova posizione, questo se la
lista è linkata semplice.
struct nodo{ITEM item; struct nodo *next;} t->next = x->next; x->next = t; Nel caso di li-
ste doppio linkate
struct nodo{ITEM item; struct nodo *prev, *next;} t->next = x->next; x->next->prev = t;
x->next = t; t->prev = x;

51
Cancellazione di un nodo (elemento)
Dato che si trattano specialmente liste linkate semplici, è necessario fermarsi all’elemento precedente del
quale bisogna cancellare. Denominando con x l’elemento precedente.
L’eliminazione è strutturata sostanzialmente da

temp = x->next; x-next = x->next->next; elaborazione(temp);


Dove elaborazione(temp) è spesso una semplice richiamata a free(temp), ma ciò non è vincolante.
Nel caso di lista doppio linkata la cancellazione è nel seguente modo (t elemento da cancellare)
t->prev->next = t->next; t->next->prev = t->prev; elaborazione(t);

In realtà una lista può anche essere implementata attraverso un vettore statico, in questo caso l’inseri-
mento, la cancellazione e l’estrazione di elementi della lista hanno complessità di O(n) in quanto occorre
shiftare tutti gli elementi per “ricompattare” il vettore, mentre l’accesso a un singolo elemento può essere
determinato dall’indice (complessità costante O(1)).

52
Parte III

Laboratorio

53
Capitolo 4

Esercitazione 1

4.1 Esercizio n. 1: manipolazione di una matrice - I


Un file di testo contiene una matrice quadrata di numeri reali con il seguente formato:
• la prima riga del file specifica la dimensione reale della matrice (si assuma che sia comunque al più
pari a 10).
• ciascuna delle righe successive contiene i valori corrispondenti a una riga della matrice, separati da
uno o più spazi.
Si realizzi un programma C che:
• legga tale matrice dal file di ingresso, il cui nome è specificato dall’utente.
• generi una nuova matrice, delle stesse dimensioni di quella appena acquisita, in cui il valore di
ciascun elemento è:
– 0 se il corrispondente elemento della matrice di ingresso è negativo o nullo.
– l’ordine di grandezza del corrispondente elemento della matrice di ingresso (definito come la
più piccola potenza di 10 che è maggiore del valore in questione) in caso contrario.
• visualizzi sul video un opportuno messaggio a seconda che la matrice cosı̀ generata risulti simmetrica
oppure no.
Per quanto possibile, si cerchi di risolvere il problema proposto implementando delle opportune funzioni.

# include < stdio .h >
2 # define MAXDIM 10
# define MAXSTR 255
4 int isSimmetrica ( float matrix [][ MAXDIM ] , int dim ) ;
int leggi_file ( char * nome_file , float matrix [][ MAXDIM ]) ;
6 int potenza_sup ( int base , int numero ) ;
void manipolazione ( float matrix [][ MAXDIM ] , int dim ) ;
8 int main ()
{
10 char path [ MAXSTR +1]={0};
float matrix [ MAXDIM ][ MAXDIM ];
12 int dim ;
printf ( " Introduci il nome del file contenente la matrice :\ n \ t " ) ;
14 gets ( path ) ;
if (( dim = leggi_file ( path , matrix ) ) >=0)
16 {
manipolazione ( matrix , dim ) ;
18 printf ( " La matrige generata dal file in ingresso risulta % s .\ n " ,
( isSimmetrica ( matrix , dim ) ? " simmetrica " : " non simmetrica "
));
}
20 else
{
22 printf ( " Il file % s non e ’ stato aperto .\ n " , path ) ;

55
system ( " pause " ) ;
24 return 1;
}
26 system ( " pause " ) ;
return 0;
28 }
void manipolazione ( float matrix [][ MAXDIM ] , int dim )
30 {
int i , j ;
32 for ( i =0; i < dim ; i ++)
{
34 for ( j =0; j < dim ; j ++)
{
36 if ( matrix [ i ][ j ] <=0)
matrix [ i ][ j ]=0;
38 else
matrix [ i ][ j ] = potenza_sup (10 , matrix [ i ][ j ]) ;
40 }
}
42 }
int potenza_sup ( int base , int numero )
44 {
int i =1;
46 while (i < numero )
i *= base ;
48 return i ;
}
50 int leggi_file ( char * nome_file , float matrix [][ MAXDIM ])
{
52 FILE * f ;
int i , j , dim ;
54 if (( f = fopen ( nome_file , " r " ) ) == NULL )
return 0;
56 fscanf (f , " % d " , & dim ) ;
for ( i =0; i < dim ; i ++)
58 {
for ( j =0; j < dim ; j ++)
60 {
if ( fscanf (f , " % f " , & matrix [ i ][ j ]) == EOF )
62 {
fclose ( f ) ;
64 return -1;
}
66 }
}
68 fclose ( f ) ;
return dim ;
70 }
int isSimmetrica ( float matrix [][ MAXDIM ] , int dim )
72 {
int i , j ;
74 for ( i =1; i < dim ; i ++)
{
76 for ( j =0; j < i ; j ++)
{
78 if ( matrix [ i ][ j ]!= matrix [ j ][ i ])
return 0;
80 }
}
82 return 1;
}

56
 

Listing 4.1: Manipolazione di una matrice - I

4.2 Esercizio n. 2 - Manipolazione di una matrice - II


Un file di testo contiene una matrice di interi con il seguente formato:

• la prima riga del file specifica le dimensioni reali della matrice (numero di righe nr e numero di
colonne nc). Si assuma che entrambi i valori siano comunque al più pari a 20.

• ciascuna delle nr righe successive contiene gli nc valori corrispondenti a una riga della matrice,
separati da uno o più spazi.

Si scriva un programma C che:

• legga tale matrice dal file di ingresso.

• generi una nuova matrice, in cui il valore di ciascun elemento è dato dalla media aritmetica delle
(al più) 8 caselle adiacenti all’elemento corrispondente della matrice di ingresso.

• scriva la matrice cosı̀ ottenuta su un file di uscita, con lo stesso formato del file di ingresso.

Il nome dei due file sia passato al programma sulla riga di comando.

1 # include < stdio .h >
# include < stdlib .h >
3 # define MAXR 20
# define MAXC 20
5 # define MAXSTR 255
void manipolazione ( FILE * dev ) ;
7 int leggi_file ( char * nome_file ) ;
int add ( int rs , int cs , int * sum ) ;
9 float matrix [ MAXR ][ MAXC ];
int r , c ;
11 int main ( int argc , char ** argv )
{
13 FILE * F ;
if ( argc ==3)
15 {
if ( leggi_file ( argv [1]) )
17 {
if (( F = fopen ( argv [2] , " w " ) ) == NULL )
19 {
printf ( " Il file % s non e ’ stato aperto .\ n " , argv [2]) ;
21 system ( " pause " ) ;
return 1;
23 }
manipolazione ( F ) ;
25 fclose ( F ) ;
}
27 else
{
29 printf ( " Il file % s non e ’ stato aperto .\ n " , argv [1]) ;
system ( " pause " ) ;
31 return 2;
}
33 }
else
35 {
printf ( " Comandi inseriti non validi .\ n % s file_input file_output \
n " , argv [0]) ;
37 system ( " pause " ) ;
return 3;

57
39 }
system ( " pause " ) ;
41 return 0;
}
43 int leggi_file ( char * nome_file )
{
45 FILE * f ;
int i , j ;
47 if (( f = fopen ( nome_file , " r " ) ) == NULL )
return 0;
49 fscanf (f , " % d % d " , &r , & c ) ;
for ( i =0; i < r ; i ++)
51 {
for ( j =0; j < c ; j ++)
53 {
if ( fscanf (f , " % f " , & matrix [ i ][ j ]) == EOF )
55 {
fclose ( f ) ;
57 return 0;
}
59 }
}
61 fclose ( f ) ;
return 1;
63 }

int add ( int rs , int cs , int * sum )


65 {

if ( rs >=0 && cs >=0 && rs < r && cs < c )


67 {
* sum = * sum + matrix [ rs ][ cs ];
69 return 1;
}
71 return 0;
}
73 void manipolazione ( FILE * dev )
{
75 int i , j ;
int n_el , sum ;
77 fprintf ( dev , " % d % d \ n " , r , c ) ;
for ( i =0; i < r ; i ++)
79 {
for ( j =0; j < c ; j ++)
81 {
sum =0;
83 n_el =0;
n_el += add (i -1 , j -1 , & sum ) ;
85 n_el += add (i -1 , j , & sum ) ;
n_el += add (i -1 , j +1 , & sum ) ;
87 n_el += add (i , j -1 , & sum ) ;
n_el += add (i , j +1 , & sum ) ;
89 n_el += add ( i +1 , j -1 , & sum ) ;
n_el += add ( i +1 , j , & sum ) ;
91 n_el += add ( i +1 , j +1 , & sum ) ;
fprintf ( dev , " %.1 f " , (( float ) sum ) / n_el ) ;
93 }
fprintf ( dev , " \ n " ) ;
95 }
}
 

Listing 4.2: Manipolazione di una matrice - II
Tale soluzione effettua per ogni casella una serie di otto condizioni, per controllare bene gli elementi
presenti sul bordo.

58

# include < stdio .h >
2 # include < stdlib .h >
# define MAXR 20
4 # define MAXC 20
# define MAXSTR 255
6 void manipolazione ( FILE * dev ) ;
int leggi_file ( char * nome_file ) ;
8 float sum ( int r , int c ) ;
float matrix [ MAXR +2][ MAXC +2];
10 int r , c ;

int main ( int argc , char ** argv )


12 {

FILE * F ;
14 if ( argc ==3)
{
16 if ( leggi_file ( argv [1]) )
{
18 if (( F = fopen ( argv [2] , " w " ) ) == NULL )
{
20 printf ( " Il file % s non e ’ stato aperto .\ n " , argv [2]) ;
system ( " pause " ) ;
22 return 1;
}
24 manipolazione ( F ) ;
fclose ( F ) ;
26 }
else
28 {
printf ( " Il file % s non e ’ stato aperto .\ n " , argv [1]) ;
30 system ( " pause " ) ;
return 2;
32 }
}
34 else
{
36 printf ( " Comandi inseriti non validi .\ n % s file_input file_output \
n " , argv [0]) ;
system ( " pause " ) ;
38 return 3;
}
40 system ( " pause " ) ;
return 0;
42 }

int leggi_file ( char * nome_file )


44 {

FILE * f ;
46 int i , j ;
if (( f = fopen ( nome_file , " r " ) ) == NULL )
48 return 0;
fscanf (f , " % d % d " , &r , & c ) ;
50 for ( i =1; i <= r ; i ++)
{
52 for ( j =1; j <= c ; j ++)
{
54 if ( fscanf (f , " % f " , & matrix [ i ][ j ]) == EOF )
{
56 fclose ( f ) ;
return 0;
58 }
}
60 }

59
fclose ( f ) ;
62 return 1;
}
64 float sum ( int rc , int cc )
{
66 float s =0;
s += matrix [ rc -1][ cc -1];
68 s += matrix [ rc -1][ cc ];
s += matrix [ rc -1][ cc +1];
70 s += matrix [ rc ][ cc -1];
s += matrix [ rc ][ cc +1];
72 s += matrix [ rc +1][ cc -1];
s += matrix [ rc +1][ cc ];
74 s += matrix [ rc +1][ cc +1];
printf ( " sum (%2 d , %2 d ) =%2.0 f \ n " , rc , cc , s ) ;
76 return s ;
}
78 void manipolazione ( FILE * dev )
{
80 int i , j ;
fprintf ( dev , " % d % d \ n " , r , c ) ;
82 fprintf ( dev , " %.1 f " , sum (1 , 1) /3) ;
for ( j =2; j < c ; j ++)
84 fprintf ( dev , " %.1 f " , sum (1 , j ) /5) ;
if ( j !=2)
86 fprintf ( dev , " %.1 f \ n " , sum (1 , j ) /3) ;
for ( i =2; i < r ; i ++)
88 {
fprintf ( dev , " %.1 f " , sum (i , 1) /5) ;
90 for ( j =2; j < c ; j ++)
fprintf ( dev , " %.1 f " , sum (i , j ) /8) ;
92 if ( j !=2)
fprintf ( dev , " %.1 f \ n " , sum (i , j ) /5) ;
94 }
if ( i !=2)
96 {
fprintf ( dev , " %.1 f " , sum (i , 1) /3) ;
98 for ( j =2; j < c ; j ++)
fprintf ( dev , " %.1 f " , sum (i , j ) /5) ;
100 if ( j !=2)
fprintf ( dev , " %.1 f \ n " , sum (i , j ) /3) ;
102 }
}
 

Listing 4.3: Manipolazione di una matrice - III
Tale implementazione, a differenza della precedente, evita il controllo per ogni casella, semplicemente
mettendo un bordo esterno alla matrice. Si avrà un’utilizzo maggiore della memoria ma una riduzione
evidente degli if da valutare.

4.3 Esercizio n. 3: riformattazione di un testo


Un file contiene un testo di lunghezza indefinita. Si desidera un programma C che riformatti tale testo
mediante le regole seguenti:
• rimozione di tutti i caratteri di spaziatura
• trasformazione di tutti i caratteri alfabetici che si trovano dopo un carattere di spaziatura nel
carattere maiuscolo corrispondente, e degli altri nel corrispondente minuscolo
• riduzione della lunghezza delle righe del testo a 20 caratteri ciascuna (l’ultima può ovviamente
essere più corta)
Il testo cosı̀ codificato venga scritto su un file di uscita.

60

# include < stdio .h >
2 # include < stdlib .h >
# define path_in " text . txt "
4 # define path_out " text_out . txt "
# define row_lenght 20
6 char toLower ( char c )
{
8 if (c >= ’A ’ && c <= ’Z ’)
return ’a ’ + c - ’A ’;
10 else
return c ;
12 }

char toUpper ( char c )


14 {

if (c >= ’a ’ && c <= ’z ’)


16 return ’A ’ + c - ’a ’;
else
18 return c ;
}
20 int main ()
{
22 FILE * fin , * fout ;
char c , c_prec ;
24 int c_write ;
if (( fin = fopen ( path_in , " r " ) ) == NULL )
26 {
printf ( " Il file % s non e ’ stato aperto .\ n " , path_in ) ;
28 system ( " pause " ) ;
return 1;
30 }
if (( fout = fopen ( path_out , " w " ) ) == NULL )
32 {
printf ( " Il file % s non e ’ stato aperto .\ n " , path_out ) ;
34 system ( " pause " ) ;
return 2;
36 }
c_prec = ’ ’;
38 c_write = 0;
while (( c = fgetc ( fin ) ) != EOF )
40 {
if ( c != ’ ’)
42 {
if ( c_prec == ’ ’)
44 fprintf ( fout , " % c " , toUpper ( c ) ) ;
else
46 fprintf ( fout , " % c " , toLower ( c ) ) ;
c_write ++;
48 if ( c_write == row_lenght )
{
50 c_write =0;
fprintf ( fout , " \ n " ) ;
52 }
}
54 c_prec = c ;
}
56 return 0;
}
 

Listing 4.4: Riformattazione di un testo

61
62
Capitolo 5

Esercitazione 2

5.1 Esercizio n. 1: decompressione di un testo


Un’applicazione per la compressione di testi funziona in questo modo:

• Dato un file di testo, l’applicazione individua anzitutto un’insieme di parole del testo (tipicamente
lunghe e ripetute) che verranno poi elaborate come segue.

• A ciascuna delle parole dell’insieme cosı̀ costruito viene associato un intero univoco, con valore
sempre compreso nell’intervallo [0 - 99]. L’applicazione riporta in un secondo file di testo ogni
associazione intero/parola (una per riga).

• Nel file di testo originale, infine, ogni parola che appartiene all’insieme viene sostituita dall’intero
ad essa associato (preceduto dal carattere $).

Si desidera un programma C che, dati due file (il file di testo trasformato e il corrispondente file delle
associazioni), ricostruisca il file di testo originale. Si può assumere che ogni riga del file “compresso” sia
lunga al più 80 caratteri.

Suggerimento: si usi un vettore di stringhe (realizzato come matrice di caratteri) per memorizzare
l’associazione tra un intero (indice del vettore, ovvero indice di riga della matrice) e la parola (elemento
del vettore, corrispondente a una riga della matrice) ad esso collegata. Il file di ingresso, inoltre, sia letto
per righe.

1 # include < stdio .h >
# include < string .h >
3 # include < stdlib .h >
# define ROWLENGHT 80
5 # define MAXINDEX 99
int decomprimi ( char * filename_input , char * filename_output , char corr [][
ROWLENGHT +1]) ;
7 int l e g g i _ c o r r i s p o n d e n z e ( char * filename , char corr [][ ROWLENGHT +1]) ;
int main ()
9 {

char corrispondenze [ MAXINDEX +1][ ROWLENGHT +1];


11 if (! l e g g i _ c o r r i s p o n d e n z e ( " corr . txt " , corrispondenze ) )
{
13 printf ( " Errore apertura file delle corrispondenze .\ n " ) ;
system ( " pause " ) ;
15 return -1;
}
17 if (! decomprimi ( " compressed . txt " , " extracted . txt " , corrispondenze ) )
{
19 printf ( " Errore in decompressione .\ n " ) ;
system ( " pause " ) ;
21 return -2;
}
23 return 0;
}

63
25 int decomprimi ( char * filename_input , char * filename_output , char corr [][
ROWLENGHT +1])
{
27 FILE * fin , * fout ;
char c ;
29 int num_c , corrispondenza ;
if (( fin = fopen ( filename_input , " r " ) ) == NULL || ( fout = fopen (
filename_output , " w " ) ) == NULL )
31 return 0;
corrispondenza = 0;
33 while ( fscanf ( fin , " % c " , & c ) != EOF )
{
35 if ( c == ’$ ’)
{
37 fscanf ( fin , " % d " , & num_c ) ;
printf ( " % s " , corr [ num_c ]) ;
39 }
else
41 printf ( " % c " , c ) ;
}
43 return 1;
}
45 int l e g g i _ c o r r i s p o n d e n z e ( char * filename , char corr [][ ROWLENGHT +1])
{
47 FILE * f ;
int n_c ;
49 for ( n_c =0; n_c < MAXINDEX +1; n_c ++)
corr [ n_c ][0]=0;
51 if (( f = fopen ( filename , " r " ) ) == NULL )
return 0;
53 while ( fscanf (f , " % d " , & n_c ) != EOF )
fscanf (f , " % s " , corr [ n_c ]) ;
55 fclose ( f ) ;
return 1;
57 }
 

Listing 5.1: Decompressione di un testo

5.2 Esercizio n. 2: stringhe periodiche.


Si scriva un programma in grado di:

• leggere da tastiera un numero indefinito di stringhe, ciascuna composta da soli caratteri alfanumerici
e di lunghezza massima pari a 30. La lettura termini quando viene introdotta la parola “stop”.

• indicare, per ciascuna stringa acquisita, se essa è “periodica” oppure no, ovvero costituita da un’u-
nica sotto-stringa che si ripete. Ad esempio, sono periodiche (con periodo indicato tra parentesi)
le seguenti sotto-stringhe: “zzzzz” (1), “k21k21” (3), “vivaviva” (4), etc. Si noti che una stringa
composta da un solo carattere, come anche una stringa in cui l’ultimo periodo non risulti concluso,
non deve essere considerata periodica.

# include < stdio .h >
2 # include < stdlib .h >
# include < ctype .h >
4 # include < string .h >
# define terminatore " stop "
6 # define LENGHTMAX 30
int periodo ( char str []) ;
8 int main ()
{
10 char str [ LENGHTMAX +1]={0};

64
int i , p , err =0;
12 do
{
14 printf ( " Stringa (% s per terminare ) ?\ t " , terminatore ) ;
scanf ( " % s " , str ) ;
16 if ( strcmp ( str , terminatore ) ==0)
continue ;
18 for ( i =0; str [ i ]!= ’ \0 ’; i ++)
if (! isalnum ( str [ i ]) )
20 err =1;
if ( err )
22 {
err =0;
24 continue ;
}
26 printf ( " La stringa % s " , str ) ;
p = periodo ( str ) ;
28 if (p >=1)
printf ( " e ’ periodica ( periodo % d ) .\ n " , p ) ;
30 else
printf ( " non e ’ periodica .\ n " ) ;
32 }
while ( strcmp ( str , terminatore ) !=0) ;
34 return 0;
}
36 int egualNcharacter ( char s1 [] , char s2 [] , int n )
{
38 int i ;
i =0;
40 while (*( s1 + i ) ==*( s2 + i ) && *( s1 + i ) != ’ \0 ’ && i < n )
i ++;
42 return i == n ;
}
44 int periodo ( char str [])
{
46 int i , j ;
int lenstr = strlen ( str ) ;
48 if ( lenstr ==1 || lenstr == LENGHTMAX )
return 0;
50 for ( i =1; i <= lenstr /2; i ++)
{
52 if ( lenstr % i ==0)
{
54 for ( j = i ; j < lenstr && egualNcharacter ( str , str +j , i ) ; j += i )
;
56 if ( j == lenstr )
return i ;
58 }
}
60 return 0;
}
 

Listing 5.2: Stringhe periodiche

5.3 Esercizio n. 3: voli aerei


Ogni riga di un file di testo descrive un volo aereo, nel formato:
<sigla_partenza> <sigla_arrivo>
in cui le sigle sono stringhe di tre caratteri (senza spazi) che identificano gli aeroporti di partenza e di
arrivo. Supponendo che sia specificato un massimo di 200 voli, si scriva un programma che:

65
• legga tale file (di nome predefinito), memorizzandone il contenuto in un’opportuna struttura dati.

• indichi sul video quali e quante sono le tratte aeree per le quali esistono sia il volo di andata che
quello di ritorno.

# include < stdio .h >
2 # include < stdlib .h >
# include < string .h >
4 # define SIGLA_LENGHT 3
# define MAX_FLIGHT 200
6 # define flight_file " voli . txt "
typedef struct flight_t
8 {

char departure [ SIGLA_LENGHT +1] , arrival [ SIGLA_LENGHT +1];


10 } flight ;
int leggi_voli ( char * filename , flight voli [] , int * n_voli ) ;
12 void elabora_info ( flight voli [] , int n_voli ) ;
int main ()
14 {

flight voli [ MAX_FLIGHT ];


16 int n_voli ;
if (! leggi_voli ( flight_file , voli , & n_voli ) )
18 {
printf ( " Errore nella lettura del file % s \ n " , flight_file ) ;
20 system ( " pause " ) ;
return 1;
22 }
elabora_info ( voli , n_voli ) ;
24 system ( " pause " ) ;
return 0;
26 }

int leggi_voli ( char * filename , flight voli [] , int * n_voli )


28 {

FILE * f ;
30 if (( f = fopen ( filename , " r " ) ) == NULL )
return 0;
32 * n_voli = 0;
while (* n_voli < MAX_FLIGHT && fscanf (f , " % s % s " , voli [* n_voli ].
departure , voli [* n_voli ]. arrival ) ==2)
34 * n_voli +=1;
fflush ( f ) ;
36 fclose ( f ) ;
return 1;
38 }

void elabora_info ( flight voli [] , int n_voli )


40 {

int i , j , ar =0;
42 for ( i =0; i < n_voli -1; i ++)
{
44 if ( voli [ i ]. arrival [0]==0)
continue ;
46 for ( j = i +1; j < n_voli ; j ++)
{
48 if ( voli [ j ]. arrival [0]==0)
continue ;
50 if ( strcmp ( voli [ i ]. departure , voli [ j ]. arrival ) ==0 && strcmp (
voli [ i ]. arrival , voli [ j ]. departure ) ==0)
{
52 ar ++;
printf ( " % s % s \ n " , voli [ i ]. departure , voli [ i ]. arrival ) ;
54 voli [ j ]. arrival [0] = voli [ j ]. departure [0] = 0;
}

66
56 }
}
58 printf ( " Totale connessioni A / R : % d \ n " , ar ) ;
}
 

Listing 5.3: Voli aerei

67
68
Capitolo 6

Esercitazione 3

6.1 Esercizio n. 1: confronto tra algoritmi di ordinamento


Si realizzi un programma che:
• legga due numeri interi positivi M ed N .
• generi casualmente N numeri interi, memorizzandoli in un vettore allocato dinamicamente.
• ordini tale vettore in maniera crescente, utilizzando uno dei seguenti algoritmi:
– insertion sort
– selection sort
– bubble sort
ripeta la generazione casuale e l’ordinamento per M volte.
Si confrontino le prestazioni dei tre algoritmi per i valori di M = {10, 100, 1000} e di N = {1000, 5000, 10000},
misurandone i tempi di esecuzione.

1 # include < stdio .h >
# include < stdlib .h >
3 # include < time .h >
# define VMAX RAND_MAX
5 int * my_alloca ( int n_el ) ;
void genera_casuale ( int * vet , int n_el , int v_max ) ;
7 void insersion_sort ( int * vet , int dim ) ;
void bubble_sort ( int * vet , int dim ) ;
9 void selection_sort ( int * vet , int dim ) ;
int is_ordinato ( int * vet , int dim ) ;
11 int main ()
{
13 int * a ;
int t0 , t1 ;
15 int i , j , M ;
int vN [] = {1000 , 5000 , 10000};
17 int vM [] = {10 , 100 , 1000};
printf ( " N M insersion_sort bubble_sort selection_sort \ n " ) ;
19 for ( i =0; i < sizeof ( vN ) / sizeof ( int ) ; i ++)
{
21 a = my_alloca ( vN [ i ]) ;
for ( j =0; j < sizeof ( vM ) / sizeof ( int ) ; j ++)
23 {
printf ( " %6 d %6 d " , vN [ i ] , vM [ j ]) ;
25 t0 = clock () ;
for ( M =0; M < vM [ j ]; M ++)
27 {
genera_casuale (a , vN [ i ] , VMAX ) ;
29 insersion_sort (a , vN [ i ]) ;
}

69
31 t1 = clock () ;
printf ( " %14.3 f " , (( float ) ( t1 - t0 ) ) / CLOCKS_PER_SEC ) ;
33 t0 = clock () ;
for ( M =0; M < vM [ j ]; M ++)
35 {
genera_casuale (a , vN [ i ] , VMAX ) ;
37 bubble_sort (a , vN [ i ]) ;
}
39 t1 = clock () ;
printf ( " %11.3 f " , (( float ) ( t1 - t0 ) ) / CLOCKS_PER_SEC ) ;
41 t0 = clock () ;
for ( M =0; M < vM [ j ]; M ++)
43 {
genera_casuale (a , vN [ i ] , VMAX ) ;
45 selection_sort (a , vN [ i ]) ;
}
47 t1 = clock () ;
printf ( " %14.3 f " , (( float ) ( t1 - t0 ) ) / CLOCKS_PER_SEC ) ;
49 printf ( " \ n " ) ;
}
51 free ( a ) ;
}
53 system ( " pause " ) ;
return 0;
55 }
int * my_alloca ( int n_el )
57 {
int * vet ;
59 vet = ( int *) malloc ( n_el * sizeof ( int ) ) ;
return vet ;
61 }
void genera_casuale ( int * vet , int n_el , int v_max )
63 {
for ( n_el - -; n_el >=0; n_el - -)
65 {
vet [ n_el ] = rand () % v_max ;
67 }
}
69 void insersion_sort ( int * vet , int dim )
{
71 int i , j , temp ;
for ( i =1; i < dim ; i ++)
73 {
temp = vet [ i ];
75 for ( j =i -1; j >=0 && temp < vet [ j ]; j - -)
{
77 vet [ j +1] = vet [ j ];
}
79 vet [ j +1]= temp ;
}
81 }
void bubble_sort ( int * vet , int dim )
83 {
int i , j , temp , scambio ;
85 scambio =1;
for ( i = dim -1; i >1 && scambio ; i - -)
87 {
scambio =0;
89 for ( j =0; j < i ; j ++)
{
91 if ( vet [ j ] > vet [ j +1])
{

70
93 temp = vet [ j ];
vet [ j ] = vet [ j +1];
95 vet [ j +1] = temp ;
scambio =1;
97 }
}
99 }
}
101 void selection_sort ( int * vet , int dim )
{
103 int i , j , temp , min ;
for ( i =0; i < dim -2; i ++)
105 {
min = i ;
107 for ( j = i +1; j < dim -1; j ++)
{
109 if ( vet [ j ] < vet [ min ])
{
111 min = j ;
}
113 }
if ( min != i )
115 {
temp = vet [ i ];
117 vet [ i ] = vet [ min ];
vet [ min ] = temp ;
119 }
}
121 }

int is_ordinato ( int * vet , int dim )


123 {

int i ;
125 for ( i =1; i < dim ; i ++)
if ( vet [ i ] < vet [i -1])
127 return 0;
return 1;
129 }
 

Listing 6.1: Confronto degli algoritmi di ordinamento

6.2 Esercizio n. 2: ordinamento di stringhe


Un file di testo contiene un numero indefinito di stringhe, ciascuna di lunghezza massima pari a 30
caratteri, una stringa per ogni riga del file. Si scriva un programma C che:

• acquisisca le stringhe contenute nel file di ingresso, memorizzandole in un vettore.

• ordini tale vettore mediante uno degli algoritmi di ordinamento noti.

• scriva il vettore ordinato su un secondo file di testo.

Si realizzi il programma in almeno due versioni:

1. facendo uso di una matrice statica di caratteri per memorizzare le stringhe del file di ingresso
(assumendo che queste siano in numero al più pari a 100).

2. allocando dinamicamente il vettore in cui salvare le stringhe e le stesse stringhe all’atto della
memorizzazione.

Si noti che nel primo caso lo “scambio” tra due stringhe ai fini dell’ordinamento deve essere eseguito
tramite delle opportune chiamate alla funzione strcpy(), mentre nel secondo occorre solo scambiare i
relativi puntatori.

71

# include < stdio .h >
2 # include < stdlib .h >
# include < string .h >
4 # define path_in " testo . txt "
# define path_out " testo1 . txt "
6 # define lenMax 30
# define strMax 100
8 int leggi_file ( char * path , char matrix [][ lenMax +1] , int * n_stringhe ) ;
int salva_file ( char * path , char vet [][ lenMax +1] , int dim ) ;
10 void swap ( char mat [][ lenMax +1] , int i , int j ) ;
void bubble_sort ( char vet [][ lenMax +1] , int dim ) ;
12 void ordina ( char mat [][ lenMax +1] , int n_str ) ;
int main ()
14 {

int n_str ;
16 char mat [ strMax ][ lenMax +1];
if (! leggi_file ( path_in , mat , & n_str ) )
18 {
printf ( " Errore in apertura del file % s .\ n " , path_in ) ;
20 system ( " pause " ) ;
return 1;
22 }
ordina ( mat , n_str ) ;
24 if (! salva_file ( path_out , mat , n_str ) )
{
26 printf ( " Errore in apertura del file % s .\ n " , path_out ) ;
system ( " pause " ) ;
28 return 1;
}
30 system ( " pause " ) ;
return 0;
32 }

int leggi_file ( char * path , char matrix [][ lenMax +1] , int * n_stringhe )
34 {

FILE * f ;
36 * n_stringhe =0;
if (( f = fopen ( path , " r " ) ) == NULL )
38 return 0;
while ( fscanf (f , " % s " , matrix [* n_stringhe ]) != EOF )
40 * n_stringhe +=1;
return 1;
42 }

int salva_file ( char * path , char vet [][ lenMax +1] , int dim )
44 {

FILE * f ;
46 if (( f = fopen ( path , " w " ) ) == NULL )
return 0;
48 int i ;
for ( i =0; i < dim ; i ++)
50 fprintf (f , " % s \ n " , vet [ i ]) ;
fflush ( f ) ;
52 fclose ( f ) ;
return 1;
54 }

void swap ( char mat [][ lenMax +1] , int i , int j )


56 {

char temp [ lenMax +1];


58 strcpy ( temp , mat [ i ]) ;
strcpy ( mat [ i ] , mat [ j ]) ;
60 strcpy ( mat [ j ] , temp ) ;
}

72
62 void bubble_sort ( char vet [][ lenMax +1] , int dim )
{
64 int i , j , scambio ;
scambio =1;
66 for ( i = dim -1; i >1 && scambio ; i - -)
{
68 scambio =0;
for ( j =0; j < i ; j ++)
70 {
if ( strcmp ( vet [ j ] , vet [ j +1]) >0)
72 {
swap ( vet , j , j +1) ;
74 scambio =1;
}
76 }
}
78 }

void ordina ( char mat [][ lenMax +1] , int n_str )


80 {

bubble_sort ( mat , n_str ) ;


82 }
 

Listing 6.2: Ordinamento di stringhe con allocazione statica

1 # include < stdio .h >
# include < stdlib .h >
3 # include < string .h >
# define path_in " testo . txt "
5 # define path_out " testo1 . txt "
# define lenMax 30
7 char ** leggi_file ( char * path , int * n_stringhe ) ;
void swap ( char ** mat , int i , int j ) ;
9 void ordina ( char ** mat , int n_str ) ;
void bubble_sort ( char ** vet , int dim ) ;
11 int salva_file ( char * path , char ** vet , int dim ) ;
int main ()
13 {

int n_str ;
15 char ** mat = leggi_file ( path_in , & n_str ) ;
if ( mat == NULL )
17 {
printf ( " Errore in apertura del file % s .\ n " , path_in ) ;
19 system ( " pause " ) ;
return 1;
21 }
ordina ( mat , n_str ) ;
23 if (! salva_file ( path_out , mat , n_str ) )
{
25 printf ( " Errore in apertura del file % s .\ n " , path_out ) ;
system ( " pause " ) ;
27 return 1;
}
29 free ( mat ) ;
system ( " pause " ) ;
31 return 0;
}
33 char ** leggi_file ( char * path , int * n_stringhe )
{
35 FILE * f ;
char ** matrix ;
37 matrix = NULL ;
* n_stringhe =0;

73
39 if (( f = fopen ( path , " r " ) ) == NULL )
return matrix ;
41 * n_stringhe =1;
matrix = ( char **) malloc ( sizeof ( char *) ) ;
43 matrix [0] = ( char *) malloc ( lenMax * sizeof ( char ) ) ;
while ( fscanf (f , " % s " , matrix [* n_stringhe -1]) != EOF )
45 {
* n_stringhe +=1;
47 matrix = ( char **) realloc ( matrix , * n_stringhe * sizeof ( char *) ) ;
matrix [* n_stringhe -1] = ( char *) malloc ( lenMax * sizeof ( char ) ) ;
49 }
fclose ( f ) ;
51 * n_stringhe -=1;
return matrix ;
53 }
void swap ( char ** mat , int i , int j )
55 {

char * a ;
57 a = mat [ i ];
mat [ i ] = mat [ j ];
59 mat [ j ] = a ;
}
61 void ordina ( char ** mat , int n_str )
{
63 bubble_sort ( mat , n_str ) ;
}
65 void bubble_sort ( char ** vet , int dim )
{
67 int i , j , scambio ;
scambio =1;
69 for ( i = dim -1; i >1 && scambio ; i - -)
{
71 scambio =0;
for ( j =0; j < i ; j ++)
73 {
if ( strcmp ( vet [ j ] , vet [ j +1]) >0)
75 {
swap ( vet , j , j +1) ;
77 scambio =1;
}
79 }
}
81 }

int salva_file ( char * path , char ** vet , int dim )


83 {

FILE * f ;
85 if (( f = fopen ( path , " w " ) ) == NULL )
return 0;
87 int i ;
for ( i =0; i < dim ; i ++)
89 fprintf (f , " % s \ n " , vet [ i ]) ;
fflush ( f ) ;
91 fclose ( f ) ;
return 1;
93 }
 

Listing 6.3: Ordinamento di stringhe con allocazione dinamica

74
Capitolo 7

Esercitazione 4

7.1 Esercizio n. 1: occorrenze di parole


Si scriva un programma in grado di contare le occorrenze di ogni parola, appartenente a un insieme
specificato, all’interno di un generico testo.
Più in dettaglio, l’elenco delle parole da ricercare è memorizzato in un file di testo, in ragione di una
parola per riga del file. La lunghezza delle singole parole è limitata a 20 caratteri, e inoltre il numero
totale di tali parole è indicato sulla prima riga del file stesso.
Le parole cosı̀ specificate devono essere ricercate in un testo memorizzato in un secondo file. Il numero
di righe di questo file è indefinito, ma si può assumere che la lunghezza di ogni riga sia al più pari a 100
caratteri.
Come output, il programma deve riportare su video l’elenco delle parole contenute nel primo file, ciascuna
seguita dal numero di occorrenze con cui compare nel testo contenuto nel secondo file. Si noti che i due
file devono essere letti una volta sola.
Si trascurino i possibili problemi derivanti dall’uso della punteggiatura (ovvero si considerino le stringhe
“parola”, “parola.”, “parola!”, etc., come diverse).

POSSIBILE VARIANTE Si trattino le lettere maiuscole e quelle minuscole come equivalenti (ovvero,
si considerino le stringhe “parola”, “PAROLA”, “ParolA”, etc., come corrispondenti).

# include < stdio .h >
2 # include < stdlib .h >
# include < string .h >
4 # include < ctype .h >
# define occorrenze_path " ricercare . txt "
6 # define file_path " file . txt "
# define max_word_lenght 20
8 # define VARIANTE 1
struct occorrenze
10 {

char * word ;
12 int number ;
};
14 struct occorrenze * read_words ( char * path , int * n_w ) ;
int ricerca ( char * el , struct occorrenze * list , int n_w ) ;
16 void up date_o ccorre nze ( struct occorrenze * list , int n_w , char * path ) ;
void print_occorrenze ( struct occorrenze * list , int n_w ) ;
18 void toLower ( char * str ) ;
int main ()
20 {

struct occorrenze * words ;


22 int n_words ;
words = read_words ( occorrenze_path , & n_words ) ;
24 upd ate_oc corren ze ( words , n_words , file_path ) ;
print_occorrenze ( words , n_words ) ;
26 system ( " pause " ) ;
return 0;
28 }

75
struct occorrenze * read_words ( char * path , int * n_w )
30 {
* n_w =0;
32 struct occorrenze * list ;
int i ;
34 char tmp [ max_word_lenght +1];
FILE * f ;
36 if (( f = fopen ( path , " r " ) ) == NULL )
return NULL ;
38 fscanf (f , " % d " , n_w ) ;
list = ( struct occorrenze *) malloc ((* n_w ) * sizeof ( struct occorrenze ) )
;
40 for ( i =0; i <* n_w ; i ++)
{
42 list [ i ]. number =0;
fscanf (f , " % s " , tmp ) ;
44 list [ i ]. word = strdup ( tmp ) ;
if ( VARIANTE )
46 toLower ( list [ i ]. word ) ;
}
48 fclose ( f ) ;
return list ;
50 }
void upd ate_o ccorre nze ( struct occorrenze * list , int n_w , char * path )
52 {
FILE * f ;
54 if (( f = fopen ( path , " r " ) ) == NULL )
;
56 char * t ;
int n_occorrenza ;
58 t = ( char *) malloc ( max_word_lenght * sizeof ( char ) ) ;
while ( fscanf (f , " % s " , t ) != EOF )
60 {
n_occorrenza = ricerca (t , list , n_w ) ;
62 if ( n_occorrenza != -1)
list [ n_occorrenza ]. number ++;
64 }
fclose ( f ) ;
66 }
int ricerca ( char * el , struct occorrenze * list , int n_w )
68 {
int i ;
70 if ( VARIANTE )
toLower ( el ) ;
72 for ( i =0; i < n_w ; i ++)
{
74 if ( strcmp ( el , list [ i ]. word ) ==0)
return i ;
76 }
return -1;
78 }
void print_occorrenze ( struct occorrenze * list , int n_w )
80 {
int i ;
82 for ( i =0; i < n_w ; i ++)
printf ( " % s - % d occorrenz % c \ n " , list [ i ]. word , list [ i ]. number , (
list [ i ]. number ==1 ? ’a ’ : ’e ’) ) ;
84 }
void toLower ( char * str )
86 {
int i ;
88 for ( i =0; str [ i ]!=0; i ++)

76
str [ i ] = tolower ( str [ i ]) ;
90 }
 

Listing 7.1: Occorrenza di parole

7.2 Esercizio n. 2: indice analitico


Si completi il codice ottenuto dall’esercizio precedente in maniera tale che, oltre al numero di occorrenze, il
programma visualizzi anche le posizioni (intese come numero d’ordine all’interno delle stringhe contenute
nel testo, numerate progressivamente a partire da 1) in cui le parole specificate dal primo file sono state
rintracciate nel secondo file.
Si considerino due casi:

1. il programma limiti la visualizzazione di tali posizioni alle prime 10 occorrenze di ciascuna parola
(qualora una stringa venisse rintracciata più di 10 volte, il programma fornisca il conteggio esatto
delle occorrenze, ma riporti solo le prime 10 posizioni)

2. il programma visualizzi correttamente tutte le posizioni in cui ogni parola del primo file appare nel
secondo

Si noti che nel primo caso è sufficiente utilizzare, per ciascuna parola da rintracciare, un vettore statico, il
cui contenuto può essere generato durante la stessa fase di conteggio delle occorrenze. Nel secondo caso,
invece, a meno di ricorrere a qualche altra struttura dati, si deve utilizzare un vettore dinamico, allocato
e “riempito” solo dopo aver trovato il numero di occorrenze di ciascuna parola.

1 # include < stdio .h >
# include < stdlib .h >
3 # include < string .h >
# include < ctype .h >
5 # define occorrenze_path " ricercare . txt "
# define file_path " file . txt "
7 # define max_word_lenght 20
# define VARIANTE 1
9 # define limita_indice 10
struct occorrenze
11 {

char * word ;
13 int number , n_index ;
int * indice ;
15 };

struct occorrenze * read_words ( char * path , int * n_w ) ;


17 int ricerca ( char * el , struct occorrenze * list , int n_w ) ;
void up date_o ccorre nze ( struct occorrenze * list , int n_w , char * path ) ;
19 void print_occorrenze ( struct occorrenze * list , int n_w ) ;
void toLower ( char * str ) ;
21 int main ()
{
23 struct occorrenze * words ;
int n_words ;
25 words = read_words ( occorrenze_path , & n_words ) ;
upd ate_oc corren ze ( words , n_words , file_path ) ;
27 print_occorrenze ( words , n_words ) ;
system ( " pause " ) ;
29 return 0;
}
31 struct occorrenze * read_words ( char * path , int * n_w )
{
33 * n_w =0;
struct occorrenze * list ;
35 int i ;
char tmp [ max_word_lenght +1];
37 FILE * f ;

77
if (( f = fopen ( path , " r " ) ) == NULL )
39 return NULL ;
fscanf (f , " % d " , n_w ) ;
41 list = ( struct occorrenze *) malloc ((* n_w ) * sizeof ( struct occorrenze ) )
;
for ( i =0; i <* n_w ; i ++)
43 {
list [ i ]. number =0;
45 list [ i ]. n_index =0;
list [ i ]. indice = NULL ;
47 fscanf (f , " % s " , tmp ) ;
list [ i ]. word = strdup ( tmp ) ;
49 if ( VARIANTE )
toLower ( list [ i ]. word ) ;
51 }
fclose ( f ) ;
53 return list ;
}
55 void upd ate_o ccorre nze ( struct occorrenze * list , int n_w , char * path )
{
57 FILE * f ;
if (( f = fopen ( path , " r " ) ) == NULL )
59 ;
char * t ;
61 int n_occorrenza , n_word =0;
t = ( char *) malloc ( max_word_lenght * sizeof ( char ) ) ;
63 while ( fscanf (f , " % s " , t ) != EOF )
{
65 n_word ++;
n_occorrenza = ricerca (t , list , n_w ) ;
67 if ( n_occorrenza != -1)
{
69 list [ n_occorrenza ]. number ++;
if ( limita_indice >0)
71 {
if ( list [ n_occorrenza ]. n_index != limita_indice )
73 {
list [ n_occorrenza ]. n_index ++;
75 list [ n_occorrenza ]. indice = ( int *) realloc ( list [
n_occorrenza ]. indice , ( list [ n_occorrenza ]. n_index
) * sizeof ( int ) ) ;
list [ n_occorrenza ]. indice [ list [ n_occorrenza ]. n_index
-1] = n_word ;
77 }
}
79 else
{
81 list [ n_occorrenza ]. n_index ++;
list [ n_occorrenza ]. indice = ( int *) realloc ( list [
n_occorrenza ]. indice , ( list [ n_occorrenza ]. n_index ) *
sizeof ( int ) ) ;
83 list [ n_occorrenza ]. indice [ list [ n_occorrenza ]. n_index -1]
= n_word ;
}
85 }
}
87 fclose ( f ) ;
}
89 int ricerca ( char * el , struct occorrenze * list , int n_w )
{
91 int i ;
if ( VARIANTE )

78
93 toLower ( el ) ;
for ( i =0; i < n_w ; i ++)
95 {
if ( strcmp ( el , list [ i ]. word ) ==0)
97 return i ;
}
99 return -1;
}
101 void print_occorrenze ( struct occorrenze * list , int n_w )
{
103 int i , j ;
for ( i =0; i < n_w ; i ++)
105 {
printf ( " % s - % d occorrenz % c " , list [ i ]. word , list [ i ]. number , (
list [ i ]. number ==1 ? ’a ’ : ’e ’) ) ;
107 if ( list [ i ]. n_index >0)
{
109 printf ( " parol % c : " , ( list [ i ]. n_index ==1 ? ’a ’ : ’e ’) ) ;
for ( j =0; j < list [ i ]. n_index ; j ++)
111 printf ( " % d " , list [ i ]. indice [ j ]) ;
}
113 printf ( " \ n " ) ;
}
115 }

void toLower ( char * str )


117 {

int i ;
119 for ( i =0; str [ i ]!=0; i ++)
str [ i ] = tolower ( str [ i ]) ;
121 }
 

Listing 7.2: Occorrenze parole con indice analitico

7.3 Esercizio n. 3: prodotto di matrici


Due matrici di interi (m1 e m2) sono memorizzate in due file con formato identico:
• la prima riga del file contiene le dimensioni (numero di righe r e di colonne c) della matrice
• le r righe successive contengono i c valori che appartengono alla riga corrente della matrice
Si scriva un programma che:
• acquisisca dinamicamente le due matrici, verificando che le loro dimensioni permettano di eseguire
il prodotto m1 · m2
• calcoli la matrice prodotto (allocando dinamicamente anch’essa)
• memorizzi tale matrice in un terzo file, con formato simile a quello di ingresso
I nomi dei tre file siano passati al programma sulla riga di comando.

1 # include < stdio .h >
# include < stdlib .h >
3 struct matrix
{
5 int r , c ;
int ** value ;
7 };

void read_matrix ( struct matrix * mat , FILE * f ) ;


9 void print_matrix ( struct matrix mat , FILE * f ) ;
int is_multiplicate ( struct matrix mat1 , struct matrix mat2 ) ;
11 void multiplicate ( struct matrix * result , struct matrix * mat1 , struct
matrix * mat2 ) ;

79
int row_for_colums ( struct matrix * mat1 , struct matrix * mat2 , int r , int
c);
13 int main ( int argc , char * argv [])
{
15 if ( argc <4)
{
17 printf ( " Parametri insufficienti : % s matrix1_file matrix2_file
result_file \ n " , argv [0]) ;
return EXIT_FAILURE ;
19 }
FILE * f1 , * f2 , * f3 ;
21 if (( f1 = fopen ( argv [1] , " r " ) ) == NULL )
{
23 printf ( " Errore apertura % s \ n " , argv [1]) ;
return EXIT_FAILURE ;
25 }
if (( f2 = fopen ( argv [2] , " r " ) ) == NULL )
27 {
printf ( " Errore apertura % s \ n " , argv [2]) ;
29 fclose ( f1 ) ;
return EXIT_FAILURE ;
31 }
if (( f3 = fopen ( argv [3] , " w " ) ) == NULL )
33 {
printf ( " Errore apertura % s \ n " , argv [3]) ;
35 fclose ( f1 ) ;
fclose ( f2 ) ;
37 return EXIT_FAILURE ;
}
39 struct matrix mat [3]={{0}};
read_matrix (& mat [0] , f1 ) ;
41 read_matrix (& mat [1] , f2 ) ;
if ( is_multiplicate ( mat [0] , mat [1]) )
43 multiplicate (& mat [2] , & mat [0] , & mat [1]) ;
print_matrix ( mat [2] , f3 ) ;+
45 fclose ( f1 ) ;
fclose ( f2 ) ;
47 fflush ( f3 ) ;
fclose ( f3 ) ;
49 return 0;
}
51 void read_matrix ( struct matrix * mat , FILE * f )
{
53 fscanf (f , " % d % d " , & mat - >r , & mat - > c ) ;
mat - > value = ( int **) malloc (( mat - > r ) * sizeof ( int *) ) ;
55 int i , j ;
for ( i =0; i < mat - > r ; i ++)
57 {
mat - > value [ i ] = ( int *) malloc (( mat - > c ) * sizeof ( int ) ) ;
59 for ( j =0; j < mat - > c ; j ++)
fscanf (f , " % d " , & mat - > value [ i ][ j ]) ;
61 }
}
63 void print_matrix ( struct matrix mat , FILE * f )
{
65 fprintf (f , " % d % d \ n " , mat .r , mat . c ) ;
int i , j ;
67 for ( i =0; i < mat . r ; i ++)
{
69 for ( j =0; j < mat . c ; j ++)
fprintf (f , " % d " , mat . value [ i ][ j ]) ;
71 fprintf (f , " \ n " ) ;

80
}
73 }
int is_multiplicate ( struct matrix mat1 , struct matrix mat2 )
75 {

return mat1 . c == mat2 . r ;


77 }

void multiplicate ( struct matrix * result , struct matrix * mat1 , struct


matrix * mat2 )
79 {

result - > r = mat1 - > r ;


81 result - > c = mat2 - > c ;
int i , j ;
83 result - > value = ( int **) malloc (( result - > r ) * sizeof ( int *) ) ;
for ( i =0; i < result - > r ; i ++)
85 {
result - > value [ i ] = ( int *) malloc (( result - > c ) * sizeof ( int ) ) ;
87 for ( j =0; j < result - > c ; j ++)
result - > value [ i ][ j ] = row_for_colums ( mat1 , mat2 , i , j ) ;
89 }
}
91 int row_for_colums ( struct matrix * mat1 , struct matrix * mat2 , int r , int
c)
{
93 int res , i ;
for ( i = res =0; i < mat1 - > c ; i ++)
95 res += mat1 - > value [ r ][ i ]* mat2 - > value [ i ][ c ];
return res ;
97 }
 

Listing 7.3: Prodotto di matrici

81
82
Capitolo 8

Esercitazione 5

8.1 Esercizio n. 1: Generazione di numeri binari.


Si realizzi un programma C che, acquisito da tastiera un numero N strettamente positivo, generi e
visualizzi a video (mediante una funzione ricorsiva) tutti i numeri binari di N bit.

Vincoli e Restrizioni La generazione dei numeri binari deve essere ottenuta senza eseguire alcuna
conversione di valori da decimale a binario.

Suggerimento Si utilizzi un vettore di N interi per rappresentare il numero binario (ogni elemento del
vettore corrisponda a un bit del numero binario). La funzione ricorsiva deve, a ogni passo della ricorsione,
assegnare il valore di uno dei bit del numero binario (si possono avere solo i casi 0 e 1). Per ognuno dei
due possibili valori, si deve poi ricorrere per “sistemare” i bit successivi. Al termine della ricorsione,
quindi, occorre visualizzare il numero cosı̀ ottenuto.

1 # include < stdio .h >
# include < malloc .h >
3 # include < string .h >
void disposizioni ( char * prefix , int n ) ;
5 void s ta m p a_ d i sp o s iz i o ni ( int n ) ;
int main ()
7 {

int n ;
9 printf ( " Inserisci il numero di bit :\ t " ) ;
scanf ( " % d " , & n ) ;
11 s t am p a _d i s po s i zi o n i ( n ) ;
return 0;
13 }

void disposizioni ( char * prefix , int n )


15 {

int s ;
17 if ( n ==0)
printf ( " % s \ n " , prefix ) ;
19 else
{
21 s = strlen ( prefix ) ;
prefix [ s ]= ’0 ’;
23 prefix [ s +1]= ’ \0 ’;
disposizioni ( prefix , n -1) ;
25 prefix [ s ]= ’1 ’;
prefix [ s +1]= ’ \0 ’;
27 disposizioni ( prefix , n -1) ;
}
29 }

void s ta m p a_ d i sp o s iz i o ni ( int n )
31 {

char * str ;
33 if (( str = ( char *) malloc (( n +1) * sizeof ( char ) ) ) == NULL )

83
return ;
35 str [0]= ’ \0 ’;
disposizioni ( str , n ) ;
37 }
 

Listing 8.1: Generazione di numeri binari

8.2 Esercizio n. 2: Sviluppo di un sistema del totocalcio.


Un file di testo contiene la descrizione di un sistema relativo a una schedina del gioco del totocalcio. Il
file è composto da N righe, in ognuna delle quali si possono trovare uno, due oppure tutti e tre i simboli
per i possibili risultati (che sono 1, 2 e X). Si scriva un programma C che, acquisito il contenuto del
file in un’opportuna struttura dati interna, generi tutte le colonne della schedina appartenenti al sistema
letto, espandendo tutte le coppie e le triple di simboli e generando tutte le possibili combinazioni. Tali
combinazioni siano, quindi, memorizzate in un secondo file, in ragione di una per riga.
Si noti che il valore di N non è noto a priori, nè esso compare in alcun modo nel file di ingresso: tale
valore deve essere opportunamente dedotto dal programma a partire dal contenuto del file stesso.

1 # include < stdio .h >
# include < string .h >
3 # include < malloc .h >
# define PATH " sistema . txt "
5 char ** contenuto_file ( char * path , int * n_righe ) ;
void stampa_colonne ( char * path , char ** file , int n_righe ) ;
7 void genera_e_stampa ( FILE * fp , char ** file , int n_righe , char * str ) ;
void print_reversed ( char * str ) ;
9 int main ()
{
11 char ** file ;
int n_righe ;
13 if (( file = contenuto_file ( PATH , & n_righe ) ) == NULL )
return -1;
15 stampa_colonne ( " stdout " , file , n_righe ) ;
free ( file ) ;
17 return 0;
}
19 char ** contenuto_file ( char * path , int * n_righe )
{
21 int i ;
char ** res , tmp [5];
23 FILE * f ;
* n_righe =0;
25 if (( f = fopen ( path , " r " ) ) == NULL )
return NULL ;
27 for (; fgets ( tmp , 5 , f ) != NULL ; (* n_righe ) ++)
;
29 rewind ( f ) ;
if (( res =( char **) malloc ((* n_righe ) * sizeof ( char *) ) ) == NULL )
31 {
* n_righe =0;
33 return NULL ;
}
35 for ( i =0; i <(* n_righe ) ; i ++)
{
37 fscanf (f , " % s " , tmp ) ;
res [ i ] = strdup ( tmp ) ;
39 if ( res [ i ]== NULL )
{
41 for (i - -; i >=0; i - -)
free ( res [ i ]) ;
43 free ( res ) ;

84
* n_righe =0;
45 return NULL ;
}
47 }
return res ;
49 }
void stampa_colonne ( char * path , char ** file , int n_righe )
51 {

if ( path == NULL || file == NULL || n_righe ==0)


53 return ;
FILE * fp ;
55 char * str ;
if (( str = ( char *) malloc (( n_righe +1) * sizeof ( char ) ) ) == NULL )
57 return ;
if ( strcmp ( path , " stdout " ) ==0)
59 fp = stdout ;
else
61 if (( fp = fopen ( path , " w " ) ) == NULL )
return ;
63 str [0]= ’ \0 ’;
genera_e_stampa ( fp , file , n_righe , str ) ;
65 if ( fp == stdout )
fclose ( fp ) ;
67 free ( str ) ;
}
69 void genera_e_stampa ( FILE * fp , char ** file , int n_righe , char * prefix )
{
71 if ( n_righe ==0)
print_reversed ( prefix ) ;
73 else
{
75 int i , s = strlen ( file [ n_righe -1]) , sp = strlen ( prefix ) ;
for ( i =0; i < s ; i ++)
77 {
prefix [ sp ] = file [ n_righe -1][ i ];
79 prefix [ sp +1] = ’ \0 ’;
genera_e_stampa ( fp , file , n_righe -1 , prefix ) ;
81 }
}
83 }

void print_reversed ( char * str )


85 {

int i ;
87 for ( i = strlen ( str ) -1; i >=0; i - -)
printf ( " % c " , str [ i ]) ;
89 printf ( " \ n " ) ;
}
 

Listing 8.2: Sviluppo di un sistema del totocalcio

8.3 Esercizio n. 3: calcolo del determinante di una matrice.


Un file contiene una matrice quadrata di interi di lato N . Si scriva un programma in grado di leggere il
file in questione e di calcolare il valore del determinante della matrice in esso contenuta, visualizzando
tale valore sul video.
Si ricorda che il determinante di una matrice quadrata di lato 1 coincide con il valore dell’unico ele-
mento della matrice stessa, mentre, per valori superiori del “lato”, esso può essere calcolato (in maniera
equivalente) mediante una delle seguenti formule:
N
X N
X
∆= (−1)i+j · mij · ∆ij j ∈ [1, N ] ∆= (−1)i+j · mij · ∆ij i ∈ [1, N ]
i=1 i=1

85
essendo mij l’elemento della matrice in posizione [i, j] e ∆ij il determinante della matrice che si ottiene
cancellando la riga i e la colonna j dalla matrice di partenza.
Si noti che il valore di N non è noto: esso deve essere dedotto dal contenuto del file di ingresso.

# include < stdio .h >
2 # include < string .h >
# include < malloc .h >
4 # define PATH " matrice . txt "
float ** leggi_file ( char * path , int * dim ) ;
6 float determinante ( float ** matrice , int dim ) ;
float ** c o m p l e m e n t o _ a l g e b r i c o ( float ** matrice , int dim , int r , int c ) ;
8 int main ()
{
10 float ** matrice ;
int dim ;
12 if (( matrice = leggi_file ( PATH , & dim ) ) == NULL )
return -1;
14 printf ( " Il determinante della matrice nel file e ’ % f \ n " ,
determinante ( matrice , dim ) ) ;
return 0;
16 }

float ** c o m p l e m e n t o _ a l g e b r i c o ( float ** matrice , int dim , int r , int c )


18 {

if ( matrice == NULL || r <0 || c <0 || r >= dim || c >= dim )


20 return NULL ;
float ** complemento ;
22 if (( complemento =( float **) malloc (( dim -1) * sizeof ( float *) ) ) == NULL )
return 0;
24 int i , j ;
int in , jn ;
26 for ( i = in =0; i < dim ; i ++)
{
28 if ( i != dim -1)
if (( complemento [ i ]=( float *) malloc (( dim -1) * sizeof ( float ) ) ) ==
NULL )
30 return 0;
if ( i == r )
32 continue ;
for ( j = jn =0; j < dim ; j ++)
34 {
if ( j == c )
36 continue ;
complemento [ in ][ jn ]= matrice [ i ][ j ];
38 jn ++;
if ( jn %( dim -1) ==0)
40 {
jn =0;
42 in ++;
}
44 }
}
46 return complemento ;
}
48 float determinante ( float ** matrice , int dim )
{
50 int i ;
if ( dim ==1)
52 return matrice [0][0];
float res =0;
54 float ** mtemp ;
for ( i =0; i < dim ; i ++)
56 {

86
mtemp = c o m p l e m e n t o _ a l g e b r i c o ( matrice , dim , 0 , i ) ;
58 if ( mtemp == NULL )
exit ( EXIT_FAILURE ) ;
60 if ( i %2==0)
res += matrice [0][ i ]* determinante ( mtemp , dim -1) ;
62 else
res -= matrice [0][ i ]* determinante ( mtemp , dim -1) ;
64 free ( mtemp ) ;
}
66 return res ;
}
68 float ** leggi_file ( char * path , int * dim )
{
70 float ** mat ;
FILE * fp ;
72 * dim =0;
if (( fp = fopen ( path , " r " ) ) == NULL )
74 return NULL ;
int i , j ;
76 char c ;
while (( c = fgetc ( fp ) ) != EOF )
78 if ( c == ’\ n ’)
(* dim ) ++;
80 rewind ( fp ) ;
if (( mat = ( float **) malloc ((* dim ) * sizeof ( float *) ) ) == NULL )
82 {
fclose ( fp ) ;
84 return NULL ;
}
86 for ( i =0; i <(* dim ) ; i ++)
{
88 if (( mat [ i ] = ( float *) malloc ((* dim ) * sizeof ( float ) ) ) == NULL )
{
90 for (i - -; i >=0; i - -)
free ( mat [ i ]) ;
92 free ( mat ) ;
return NULL ;
94 }
for ( j =0; j <(* dim ) ; j ++)
96 fscanf ( fp , " % f " , & mat [ i ][ j ]) ;
}
98 fclose ( fp ) ;
return mat ;
100 }
 

Listing 8.3: Determinante di una matrice

87
88
Capitolo 9

Esercitazione 6

9.1 Esercizio n. 1: Punti del piano.


Si scriva un programma che:

• acquisisca da file un numero indefinito di punti del piano cartesiano, memorizzandoli in un vettore
di strutture (“punto”) allocato dinamicamente. Le coordinate dei punti sono riportate nel file di
ingresso su righe consecutive, in ragione di una riga del file per punto cartesiano.

• determini:

– la coppia di punti più vicini tra loro, visualizzandone le coordinate.


– la coppia di punti più lontani tra loro, visualizzandone le coordinate.
– il numero di segmenti, ottenuti congiungendo due qualsiasi punti dell’insieme letto, che hanno
lunghezza inferiore a un valore d inserito dall’utente.

• ordini (mediante un algoritmo noto) i punti memorizzati nel vettore per valori crescenti di distanza
dall’origine del piano. Il vettore cosı̀ ordinato sia salvato su un file di uscita.

Vincoli e restrizioni Si richiede che il programma venga realizzato su più file, con struttura modulare,
come suggerito nel §3.1 del Sedgewick. Più in dettaglio, si preveda un modulo per la gestione del tipo
“punto”, composto da interfaccia e implementazione, contenente la dichiarazione del tipo stesso e le
funzioni base per operare su questo tipo di dato (ad esempio, per eseguire la lettura di un punto, calcolare
la distanza tra due punti, visualizzare le coordinate di un dato punto, etc.), e un modulo “client” che,
utilizzando tali funzioni, soddisfi le richieste dell’esercizio.

# include < stdio .h >
2 # include " point . h "
# define path_in " punti . txt "
4 # define path_out " punti_ordinati . txt "
void vfree ( point ** lista , int n_punti ) ;
6 point ** re ad _p oi nt _b y_ fi le ( char * path , int * n_p ) ;
int * c o p p i a _ m i n i m a _ d i s t a n z a ( point ** lista , int n_punti ) ;
8 int * c o p p i a _ m a s s i m a _ d i s t a n z a ( point ** lista , int n_punti ) ;
int s e g m e n t i _ d i s t a n z a _ m i n o r e ( point ** lista , int n_punti , float d ) ;
10 int ordina ( point ** lista , int n_punti , char * path ) ;
void quickSort ( point ** lista , int l , int r ) ;
12 int partition ( point ** lista , int l , int r ) ;
int main ()
14 {

int * min , * max , n_segmenti ;


16 point ** lista ;
int n_punti ;
18 float d ;
if (( lista = r ea d_ po in t_ by _f il e ( path_in , & n_punti ) ) == NULL )
20 return 1;
do
22 {
printf ( " Introduci il parametro d :\ t " ) ;

89
24 scanf ( " % f " , & d ) ;
} while (d <=0) ;
26 if (( min = c o p p i a _ m i n i m a _ d i s t a n z a ( lista , n_punti ) ) == NULL )
{
28 printf ( " Errore allocazione dimanica della memoria .\ n " ) ;
vfree ( lista , n_punti ) ;
30 }
if (( max = c o p p i a _ m a s s i m a _ d i s t a n z a ( lista , n_punti ) ) == NULL )
32 {
printf ( " Errore allocazione dimanica della memoria .\ n " ) ;
34 free ( min ) ;
vfree ( lista , n_punti ) ;
36 }
n_segmenti = s e g m e n t i _ d i s t a n z a _ m i n o r e ( lista , n_punti , d ) ;
38 printf ( " La coppia dei punti piu ’ vicini tra loro e ’ composta dai
punti \ n \ t " ) ;
pPrint_to_stream ( lista [ min [0]] , stdout ) ;
40 printf ( " \ n \ t " ) ;
pPrint_to_stream ( lista [ min [1]] , stdout ) ;
42 printf ( " \ n " ) ;
printf ( " La coppia dei punti piu ’ lontani tra loro e ’ composta dai
punti \ n \ t " ) ;
44 pPrint_to_stream ( lista [ max [0]] , stdout ) ;
printf ( " \ n \ t " ) ;
46 pPrint_to_stream ( lista [ max [1]] , stdout ) ;
printf ( " \ n " ) ;
48 if ( n_segmenti ==0)
printf ( " Non sono presenti segmenti con lunghezza inferiore a % f \
n", d);
50 else if ( n_segmenti ==1)
printf ( " E ’ presente un segmento con lunghezza inferiore a % f \ n " ,
d);
52 else
printf ( " Sono presenti % d segmenti con lunghezza inferiore a % f \ n
" , n_segmenti , d ) ;
54 ordina ( lista , n_punti , path_out ) ;
free ( min ) ;
56 free ( max ) ;
vfree ( lista , n_punti ) ;
58 return 0;
}
60 void vfree ( point ** lista , int n_punti )
{
62 int i ;
for ( i =0; i < n_punti ; i ++)
64 free ( lista [ i ]) ;
free ( lista ) ;
66 }
point ** re ad _p oi nt _b y_ fi le ( char * path , int * n_p )
68 {
FILE * f ;
70 point ** res ;
char c ;
72 int i ;
* n_p =0;
74 if (( f = fopen ( path , " r " ) ) == NULL )
{
76 printf ( " Errore lettura file di ingresso % s \ n " , path ) ;
return NULL ;
78 }
while (( c = fgetc ( f ) ) != EOF )
80 if ( c == ’\ n ’)

90
(* n_p ) ++;
82 if (* n_p ==0)
{
84 printf ( " Non sono presenti punti nel file .\ n " ) ;
return NULL ;
86 }
if (( res = ( point **) malloc ((* n_p ) * sizeof ( point *) ) ) == NULL )
88 {
printf ( " Errore allocazione dinamica della memoria .\ n " ) ;
90 fclose ( f ) ;
return NULL ;
92 }
rewind ( f ) ;
94 for ( i =0; i <(* n_p ) ; i ++)
if (( res [ i ]= pRead_by_stream ( f ) ) == NULL )
96 {
printf ( " Errore allocazione dinamica della memoria .\ n " ) ;
98 for (i - -; i >=0; i - -)
free ( res [ i ]) ;
100 free ( res ) ;
return NULL ;
102 }
return res ;
104 }
int * c o p p i a _ m i n i m a _ d i s t a n z a ( point ** lista , int n_punti )
106 {
int i , j , * res ;
108 double dmin = -1.0 , dcorr ;
if (( res =( int *) malloc (2* sizeof ( int ) ) ) == NULL )
110 {
printf ( " Errore allocazione dinamica della memoria .\ n " ) ;
112 return NULL ;
}
114 for ( i =0; i < n_punti -1; i ++)
for ( j = i +1; j < n_punti ; j ++)
116 {
dcorr = pDistanza ( lista [ i ] , lista [ j ]) ;
118 if ( dmin == -1.0 || dcorr < dmin )
{
120 res [0] = i ;
res [1] = j ;
122 dmin = dcorr ;
}
124 }
return res ;
126 }
int * c o p p i a _ m a s s i m a _ d i s t a n z a ( point ** lista , int n_punti )
128 {
int i , j , * res ;
130 double dmax = -1.0 , dcorr ;
if (( res =( int *) malloc (2* sizeof ( int ) ) ) == NULL )
132 {
printf ( " Errore allocazione dinamica della memoria .\ n " ) ;
134 return NULL ;
}
136 for ( i =0; i < n_punti -1; i ++)
{
138 for ( j = i +1; j < n_punti ; j ++)
{
140 dcorr = pDistanza ( lista [ i ] , lista [ j ]) ;
{
142 res [0] = i ;

91
res [1] = j ;
144 dmax = dcorr ;
}
146 }
}
148 return res ;
}
150 int s e g m e n t i _ d i s t a n z a _ m i n o r e ( point ** lista , int n_punti , float d )
{
152 d = d*d;
int i , j , res ;
154 for ( i = res =0; i < n_punti -1; i ++)
for ( j = i +1; j < n_punti ; j ++)
156 {
if (( lista [ i ] - >x - lista [ j ] - > x ) *( lista [ i ] - >x - lista [ j ] - > x ) +(
lista [ i ] - >y - lista [ j ] - > y ) *( lista [ i ] - >y - lista [ j ] - > y ) <d )
158 res ++;
}
160 return res ;
}
162 int ordina ( point ** lista , int n_punti , char * path )
{
164 FILE * f ;
if (( f = fopen ( path , " w " ) ) == NULL )
166 {
printf ( " Errore apertura file di output % s \ n " , path ) ;
168 return 0;
}
170 quickSort ( lista , 0 , n_punti -1) ;
int i ;
172 for ( i =0; i < n_punti ; i ++)
{
174 pPrint_to_stream ( lista [ i ] , f ) ;
fprintf (f , " \ n " ) ;
176 }
fflush ( f ) ;
178 fclose ( f ) ;
return 1;
180 }
void quickSort ( point ** lista , int l , int r )
182 {
int p ;
184 if (l < r )
{
186 p = partition ( lista , l , r ) ;
quickSort ( lista , l , p ) ;
188 quickSort ( lista , p +1 , r ) ;
}
190 }
int partition ( point ** lista , int l , int r )
192 {
point * temp ;
194 int i , j ;
i = l -1;
196 j = r +1;
while (i < j )
198 {
while ( pCompareModulo ( lista [++ i ] , lista [ l ]) <0)
200 ;
while ( pCompareModulo ( lista [ - - j ] , lista [ l ]) >0)
202 ;
if (i < j )

92
204 {
temp = lista [ i ];
206 lista [ i ] = lista [ j ];
lista [ j ] = temp ;
208 }
}
210 return j ;
}
 

Listing 9.1: Punti del piano (client.c)

# include < malloc .h >
2 # include < math .h >
typedef struct point_t
4 {

float x , y ;
6 } point ;
point * pRead_by_stream ( FILE * stream ) ;
8 point * pRead_by_string ( char * str ) ;
float pDistanza ( point * p1 , point * p2 ) ;
10 float pModulo ( point * p ) ;
int pCompareModulo ( point * p1 , point * p2 ) ;
12 int p C o m p a r e M o d u l o _ c o n _ r i f e r i m e n t o ( point * p1 , point * p2 , point * rif ) ;
int pPrint_to_stream ( point *p , FILE * f ) ;
14 int p P r i n t _ t o _ s t r e a m _ w i t h _ p r e c i s i o n ( point *p , FILE *f , int cifre ) ;
 

Listing 9.2: Punti del piano (point.h)

1 # include < stdio .h >
# include " point . h "
3 point ORIGINE = {0 , 0};
point * pRead_by_stream ( FILE * stream )
5 {

if ( stream == NULL )
7 return NULL ;
point * res ;
9 if (( res = malloc ( sizeof ( point ) ) ) == NULL )
return NULL ;
11 fscanf ( stream , " % f % f " , & res - >x , & res - > y ) ;
return res ;
13 }

point * pRead_by_string ( char * str )


15 {

if ( str == NULL )
17 return NULL ;
point * res ;
19 if (( res = malloc ( sizeof ( point ) ) ) == NULL )
return NULL ;
21 sscanf ( str , " % f % f " , & res - >x , & res - > y ) ;
return res ;
23 }

float pDistanza ( point * p1 , point * p2 )


25 {

double dx , dy ;
27 dx = p1 - >x - p2 - > x ;
dy = p1 - >y - p2 - > y ;
29 return sqrt (( dx * dx ) +( dy * dy ) ) ;
}
31 float pModulo ( point * p )
{
33 return pDistanza (p , & ORIGINE ) ;
}

93
35 int pCompareModulo ( point * p1 , point * p2 )
{
37 return pModulo ( p1 ) - pModulo ( p2 ) ;
}
39 int p C o m p a r e M o d u l o _ c o n _ r i f e r i m e n t o ( point * p1 , point * p2 , point * rif )
{
41 return pDistanza ( p1 , rif ) - pDistanza ( p2 , rif ) ;
}
43 int pPrint_to_stream ( point *p , FILE * f )
{
45 return p P r i n t _ t o _ s t r e a m _ w i t h _ p r e c i s i o n (p , f , 2) ;
}
47 int p P r i n t _ t o _ s t r e a m _ w i t h _ p r e c i s i o n ( point *p , FILE *f , int cifre )
{
49 if ( f == NULL )
return 0;
51 fprintf (f , " %.* f %.* f " , cifre , p - >x , cifre , p - > y ) ;
return 1;
53 }
 

Listing 9.3: Punti del piano (point.c)

9.2 Esercizio n. 2: Ricerche dicotomiche.


Un file contiene un insieme di dati, in numero ignoto, di un generico tipo item. Supponendo che essi
risultino ordinati, si scriva un programma modulare che, memorizzato il contenuto del file in un’opportuna
struttura dati interna, permetta all’utente di effettuare delle ricerche dicotomiche.
Più in dettaglio, il programma deve essere composto da due moduli:
• l’interfaccia utente (il main), contenente anche la funzione di ricerca binaria.
• un modulo con le funzioni per la gestione di basso livello dei dati.
Si supponga che il tipo item possa essere (a scelta del programmatore):
• un numero intero.
• una stringa di lunghezza massima pari a 50 caratteri.

Vincoli e restrizioni Si richiede che il modulo dei dati contenga le funzioni per manipolare solo un
singolo dato (o, al più, due dati nel caso del confronto), senza “sapere” che tali dati sono memorizzati in
un vettore. Ad esempio, è illecito definire in questo modulo una funzione che esegua per intero la lettura
del file di ingresso e restituisca il vettore contenente i dati. Tale operazione deve essere opportunamente
eseguita all’interno del modulo principale (il main).

1 # include < stdio .h >
# include < string .h >
3 # include " item . h "
# define path_file " item . txt "
5 # define MAX 10
void v_free ( ITEM ** lista , int n_item ) ;
7 int binary_find ( ITEM ** lista , int l , int r , ITEM * el ) ;
ITEM ** leggi_da_file ( char * path , int * n_item ) ;
9 int main ()
{
11 int n_item , index , fine ;
ITEM ** lista , * iAtt ;
13 char cmd [ MAX ];
if (( lista = leggi_da_file ( path_file , & n_item ) ) == NULL )
15 return 1;
fine =0;
17 while (! fine )
{

94
19 scanf ( " % s " , cmd ) ;
if ( strcmp ( cmd , " find " ) ==0)
21 {
iAtt = iRead_By_stream ( stdin ) ;
23 if ( iAtt != NULL )
{
25 index = binary_find ( lista , 0 , n_item -1 , iAtt ) ;
if ( index == -1)
27 printf ( " Elemento non trovato .\ n " ) ;
else
29 printf ( " Elemento trovato in posizione % d .\ n " , index )
;
free ( iAtt ) ;
31 }
}
33 else if ( strcmp ( cmd , " exit " ) ==0)
fine =1;
35 else
printf ( " Comando non riconosciuto .\ n " ) ;
37 }
v_free ( lista , n_item ) ;
39 return 0;
}
41 void v_free ( ITEM ** lista , int n_item )
{
43 if ( lista == NULL )
return ;
45 for ( n_item - -; n_item >=0; n_item - -)
free ( lista [ n_item ]) ;
47 free ( lista ) ;
}
49 int binary_find ( ITEM ** lista , int l , int r , ITEM * el )
{
51 int m ;
if (l > r )
53 return -1;
m = ( l + r ) /2;
55 if ( iCompare ( el , lista [ m ]) ==0)
return m ;
57 else if ( iCompare ( el , lista [ m ]) <0)
return binary_find ( lista , l , m -1 , el ) ;
59 else
return binary_find ( lista , m +1 , r , el ) ;
61 }
ITEM ** leggi_da_file ( char * path , int * n_item )
63 {
FILE * f ;
65 ITEM ** res ;
char c ;
67 int i ;
* n_item =0;
69 if ( path == NULL )
return NULL ;
71 if (( f = fopen ( path , " r " ) ) == NULL )
{
73 printf ( " Errore apertura file .\ n " ) ;
return NULL ;
75 }
while (( c = fgetc ( f ) ) != EOF )
77 if ( c == ’\ n ’)
(* n_item ) ++;
79 rewind ( f ) ;

95
if (* n_item ==0)
81 {
printf ( " Non sono presenti item nel file % s \ n " , path ) ;
83 fclose ( f ) ;
return NULL ;
85 }
if (( res =( ITEM **) malloc ((* n_item ) * sizeof ( ITEM *) ) ) == NULL )
87 {
printf ( " Errore allocazione dinamica della memoria .\ n " ) ;
89 * n_item = 0;
fclose ( f ) ;
91 return NULL ;
}
93 for ( i =0; i <(* n_item ) ; i ++)
{
95 res [ i ] = iRead_By_stream ( f ) ;
if ( res [ i ]== NULL )
97 {
printf ( " Errore allocazione dinamica della memoria .\ n " ) ;
99 for (i - -; i >=0; i - -)
free ( res [ i ]) ;
101 free ( res ) ;
* n_item =0;
103 fclose ( f ) ;
return NULL ;
105 }
}
107 fclose ( f ) ;
return res ;
109 }
 

Listing 9.4: Ricerche dicotomiche (client.c)

1 # include < malloc .h >
typedef struct
3 {

int num ;
5 } ITEM ;
ITEM * iRead_By_stream ( FILE * stream ) ;
7 ITEM * iRead_By_string ( char * string ) ;
int iPrint ( ITEM *i , FILE * stream ) ;
9 int iCompare ( ITEM * i1 , ITEM * i2 ) ;
 

Listing 9.5: Ricerche dicotomiche (item.h)

1 # include < stdio .h >
# include " item . h "
3 ITEM * iRead_By_stream ( FILE * stream )
{
5 if ( stream == NULL )
return NULL ;
7 ITEM * res ;
if (( res =( ITEM *) malloc ( sizeof ( ITEM ) ) ) == NULL )
9 return NULL ;
fscanf ( stream , " % d " , & res - > num ) ;
11 return res ;
}
13 ITEM * iRead_By_string ( char * string )
{
15 if ( string == NULL )
return NULL ;
17 ITEM * res ;

96
if (( res =( ITEM *) malloc ( sizeof ( ITEM ) ) ) == NULL )
19 return NULL ;
sscanf ( string , " % d " , & res - > num ) ;
21 return res ;
}
23 int iPrint ( ITEM *i , FILE * stream )
{
25 if ( i == NULL || stream == NULL )
return 0;
27 fprintf ( stream , " % d " , i - > num ) ;
return 1;
29 }

int iCompare ( ITEM * i1 , ITEM * i2 )


31 {

return i1 - > num - i2 - > num ;


33 }
 

Listing 9.6: Ricerche dicotomiche (item.c)

9.3 Esercizio n. 3: Confronto tra algoritmi di ordinamento.


Si realizzi un programma modulare che:

• legga due numeri positivi M ed N .

• generi casualmente N dati, memorizzandoli in un vettore.

• ordini tale vettore in maniera “crescente”, utilizzando uno dei seguenti algoritmi:

– merge sort
– quick sort
– heap sort

• ripeta la generazione casuale e l’ordinamento per M volte.

Si confrontino le prestazioni dei tre algoritmi per i valori di M = 10, 100, 1000 e di N = 1000, 5000, 10000,
misurandone i tempi di esecuzione. Si confrontino, inoltre, le prestazioni di tali algoritmi rispetto a quelle
degli algoritmi iterativi.
Il programma deve essere composto da tre moduli distinti:

• l’interfaccia utente (il main).

• un modulo contenente le funzioni di ordinamento.

• un modulo con le funzioni per la gestione di basso livello dei dati.

Si supponga che il tipo di dato base “item” memorizzato nel vettore possa essere (a scelta):

• una stringa di lunghezza massima pari a 50 caratteri.

• un numero intero.

# include < stdio .h >
2 # include < time .h >
# include " sort . h "
4 void vfree ( ITEM ** lista , int n ) ;
ITEM ** my_alloca ( int n_el ) ;
6 void genera_casuale ( ITEM ** vet , int n_el ) ;
int main ()
8 {

ITEM ** lista ;
10 int t0 , t1 ;
int i , j , M ;
12 int vN [] = {1000 , 5000 , 10000};

97
int vM [] = {10 , 100 , 1000};
14 printf ( " N M merge_sort quick_sort \ n " ) ;
for ( i =0; i < sizeof ( vN ) / sizeof ( int ) ; i ++)
16 {
lista = my_alloca ( vN [ i ]) ;
18 for ( j =0; j < sizeof ( vM ) / sizeof ( int ) ; j ++)
{
20 printf ( " %6 d %6 d " , vN [ i ] , vM [ j ]) ;
t0 = clock () ;
22 for ( M =0; M < vM [ M ]; M ++)
{
24 genera_casuale ( lista , vN [ i ]) ;
merge_sort ( lista , 0 , vN [ i ] -1) ;
26 vfree ( lista , vN [ i ]) ;
}
28 t1 = clock () ;
printf ( " %10.3 f " , (( float ) ( t1 - t0 ) ) / CLOCKS_PER_SEC ) ;
30 t0 = clock () ;
for ( M =0; M < vM [ M ]; M ++)
32 {
genera_casuale ( lista , vN [ i ]) ;
34 quick_sort ( lista , 0 , vN [ i ] -1) ;
vfree ( lista , vN [ i ]) ;
36 }
t1 = clock () ;
38 printf ( " %10.3 f " , (( float ) ( t1 - t0 ) ) / CLOCKS_PER_SEC ) ;
printf ( " \ n " ) ;
40 }
}
42 free ( lista ) ;
system ( " pause " ) ;
44 return 0;
}
46 void vfree ( ITEM ** lista , int n )
{
48 if ( lista == NULL )
return ;
50 for (n - -; n >=0; n - -)
free ( lista [ n ]) ;
52 }

ITEM ** my_alloca ( int n_el )


54 {

ITEM ** vet ;
56 vet = ( ITEM **) malloc ( n_el * sizeof ( ITEM *) ) ;
return vet ;
58 }

void genera_casuale ( ITEM ** vet , int n_el )


60 {

for ( n_el - -; n_el >=0; n_el - -)


62 vet [ n_el ] = iInit_rand () ;
}
 

Listing 9.7: Confronto tra algoritmi di ordinamento (client.c)

1 # include < stdio .h >
# include < malloc .h >
3 typedef struct
{
5 int num ;
} ITEM ;
7 ITEM * iRead_By_stream ( FILE * stream ) ;
ITEM * iRead_By_string ( char * string ) ;

98
9 int iPrint ( ITEM *i , FILE * stream ) ;
int iCompare ( ITEM * i1 , ITEM * i2 ) ;
11 int iCopy ( ITEM * i1 , ITEM * i2 ) ;
ITEM * iInit_rand () ;
 

Listing 9.8: Confronto tra algoritmi di ordinamento (item.h)

# include < stdio .h >
2 # include < time .h >
# include " item . h "
4 # define __i_rand_max RAND_MAX
ITEM * iRead_By_stream ( FILE * stream )
6 {

if ( stream == NULL )
8 return NULL ;
ITEM * res ;
10 if (( res =( ITEM *) malloc ( sizeof ( ITEM ) ) ) == NULL )
return NULL ;
12 fscanf ( stream , " % d " , & res - > num ) ;
return res ;
14 }

ITEM * iRead_By_string ( char * string )


16 {

if ( string == NULL )
18 return NULL ;
ITEM * res ;
20 if (( res =( ITEM *) malloc ( sizeof ( ITEM ) ) ) == NULL )
return NULL ;
22 sscanf ( string , " % d " , & res - > num ) ;
return res ;
24 }

int iPrint ( ITEM *i , FILE * stream )


26 {

if ( i == NULL || stream == NULL )


28 return 0;
fprintf ( stream , " % d " , i - > num ) ;
30 return 1;
}
32 int iCompare ( ITEM * i1 , ITEM * i2 )
{
34 return i1 - > num - i2 - > num ;
}
36 int iCopy ( ITEM * i1 , ITEM * i2 )
{
38 if ( i1 == NULL || i2 == NULL )
return 0;
40 * i1 = * i2 ;
return 1;
42 }

ITEM * iInit_rand ()
44 {

ITEM * res ;
46 if (( res = ( ITEM *) malloc ( sizeof ( ITEM ) ) ) == NULL )
return NULL ;
48 res - > num = rand () % __i_rand_max ;
return res ;
50 }
 

Listing 9.9: Confronto tra algoritmi di ordinamento (item.c)

# include " item . h "
2 int merge_sort ( ITEM ** vec , int l , int r ) ;

99
void quick_sort ( ITEM ** vec , int l , int r ) ;
 

Listing 9.10: Confronto tra algoritmi di ordinamento (sort.h)

# include " sort . h "
2 int Merge ( ITEM ** vec , int l , int m , int r )
{
4 int i , j , k ;
ITEM ** tmp ;
6 if (( tmp =( ITEM **) malloc (( r +1) * sizeof ( ITEM *) ) ) == NULL )
return 0;
8 i=l;
j = m +1;
10 k=l;
while (i <= m && j <= r )
12 {
if ( iCompare ( vec [ i ] , vec [ j ]) <0)
14 tmp [ k ++] = vec [ i ++];
else
16 tmp [ k ++] = vec [ j ++];
}
18 while (i <= m )
tmp [ k ++] = vec [ i ++];
20 while (j <= r )
tmp [ k ++] = vec [ j ++];
22 for ( k = l ; k <= r ; k ++)
vec [ k ] = tmp [ k ];
24 return 1;
}
26 int merge_sort ( ITEM ** vec , int l , int r )
// add a return value because dynamic allocator can be falliure
28 {

int m ;
30 if (l < r )
{
32 m = ( l + r ) /2;
merge_sort ( vec , l , m ) ;
34 merge_sort ( vec , m +1 , r ) ;
Merge ( vec , l , m , r ) ;
36 }
return 1;
38 }

int partition ( ITEM ** vec , int l , int r )


40 {

int i , j ;
42 ITEM * tmp ;
i = l -1;
44 j = r +1;
while (i < j )
46 {
while ( iCompare ( vec [ - - j ] , vec [ l ]) >0)
48 ;
while ( iCompare ( vec [++ i ] , vec [ l ]) <0)
50 ;
if (i < j )
52 {
tmp = vec [ i ];
54 vec [ i ] = vec [ j ];
vec [ j ] = tmp ;
56 }
}
58 return j ;

100
}
60 void quick_sort ( ITEM ** vec , int l , int r )
{
62 int p ;
if (l < r )
64 {
p = partition ( vec , l , r ) ;
66 quick_sort ( vec , l , p ) ;
quick_sort ( vec , p +1 , r ) ;
68 }
}
 

Listing 9.11: Confronto tra algoritmi di ordinamento (sort.c)

101
102
Capitolo 10

Esercitazione 7

10.1 Esercizio n. 1: gestione di strutture FIFO.


Si realizzi un programma C che, attraverso un’apposita interfaccia utente, permetta di gestire una strut-
tura dati di tipo FIFO (coda non prioritaria).
Le operazione permesse devono essere quelle di:
• creazione di una nuova struttura (vuota).
• inserimento di un nuovo elemento della struttura.
• estrazione di un elemento dalla struttura.
• visualizzazione (a video) di tutti gli elementi nella base dati (opzionale).
• salvataggio della base dati su file (opzionale).
• caricamento di una nuova base dati da file (opzionale).
In questo esercizio, il programma deve essere realizzato su tre moduli distinti:
• l’interfaccia utente (il client).
• un modulo con le funzioni per la gestione della coda.
• un modulo con le funzioni per la gestione dei singoli dati.
In particolare, si desidera che l’implementazione della libreria sulla struttura dati corrisponda a un ADT
di I categoria.
Si supponga che ogni elemento della base dati possa essere (a scelta del programmatore):
• una stringa di lunghezza massima pari a 50 caratteri.
• un numero intero.
• una struttura composta da due campi (stringa + numero).
Si realizzi il programma in due versioni:
• supponendo che il numero massimo di elementi nella coda sia pari a 100 (implementazione tramite
vettori).
• supponendo che non ci sia limite al numero di elementi che è possibile memorizzare nella coda
(implementazione tramite liste).

# include < stdio .h >
2 # include < stdlib .h >
# include " queue . h "
4 # define pulisci system ( " cls " )
# define pausa while ( getchar () != ’\ n ’) ; getchar ()
6 void print_adt_stream ( Queue * coda , FILE * stream ) ;
int menu () ;
8 int read_adt_stream ( Queue * coda , FILE * stream ) ;
int main ()

103
10 {
Queue * coda = NULL ;
12 Item * item ;
int choose ;
14 int fine =0;
int errori ;
16 char path [ MAX_PATH ]={0};
FILE * f ;
18 while (! fine )
{
20 errori =0;
choose = menu () ;
22 switch ( choose )
{
24 case 1:
delete_queue ( coda ) ;
26 if (( coda = new_queue () ) == NULL )
errori += printf ( " Errore nell ’ allocazione della
memoria .\ n " ) ;
28 break ;
case 2:
30 if (( item = i_read_by_stream ( stdin ) ) == NULL )
errori += printf ( " Errore nell ’ allocazione della
memoria .\ n " ) ;
32 if (! enqueue ( coda , item ) )
errori += printf ( " Errore nell ’ inserimento dell ’ item .\ n
");
34 break ;
case 3:
36 if (( item = dequeue ( coda ) ) == NULL )
errori += printf ( " Errore nell ’ estrazione dell ’ item .\ n "
);
38 else
{
40 errori =1;
printf ( " Valore estratto : " ) ;
42 i_print ( item , stdout ) ;
printf ( " \ n " ) ;
44 i_free ( item ) ;
}
46 break ;
case 4:
48 errori =1;
print_adt_stream ( coda , stdout ) ;
50 break ;
case 5:
52 printf ( " Introduci il percorso nel quale salvare la
struttura dati :\ t " ) ;
scanf ( " % s " , path ) ;
54 if (( f = fopen ( path , " w " ) ) != NULL )
{
56 print_adt_stream ( coda , f ) ;
fflush ( f ) ;
58 fclose ( f ) ;
}
60 break ;
case 6:
62 printf ( " Introduci il percorso dal quale leggere la
struttura dati :\ t " ) ;
scanf ( " % s " , path ) ;
64 if (( f = fopen ( path , " r " ) ) != NULL )
{

104
66 if ( is_empty_queue ( coda ) )
{
68 delete_queue ( coda ) ;
if (( coda = new_queue () ) == NULL )
70 printf ( " Errore nell ’ allocazione della
memoria .\ n " ) ;
}
72 if (! read_adt_stream ( coda , f ) )
{
74 errori = 1;
printf ( " Errore nella lettura dell ’ ADT dal file \ n
");
76 }
fclose ( f ) ;
78 }
break ;
80 case 7:
delete_queue ( coda ) ;
82 fine =1;
}
84 if ( errori )
pausa ;
86 }
return 0;
88 }
int read_adt_stream ( Queue * coda , FILE * stream )
90 {
Item * item ;
92 while ( fgetc ( stream ) != ’\ n ’)
;
94 while (( item = i_read_by_stream ( stream ) ) != NULL )
if (! enqueue ( coda , item ) )
96 return 0;
return 1;
98 }
void print_adt_stream ( Queue * coda , FILE * stream )
100 {
Item * item ;
102 if ( is_empty_queue ( coda ) )
fprintf ( stream , " Struttura vuota .\ n " ) ;
104 else
{
106 fprintf ( stream , " Valori estratti :\ n " ) ;
while (( item = dequeue ( coda ) ) != NULL )
108 {
fprintf ( stream , " \ t " ) ;
110 i_print ( item , stream ) ;
fprintf ( stream , " \ n " ) ;
112 i_free ( item ) ;
}
114 }
}
116 int menu ()
{
118 int choose ;
pulisci ;
120 do
{
122 printf ( " \ nSeleziona l ’ operazione desiderata :\ n \ t1 - Creazione
nuova Queue \ n \ t2 - Inserimento di un nuovo elemento \ n \ t3 -
Estrazione e stampa di un elemento \ n \ t4 - Visualizzazione a
video di tutti gli elementi \ n \ t5 - Salvataggio su file di

105
tutti gli elementi \ n \ t6 - Caricamento degli elementi da file \
n \ t7 - Esci \ nIntroduci la scelta desiderata :\ t " ) ;
scanf ( " % d " , & choose ) ;
124 if ( choose <1 || choose >7)
{
126 pulisci ;
printf ( " \ tERRORE IMMISSIONE DATI ! " ) ;
128 }
}
130 while ( choose <1 || choose >7) ;
return choose ;
132 }
 

Listing 10.1: Queue (client.c)

1 # include < stdio .h >
# include < malloc .h >
3 typedef int Item ;
void i_print ( Item * item , FILE * stream ) ;
5 void i_free ( Item * item ) ;
Item * i_read_by_stream ( FILE * stream ) ;
 

Listing 10.2: Queue (item.h)

1 # include " item . h "
void i_print ( Item * item , FILE * stream )
3 {

fprintf ( stream , " % d " , * item ) ;


5 }

void i_free ( Item * item )


7 {

if ( item == NULL )
9 return ;
free ( item ) ;
11 }

Item * i_read_by_stream ( FILE * stream )


13 {

Item * res ;
15 if (( res =( Item *) malloc ( sizeof ( Item ) ) ) == NULL || stream == NULL )
return NULL ;
17 if ( stream == stdin )
printf ( " Introduci un numero intero :\ t " ) ;
19 if ( fscanf ( stream , " % d " , res ) !=1)
{
21 free ( res ) ;
return NULL ;
23 }
return res ;
25 }
 

Listing 10.3: Queue (item.c)

Per la versione con allocazione dinamica saranno necessari questi altri due listati

1 # include < stdio .h >
# include < malloc .h >
3 # include " item . h "
struct queue_item_t
5 {

Item * item ;
7 struct queue_item_t * next ;
};
9 typedef struct Queue_t

106
{
11 struct queue_item_t * head , * tail ;
} Queue ;
13 Queue * new_queue () ;
int is_empty_queue ( Queue * queue ) ;
15 int enqueue ( Queue * queue , Item * add ) ;
Item * dequeue ( Queue * queue ) ;
17 void print_dequeue ( Queue * queue , FILE * stream ) ;
void clean_queue ( Queue * queue ) ;
19 void delete_queue ( Queue * queue ) ;
 

Listing 10.4: Queue (queue.h)

# include < stdio .h >
2 # include " queue . h "
Queue * new_queue ()
4 {

Queue * res ;
6 if (( res = ( Queue *) malloc ( sizeof ( Queue ) ) ) == NULL )
return NULL ;
8 res - > head = NULL ;
res - > tail = NULL ;
10 return res ;
}
12 int is_empty_queue ( Queue * queue )
{
14 return queue == NULL || ( queue - > tail == NULL && queue - > head == NULL ) ;
}
16 int enqueue ( Queue * queue , Item * add )
{
18 if ( queue == NULL )
return 0;
20 struct queue_item_t * to_add ;
if (( to_add =( struct queue_item_t *) malloc ( sizeof ( struct queue_item_t )
) ) == NULL )
22 return 0;
to_add - > item = add ;
24 to_add - > next = NULL ;
if ( queue - > tail != NULL )
26 queue - > tail - > next = to_add ;
queue - > tail = to_add ;
28 if ( queue - > head == NULL )
queue - > head = to_add ;
30 printf ( " enqueue (... , " ) ;
i_print ( add , stdout ) ;
32 printf ( " ) \ n " ) ;
return 1;
34 }

Item * dequeue ( Queue * queue )


36 {

if ( is_empty_queue ( queue ) )
38 return NULL ;
struct queue_item_t * head ;
40 Item * item ;
head = queue - > head ;
42 queue - > head = queue - > head - > next ;
if ( queue - > head == NULL )
44 queue - > tail = NULL ;
item = head - > item ;
46 free ( head ) ;
return item ;
48 }

107
void print_dequeue ( Queue * queue , FILE * stream )
50 {
Item * extract ;
52 extract = dequeue ( queue ) ;
if ( extract == NULL )
54 fprintf ( stream , " ERRORE DI ESTRAZIONE \ n " ) ;
else
56 {
i_print ( extract , stream ) ;
58 fprintf ( stream , " \ n " ) ;
i_free ( extract ) ;
60 }
}
62 void clean_queue ( Queue * queue )
{
64 if ( queue == NULL )
return ;
66 Item * extracted ;
while (( extracted = dequeue ( queue ) ) != NULL )
68 i_free ( extracted ) ;
}
70 void delete_queue ( Queue * queue )
{
72 if ( queue == NULL )
return ;
74 clean_queue ( queue ) ;
free ( queue ) ;
76 }
 

Listing 10.5: Queue (queue.c)

mentre per la versione statico sono necessari questi file



1 # include < stdio .h >
# include < malloc .h >
3 # include " item . h "
# define lenght_max_queue 10
5 typedef struct Queue_t
{
7 Item * item [ lenght_max_queue ];
int head , tail ;
9 } Queue ;
Queue * new_queue () ;
11 int is_empty_queue ( Queue * queue ) ;
int is_full_queue ( Queue * queue ) ;
13 int enqueue ( Queue * queue , Item * add ) ;
Item * dequeue ( Queue * queue ) ;
15 void print_dequeue ( Queue * queue , FILE * stream ) ;
void clean_queue ( Queue * queue ) ;
17 void delete_queue ( Queue * queue ) ;
 

Listing 10.6: Queue (queue.h)

1 # include " queue . h "
Queue * new_queue ()
3 {

Queue * res ;
5 if (( res =( Queue *) malloc ( sizeof ( Queue ) ) ) == NULL )
return NULL ;
7 res - > head = res - > tail =0;
return res ;
9 }

int is_empty_queue ( Queue * queue )

108
11 {
return queue != NULL && queue - > tail == queue - > head ;
13 }
int is_full_queue ( Queue * queue )
15 {

return ( queue - > tail ) +1== queue - > head ;


17 }

int enqueue ( Queue * queue , Item * add )


19 {

if ( queue == NULL )
21 return 0;
if ( is_full_queue ( queue ) )
23 return 0;
queue - > item [ queue - > tail ] = add ;
25 queue - > tail = ( queue - > tail +1) % lenght_max_queue ;
return 1;
27 }

Item * dequeue ( Queue * queue )


29 {

if ( is_empty_queue ( queue ) )
31 return NULL ;
Item * item ;
33 item = queue - > item [ queue - > head ];
queue - > head = ( queue - > head +1) % lenght_max_queue ;
35 return item ;
}
37 void print_dequeue ( Queue * queue , FILE * stream )
{
39 Item * extract ;
extract = dequeue ( queue ) ;
41 if ( extract == NULL )
fprintf ( stream , " ERRORE DI ESTRAZIONE \ n " ) ;
43 else
{
45 i_print ( extract , stream ) ;
fprintf ( stream , " \ n " ) ;
47 i_free ( extract ) ;
}
49 }

void clean_queue ( Queue * queue )


51 {

if ( queue == NULL )
53 return ;
Item * extracted ;
55 while (( extracted = dequeue ( queue ) ) != NULL )
i_free ( extracted ) ;
57 }

void delete_queue ( Queue * queue )


59 {

if ( queue == NULL )
61 return ;
clean_queue ( queue ) ;
63 free ( queue ) ;
}
 

Listing 10.7: Queue (queue.c)

10.2 Esercizio n. 2: gestione di strutture LIFO.


Si realizzi un programma C che, attraverso un’apposita interfaccia utente, permetta di gestire una strut-
tura dati di tipo LIFO (pila).

109
Le operazione permesse devono essere quelle di:
• creazione di una nuova struttura (vuota).
• inserimento di un nuovo elemento della struttura.
• estrazione di un elemento dalla struttura.
• visualizzazione (a video) di tutti gli elementi nella base dati (opzionale).
• salvataggio della base dati su file (opzionale).
• caricamento di una nuova base dati da file (opzionale).
In questo esercizio, il programma deve essere realizzato su tre moduli distinti:
• l’interfaccia utente (il client).
• un modulo con le funzioni per la gestione della coda.
• un modulo con le funzioni per la gestione dei singoli dati.
In particolare, si desidera che l’implementazione della libreria sulla struttura dati corrisponda a un ADT
di I categoria.
Si supponga che ogni elemento della base dati possa essere (a scelta del programmatore):
• una stringa di lunghezza massima pari a 50 caratteri.
• un numero intero.
• una struttura composta da due campi (stringa + numero).
Si realizzi il programma in due versioni:
• supponendo che il numero massimo di elementi nella coda sia pari a 100 (implementazione tramite
vettori).
• supponendo che non ci sia limite al numero di elementi che è possibile memorizzare nella coda
(implementazione tramite liste).

# include < stdio .h >
2 # include < stdlib .h >
# include " stack . h "
4 # define pulisci system ( " cls " )
# define pausa while ( getchar () != ’\ n ’) ; getchar ()
6 void print_adt_stream ( Stack * pila , FILE * stream ) ;
int menu () ;
8 int read_adt_stream ( Stack * pila , FILE * stream ) ;
int main ()
10 {

Stack * pila = NULL ;


12 Item * item ;
int choose ;
14 int fine =0;
int errori ;
16 char path [ MAX_PATH ]={0};
FILE * f ;
18 while (! fine )
{
20 errori =0;
choose = menu () ;
22 switch ( choose )
{
24 case 1:
delete_stack ( pila ) ;
26 if (( pila = new_stack () ) == NULL )
errori += printf ( " Errore nell ’ allocazione della
memoria .\ n " ) ;

110
28 break ;
case 2:
30 if (( item = i_read_by_stream ( stdin ) ) == NULL )
errori += printf ( " Errore nell ’ allocazione della
memoria .\ n " ) ;
32 if (! push ( pila , item ) )
errori += printf ( " Errore nell ’ inserimento dell ’ item .\ n
");
34 break ;
case 3:
36 if (( item = pop ( pila ) ) == NULL )
errori += printf ( " Errore nell ’ estrazione dell ’ item .\ n "
);
38 else
{
40 errori =1;
printf ( " Valore estratto : " ) ;
42 i_print ( item , stdout ) ;
printf ( " \ n " ) ;
44 i_free ( item ) ;
}
46 break ;
case 4:
48 errori =1;
print_adt_stream ( pila , stdout ) ;
50 break ;
case 5:
52 printf ( " Introduci il percorso nel quale salvare la
struttura dati :\ t " ) ;
scanf ( " % s " , path ) ;
54 if (( f = fopen ( path , " w " ) ) != NULL )
{
56 print_adt_stream ( pila , f ) ;
fflush ( f ) ;
58 fclose ( f ) ;
}
60 break ;
case 6:
62 printf ( " Introduci il percorso dal quale leggere la
struttura dati :\ t " ) ;
scanf ( " % s " , path ) ;
64 if (( f = fopen ( path , " r " ) ) != NULL )
{
66 if ( is_empty_stack ( pila ) )
{
68 delete_stack ( pila ) ;
if (( pila = new_stack () ) == NULL )
70 printf ( " Errore nell ’ allocazione della
memoria .\ n " ) ;
}
72 if (! read_adt_stream ( pila , f ) )
{
74 errori = 1;
printf ( " Errore nella lettura dell ’ ADT dal file \ n
");
76 }
fclose ( f ) ;
78 }
break ;
80 case 7:
delete_stack ( pila ) ;
82 fine =1;

111
}
84 if ( errori )
pausa ;
86 }
return 0;
88 }
int read_adt_stream ( Stack * pila , FILE * stream )
90 {

Item * item ;
92 while ( fgetc ( stream ) != ’\ n ’)
;
94 while (( item = i_read_by_stream ( stream ) ) != NULL )
if (! push ( pila , item ) )
96 return 0;
return 1;
98 }

void print_adt_stream ( Stack * pila , FILE * stream )


100 {

Item * item ;
102 if ( is_empty_stack ( pila ) )
fprintf ( stream , " Struttura vuota .\ n " ) ;
104 else
{
106 fprintf ( stream , " Valori estratti :\ n " ) ;
while (( item = pop ( pila ) ) != NULL )
108 {
fprintf ( stream , " \ t " ) ;
110 i_print ( item , stream ) ;
fprintf ( stream , " \ n " ) ;
112 i_free ( item ) ;
}
114 }
}
116 int menu ()
{
118 int choose ;
pulisci ;
120 do
{
122 printf ( " \ nSeleziona l ’ operazione desiderata :\ n \ t1 - Creazione
nuovo Stack \ n \ t2 - Inserimento di un nuovo elemento \ n \ t3 -
Estrazione e stampa di un elemento \ n \ t4 - Visualizzazione a
video di tutti gli elementi \ n \ t5 - Salvataggio su file di
tutti gli elementi \ n \ t6 - Caricamento degli elementi da file \
n \ t7 - Esci \ nIntroduci la scelta desiderata :\ t " ) ;
scanf ( " % d " , & choose ) ;
124 if ( choose <1 || choose >7)
{
126 pulisci ;
printf ( " \ tERRORE IMMISSIONE DATI ! " ) ;
128 }
}
130 while ( choose <1 || choose >7) ;
return choose ;
132 }
 

Listing 10.8: Stack (client.c)

1 # include < stdio .h >
# include < malloc .h >
3 typedef int Item ;
void i_print ( Item * item , FILE * stream ) ;

112
5 void i_free ( Item * item ) ;
Item * i_read_by_stream ( FILE * stream ) ;
 

Listing 10.9: Stack (item.h)

1 # include " item . h "
void i_print ( Item * item , FILE * stream )
3 {

fprintf ( stream , " % d " , * item ) ;


5 }

void i_free ( Item * item )


7 {

if ( item == NULL )
9 return ;
free ( item ) ;
11 }

Item * i_read_by_stream ( FILE * stream )


13 {

Item * res ;
15 if (( res =( Item *) malloc ( sizeof ( Item ) ) ) == NULL || stream == NULL )
return NULL ;
17 if ( stream == stdin )
printf ( " Introduci un numero intero :\ t " ) ;
19 if ( fscanf ( stream , " % d " , res ) !=1)
{
21 free ( res ) ;
return NULL ;
23 }
return res ;
25 }
 

Listing 10.10: Stack (item.c)

Per la versione con allocazione dinamica saranno necessari questi altri due listati

1 # include < stdio .h >
# include < malloc .h >
3 # include " item . h "
struct stack_item_t
5 {

Item * item ;
7 struct stack_item_t * next ;
};
9 typedef struct Stack_t
{
11 struct stack_item_t * head ;
} Stack ;
13 Stack * new_stack () ;
int is_empty_stack ( Stack * queue ) ;
15 int push ( Stack * pila , Item * add ) ;
Item * pop ( Stack * pila ) ;
17 void print_dequeue ( Stack * pila , FILE * stream ) ;
void clean_stack ( Stack * pila ) ;
19 void delete_stack ( Stack * pila ) ;
 

Listing 10.11: Stack (stack.h)

1 # include < stdio .h >
# include " stack . h "
3 Stack * new_stack ()
{
5 Stack * res ;
if (( res = ( Stack *) malloc ( sizeof ( Stack ) ) ) == NULL )

113
7 return NULL ;
res - > head = NULL ;
9 return res ;
}
11 int is_empty_stack ( Stack * queue )
{
13 return queue == NULL || queue - > head == NULL ;
}
15 int push ( Stack * pila , Item * add )
{
17 if ( pila == NULL )
return 0;
19 struct stack_item_t * to_add ;
if (( to_add =( struct stack_item_t *) malloc ( sizeof ( struct stack_item_t )
) ) == NULL )
21 return 0;
to_add - > item = add ;
23 to_add - > next = pila - > head ;
pila - > head = to_add ;
25 return 1;
}
27 Item * pop ( Stack * pila )
{
29 if ( is_empty_stack ( pila ) )
return NULL ;
31 struct stack_item_t * head ;
Item * item ;
33 head = pila - > head ;
pila - > head = pila - > head - > next ;
35 item = head - > item ;
free ( head ) ;
37 return item ;
}
39 void print_pop ( Stack * pila , FILE * stream )
{
41 Item * extract ;
extract = pop ( pila ) ;
43 if ( extract == NULL )
fprintf ( stream , " ERRORE DI ESTRAZIONE \ n " ) ;
45 else
{
47 i_print ( extract , stream ) ;
fprintf ( stream , " \ n " ) ;
49 i_free ( extract ) ;
}
51 }
void clean_stack ( Stack * pila )
53 {
if ( pila == NULL )
55 return ;
Item * extracted ;
57 while (( extracted = pop ( pila ) ) != NULL )
i_free ( extracted ) ;
59 }
void delete_stack ( Stack * pila )
61 {
if ( pila == NULL )
63 return ;
clean_stack ( pila ) ;
65 free ( pila ) ;
}

114
 

Listing 10.12: Stack (stack.c)

mentre per la versione statico sono necessari questi file



1 # include < stdio .h >
# include < malloc .h >
3 # include " item . h "
# define lenght_max_stack 10
5 typedef struct Stack_t
{
7 Item * item [ lenght_max_stack ];
int head ;
9 } Stack ;
Stack * new_stack () ;
11 int is_empty_stack ( Stack * queue ) ;
int push ( Stack * pila , Item * add ) ;
13 Item * pop ( Stack * pila ) ;
void print_dequeue ( Stack * pila , FILE * stream ) ;
15 void clean_stack ( Stack * pila ) ;
void delete_stack ( Stack * pila ) ;
 

Listing 10.13: Stack (stack.h)

1 # include < stdio .h >
# include " stack . h "
3 Stack * new_stack ()
{
5 Stack * res ;
if (( res = ( Stack *) malloc ( sizeof ( Stack ) ) ) == NULL )
7 return NULL ;
res - > head = 0;
9 return res ;
}
11 int is_full_stack ( Stack * pila )
{
13 return pila == NULL || pila - > head >= lenght_max_stack ;
}
15 int is_empty_stack ( Stack * pila )
{
17 return pila == NULL || pila - > head ==0;
}
19 int push ( Stack * pila , Item * add )
{
21 if ( pila == NULL )
return 0;
23 pila - > item [ pila - > head ++] = add ;
return 1;
25 }

Item * pop ( Stack * pila )


27 {

if ( is_empty_stack ( pila ) )
29 return NULL ;
Item * item ;
31 item = pila - > item [ - - pila - > head ];
return item ;
33 }

void print_pop ( Stack * pila , FILE * stream )


35 {

Item * extract ;
37 extract = pop ( pila ) ;
if ( extract == NULL )

115
39 fprintf ( stream , " ERRORE DI ESTRAZIONE \ n " ) ;
else
41 {
i_print ( extract , stream ) ;
43 fprintf ( stream , " \ n " ) ;
i_free ( extract ) ;
45 }
}
47 void clean_stack ( Stack * pila )
{
49 if ( pila == NULL )
return ;
51 Item * extracted ;
while (( extracted = pop ( pila ) ) != NULL )
53 i_free ( extracted ) ;
}
55 void delete_stack ( Stack * pila )
{
57 if ( pila == NULL )
return ;
59 clean_stack ( pila ) ;
free ( pila ) ;
61 }
 

Listing 10.14: Stack (stack.c)

116
Capitolo 11

Esercitazione 8

11.1 Esercizio n. 1: gestione di una coda prioritaria - I


In un centro sviluppo di una grande azienda di elettronica si fa uso di un insieme consistente di pc,
interconnessi tra loro, per eseguire numerose simulazioni sui progetti sviluppati dagli ingegneri lı̀ impiegati.
Più precisamente, quando un progettista vuole compiere un determinato test, lancia un comando (job) che
viene “raccolto” dal sistema di gestione comune e poi eseguito su uno qualunque dei calcolatori dedicati.
La strategia seguita dal gestore è la seguente:

• Se, nel momento in cui viene lanciato un job, è presente almeno un computer libero, il job viene
immediatamente eseguito su di esso.

• Se non è disponibile alcun calcolatore per eseguire un job, il job stesso viene temporaneamente
“salvato” in una lista d’attesa.

• Nel momento in cui un job termina la sua esecuzione su un pc, il gestore viene “avvertito” e un
altro job (se presente nella lista d’attesa) viene selezionato e lanciato su di esso. La selezione del
nuovo job da eseguire avviene secondo un criterio di massima priorità.

Si desidera implementare un semplice programma che emuli il comportamento del gestore dei job.
In particolare, il sistema deve essere realizzato su tre moduli:

• un modulo per la gestione dei singoli job: ciascun job è in realtà caratterizzato da numerose infor-
mazioni, ma per semplicità si supponga che, oltre al livello di priorità (intero positivo), ad esso sia
associata solo una stringa univoca di lunghezza massima pari a 30 caratteri.

• un modulo per la gestione della lista d’attesa: tale struttura dati, di fatto corrispondente a una
coda prioritaria, sia implementata (come quasi ADT o ADT di I categoria) mediante:

– una lista ordinata.


– un heap, nell’ipotesi in cui il numero di job che possono essere memorizzati nella coda sia al
più pari a 100.

• un programma principale (client) che si occupa dell’interfaccia del gestore verso il mondo esterno,
al cui interno sono realizzate le due funzioni con cui il gestore può essere richiamato (ovvero, la
richiesta di esecuzione di un nuovo job da parte di un progettista e la segnalazione di terminazione
di un job che occupava uno dei computer). Per semplicità, si includa in tale modulo anche un main
che richiami tali funzioni a scelta dell’utente, richiedendo all’utente stesso le altre informazioni che
sono necessarie per attivare la funzione desiderata (per esempio, se si richiede di eseguire un nuovo
job, il suo “nome” e la sua priorità).

Il programma, infine, visualizzi sul video opportune informazioni a fronte di ogni richiesta ricevuta (per
esempio, che la richiesta di esecuzione un nuovo job è stata acquisita ma il job è stato salvato nella lista
d’attesa, oppure che un determinato job è stato estratto dalla lista d’attesa e lanciato su un calcolatore
che si è reso disponibile, etc.).
Il numero totale di computer dedicati che sono disponibili sia una costante predefinita, dichiarata nel
modulo principale.

1 # include < stdio .h >
# include < string .h >

117
3 # include " PriorityQueue . h "
# define n_computer 3
5 # define ma x_ co mm an d_ le ng ht 256
# define log_file " log . txt "
7 int main ()
{
9 char cmd [ m ax _c om ma nd _l en gh t +1] , name [30+1];
int fine =0 , i ;
11 FILE * flog ;
job * computer [ n_computer ]={0} , * tmp ;
13 Priority_Queue * job_list ;
if (( job_list = q_init () ) == NULL )
15 return 1;
if (( flog = fopen ( log_file , " w " ) ) == NULL )
17 return 2;
while (! fine )
19 {
printf ( " >> " ) ;
21 fgets ( cmd , max_command_lenght , stdin ) ;
if ( strstr ( cmd , " exit " ) == cmd )
23 fine =1;
else if ( strstr ( cmd , " job " ) == cmd )
25 {
if (( tmp = j_read_by_string ( cmd +4) ) == NULL )
27 printf ( " Errore lettura job . " ) ;
else if (! q_enqueue ( job_list , tmp ) )
29 printf ( " Errore inserimento job . " ) ;
else
31 {
printf ( " Job % s ( p =% d ) acquisito . " , j_getName ( tmp ) ,
j_getPriority ( tmp ) ) ;
33 fprintf ( flog , " Job % s ( p =% d ) acquisito . " , j_getName ( tmp
) , j_getPriority ( tmp ) ) ;
for ( i =0; i < n_computer && computer [ i ]!= NULL ; i ++)
35 ;
if ( i != n_computer )
37 {
printf ( " Job lanciato su pc % d . " , i ) ;
39 fprintf ( flog , " Job lanciato su pc % d . " , i ) ;
computer [ i ] = q_dequeue ( job_list ) ;
41 }
else
43 {
printf ( " Job messo in attesa . " ) ;
45 fprintf ( flog , " Job messo in attesa . " ) ;
}
47 }
printf ( " \ n " ) ;
49 fprintf ( flog , " \ n " ) ;
}
51 else if ( strstr ( cmd , " stop " ) == cmd )
{
53 sscanf ( cmd +4 , " % s " , name ) ;
for ( i =0; i < n_computer ; i ++)
55 if ( computer [ i ]!= NULL && strcmp ( name , j_getName ( computer [
i ]) ) ==0)
break ;
57 if ( i == n_computer )
printf ( " Processo non trovato in esecuzione . " ) ;
59 else
{

118
61 printf ( " Job % s ( p =% d ) terminato . " , j_getName (
computer [ i ]) , j_getPriority ( computer [ i ]) ) ;
fprintf ( flog , " Job % s ( p =% d ) terminato . " , j_getName (
computer [ i ]) , j_getPriority ( computer [ i ]) ) ;
63 j_free ( computer [ i ]) ;
computer [ i ] = q_dequeue ( job_list ) ;
65 if ( computer [ i ]!= NULL )
{
67 printf ( " Job % s ( p =% d ) lanciato su pc % d . " ,
j_getName ( computer [ i ]) , j_getPriority ( computer [ i
]) , i ) ;
fprintf ( flog , " Job % s ( p =% d ) lanciato su pc % d . " ,
j_getName ( computer [ i ]) , j_getPriority ( computer [ i
]) , i ) ;
69 }
}
71 printf ( " \ n " ) ;
fprintf ( flog , " \ n " ) ;
73 }
else
75 printf ( " Comando non riconosciuto . ( job , stop , exit ) \ n " ) ;
}
77 fflush ( flog ) ;
fclose ( flog ) ;
79 q_free ( job_list ) ;
return 0;
81 }
 

Listing 11.1: Coda prioritaria - I (client.c)

1 # include < stdio .h >
# include < malloc .h >
3 # include < string .h >
typedef unsigned int Priority ;
5 typedef struct
{
7 Priority priority ;
char * name ;
9 } job ;
job * j_init ( Priority priority , char * name ) ;
11 void j_print ( job * JOB , FILE * stream ) ;
void j_free ( job * JOB ) ;
13 job * j_read_by_stream ( FILE * stream ) ;
job * j_read_by_string ( char * string ) ;
15 char * j_getName ( job * JOB ) ;
int j_getPriority ( job * JOB ) ;
 

Listing 11.2: Coda prioritaria - I (job.h)

1 # include " job . h "
# define max_name_lenght 30
3 job * j_init ( Priority priority , char * name )
{
5 job * res ;
if ( priority <=0 || name == NULL )
7 return NULL ;
if (( res =( job *) malloc ( sizeof ( job ) ) ) == NULL )
9 return NULL ;
if (( res - > name = strdup ( name ) ) == NULL )
11 {
free ( res ) ;
13 return NULL ;

119
}
15 res - > priority = priority ;
return res ;
17 }
void j_print ( job * JOB , FILE * stream )
19 {

if ( JOB == NULL || stream == NULL )


21 return ;
fprintf ( stream , " % s (% d ) \ n " , JOB - > name , JOB - > priority ) ;
23 }

void j_free ( job * JOB )


25 {

if ( JOB == NULL )
27 return ;
if ( JOB - > name == NULL )
29 free ( JOB - > name ) ;
free ( JOB ) ;
31 }

job * j_read_by_stream ( FILE * stream )


33 {

if ( stream == NULL )
35 return NULL ;
char name_tmp [ max_name_lenght +1];
37 Priority priority_tmp ;
if ( stream == stdin )
39 printf ( " Introduci : nome priorita ’\ n " ) ;
if ( fscanf ( stream , " % s % d " , name_tmp , & priority_tmp ) ==2)
41 return j_init ( priority_tmp , name_tmp ) ;
else
43 return NULL ;
}
45 job * j_read_by_string ( char * string )
{
47 if ( string == NULL )
return NULL ;
49 char name_tmp [ max_name_lenght +1];
Priority priority_tmp ;
51 if ( sscanf ( string , " % s % d " , name_tmp , & priority_tmp ) ==2)
return j_init ( priority_tmp , name_tmp ) ;
53 else
return NULL ;
55 }

char * j_getName ( job * JOB )


57 {

if ( JOB == NULL )
59 return NULL ;
return JOB - > name ;
61 }

int j_getPriority ( job * JOB )


63 {

if ( JOB == NULL )
65 return -1;
return JOB - > priority ;
67 }
 

Listing 11.3: Coda prioritaria - I (job.c)

# include " job . h "
2 typedef job ITEM ;
int i_compare ( ITEM * I1 , ITEM * I2 ) ;
4 char * i_getDescription ( ITEM * I ) ;
void i_free ( ITEM * I ) ;

120
 

Listing 11.4: Coda prioritaria - I (ITEM.h)

1 # include " ITEM . h "
int i_compare ( ITEM * I1 , ITEM * I2 )
3 {

if ( I1 == NULL || I2 == NULL )
5 return 0;
return I1 - > priority - I2 - > priority ;
7 }

char * i_getDescription ( ITEM * I )


9 {

return j_getName ( I ) ;
11 }

void i_free ( ITEM * I )


13 {

j_free ( I ) ;
15 }
 

Listing 11.5: Coda prioritaria - I (ITEM.c)

Per la versione con heap saranno necessari questi altri due listati

# include < stdio .h >
2 # include < malloc .h >
# include " ITEM . h "
4 # define max_heap_dim 127
# define LEFT ( i ) 2* i +1
6 # define RIGHT ( i ) 2* i +2
# define PARENT ( i ) ( int ) (i -1) /2
8 typedef struct
{
10 ITEM ** value ;
int heapsize , max_heapsize ;
12 } heap ;
heap * h_init ( int size ) ;
14 int h_insert ( heap * HEAP , ITEM * V ) ;
void h_heapify ( heap * HEAP , int root ) ;
16 ITEM * h_remove ( heap * HEAP ) ;
void h_free ( heap * HEAP ) ;
 

Listing 11.6: Coda prioritaria - I (HEAP.h)

1 # include " HEAP . h "
heap * h_init ( int size )
3 {

if ( size <=0)
5 return NULL ;
heap * res ;
7 if (( res =( heap *) malloc ( sizeof ( heap ) ) ) == NULL )
return NULL ;
9 int i ;
i =1;
11 while (i <= size && i < max_heap_dim )
i < <=1;
13 i - -;
if (( res - > value =( ITEM **) malloc ( i * sizeof ( ITEM *) ) ) == NULL )
15 {
free ( res ) ;
17 return NULL ;
}
19 res - > heapsize =0;

121
res - > max_heapsize = i ;
21 return res ;
}
23 int h_insert ( heap * HEAP , ITEM * V )
{
25 if ( HEAP == NULL || V == NULL || HEAP - > heapsize >= HEAP - > max_heapsize )
return 0;
27 int position = HEAP - > heapsize ;
while ( position !=0 && i_compare (V , HEAP - > value [ PARENT ( position ) ]) >0)
29 {
HEAP - > value [ position ] = HEAP - > value [ PARENT ( position ) ];
31 position = PARENT ( position ) ;
}
33 HEAP - > value [ position ] = V ;
HEAP - > heapsize ++;
35 return 1;
}
37 void h_print ( heap * HEAP )
{
39 if ( HEAP == NULL )
return ;
41 int i ;
int l =1;
43 for ( i =0; i < HEAP - > heapsize ; i ++)
{
45 printf ( " % s " , i_getDescription ( HEAP - > value [ i ]) ) ;
if ( i ==(1 < < l ) -2)
47 {
printf ( " \ n " ) ;
49 l ++;
}
51 }
printf ( " \ n " ) ;
53 }
void h_heapify ( heap * HEAP , int root )
55 {
ITEM * t ;
57 if ( HEAP == NULL )
return ;
59 int largest = root ;
if ( LEFT ( root ) < HEAP - > heapsize && i_compare ( HEAP - > value [ LEFT ( root ) ] ,
HEAP - > value [ root ]) >0)
61 largest = LEFT ( root ) ;
if ( RIGHT ( root ) < HEAP - > heapsize && i_compare ( HEAP - > value [ RIGHT ( root ) ] ,
HEAP - > value [ largest ]) >0)
63 largest = RIGHT ( root ) ;
if ( largest != root )
65 {
t = HEAP - > value [ root ];
67 HEAP - > value [ root ] = HEAP - > value [ largest ];
HEAP - > value [ largest ] = t ;
69 h_heapify ( HEAP , largest ) ;
}
71 }
ITEM * h_remove ( heap * HEAP )
73 {
if ( HEAP == NULL || HEAP - > heapsize ==0)
75 return NULL ;
ITEM * VAL = HEAP - > value [0];
77 HEAP - > value [0] = HEAP - > value [ - - HEAP - > heapsize ];
HEAP - > value [ HEAP - > heapsize ] = NULL ;
79 h_heapify ( HEAP , 0) ;

122
return VAL ;
81 }
void h_free ( heap * HEAP )
83 {

if ( HEAP == NULL )
85 return ;
int i ;
87 for ( i =0; i < HEAP - > heapsize ; i ++)
i_free ( HEAP - > value [ i ]) ;
89 free ( HEAP - > value ) ;
free ( HEAP ) ;
91 }
 

Listing 11.7: Coda prioritaria - I (HEAP.c)

# include " HEAP . h "
2 typedef heap Priority_Queue ;
Priority_Queue * q_init () ;
4 int q_enqueue ( Priority_Queue * Queue , ITEM * I ) ;
ITEM * q_dequeue ( Priority_Queue * Queue ) ;
6 int q_is_empty ( Priority_Queue * Queue ) ;
void q_free ( Priority_Queue * Queue ) ;
 

Listing 11.8: Coda prioritaria - I (PriorityQueue.h)

1 # include " Priority_queue . h "
Priority_Queue * q_init ()
3 {

Priority_Queue * res ;
5 if (( res = h_init ( max_heap_dim ) ) == NULL )
return NULL ;
7 return res ;
}
9 int q_enqueue ( Priority_Queue * Queue , ITEM * I )
{
11 if ( Queue == NULL || I == NULL )
return 0;
13 return h_insert ( Queue , I ) ;
}
15 ITEM * q_dequeue ( Priority_Queue * Queue )
{
17 return h_remove ( Queue ) ;
}
19 int q_is_empty ( Priority_Queue * Queue )
{
21 if ( Queue == NULL )
return 1;
23 return Queue - > heapsize ==0;
}
25 void q_free ( Priority_Queue * Queue )
{
27 h_free ( Queue ) ;
}
 

Listing 11.9: Coda prioritaria - I (PriorityQueue.c)

mentre per la versione con linked-list ordinata sono necessari questi file

1 # include < malloc .h >
# include " ITEM . h "
3 struct node
{
5 ITEM * item ;

123
struct node * next ;
7 };
typedef struct
9 {

struct node * head ;


11 } LIST ;
LIST * l_init () ;
13 void l_free ( LIST * List ) ;
int l_add ( LIST * List , ITEM * I ) ;
15 ITEM * l_extract_max ( LIST * List ) ;
int l_is_empty ( LIST * List ) ;
17 void l_print ( LIST * List , FILE * stream ) ;
 

Listing 11.10: Coda prioritaria - I (LIST.h)

1 # include " LIST . h "
LIST * l_init ()
3 {

LIST * res ;
5 if (( res =( LIST *) malloc ( sizeof ( LIST ) ) ) == NULL )
return NULL ;
7 res - > head = NULL ;
return res ;
9 }

void l_free ( LIST * List )


11 {

if ( List == NULL )
13 return ;
struct node * NODE , * extract ;
15 NODE = List - > head ;
while ( NODE != NULL )
17 {
extract = NODE ;
19 NODE = NODE - > next ;
free ( extract ) ;
21 }
free ( List ) ;
23 }

int l_add ( LIST * List , ITEM * I )


25 {

struct node * to_add , * succ , * pred ;


27 if ( List == NULL )
return 0;
29 if (( to_add =( struct node *) malloc ( sizeof ( struct node ) ) ) == NULL )
return 0;
31 to_add - > item = I ;
to_add - > next = NULL ;
33 succ = List - > head ;
pred = NULL ;
35 while ( succ != NULL )
{
37 if ( i_compare ( succ - > item , I ) <0)
break ;
39 pred = succ ;
succ = succ - > next ;
41 }
if ( succ != NULL )
43 {
if ( pred == NULL )
45 {
to_add - > next = List - > head ;
47 List - > head = to_add ;

124
}
49 else
{
51 to_add - > next = succ ;
pred - > next = to_add ;
53 }
}
55 else
{
57 if ( pred == NULL )
List - > head = to_add ;
59 else
pred - > next = to_add ;
61 }
return 1;
63 }
ITEM * l_extract_max ( LIST * List )
65 {

if ( List == NULL || List - > head == NULL )


67 return NULL ;
struct node * extract ;
69 ITEM * res ;
extract = List - > head ;
71 List - > head = List - > head - > next ;
res = extract - > item ;
73 free ( extract ) ;
return res ;
75 }

int l_is_empty ( LIST * List )


77 {

if ( List == NULL )
79 return 1;
return List - > head == NULL ;
81 }

void l_print ( LIST * List , FILE * stream )


83 {

if ( List == NULL || stream == NULL )


85 return ;
struct node * root ;
87 root = List - > head ;
while ( root != NULL )
89 {
i_print ( root - > item , stream ) ;
91 root = root - > next ;
}
93 }
 

Listing 11.11: Coda prioritaria - I (LIST.c)

1 # include " LIST . h "
typedef LIST Priority_Queue ;
3 Priority_Queue * q_init () ;
int q_enqueue ( Priority_Queue * Queue , ITEM * I ) ;
5 ITEM * q_dequeue ( Priority_Queue * Queue ) ;
int q_is_empty ( Priority_Queue * Queue ) ;
7 void q_free ( Priority_Queue * Queue ) ;
 

Listing 11.12: Coda prioritaria - I (PriorityQueue.h)

1 # include " Priority_queue . h "
Priority_Queue * q_init ()
3 {

125
return l_init () ;
5 }
int q_enqueue ( Priority_Queue * Queue , ITEM * I )
7 {

if ( Queue == NULL || I == NULL )


9 return 0;
return l_add ( Queue , I ) ;
11 }

ITEM * q_dequeue ( Priority_Queue * Queue )


13 {

return l_extract_max ( Queue ) ;


15 }

int q_is_empty ( Priority_Queue * Queue )


17 {

return l_is_empty ( Queue ) ;


19 }

void q_free ( Priority_Queue * Queue )


21 {

l_free ( Queue ) ;
23 }
 

Listing 11.13: Coda prioritaria - I (PriorityQueue.c)

11.2 Esercizio n. 2: gestione di una coda prioritaria - II


Si modifichi il codice sviluppato nell’esercizio precedente in modo tale che:

• ogni job sia caratterizzato, oltre che dal nome e dalla priorità, anche da un tempo di arrivo (ovvero,
l’orario in cui ne è stata richiesta l’esecuzione) e da una durata. Entrambi siano espressi nel formato
hh:mm:ss.

• l’insieme di tutti i job da eseguire, in numero ignoto, sia memorizzato in un file di testo, in ragione
di un job per riga del file, con il formato seguente:

<nome> <priorità> <arrivo> <durata>

Si assuma che i job contenuti in tale file siano ordinati per tempi di arrivo crescenti.

• l’operazione che il gestore dei job deve eseguire non sia di volta in volta richiesta all’utente: piuttosto,
a partire dal contenuto del file suddetto, il client dell’applicazione deduca automaticamente quando
i computer sono disponibili e l’istante di tempo in cui ogni job viene effettivamente lanciato su uno
dei calcolatori.

• il programma visualizzi sul video un insieme opportuno di informazioni su ciascun avvenimento in


ordine cronologico.

Suggerimento: Il client dell’applicazione, a partire dal contenuto del file dei job, simuli il comporta-
mento del gestore seguendo un ordine cronologico: ogni qual volta un nuovo job viene lanciato su un
computer, infatti, l’applicazione conosce il momento in cui il calcolatore sarà di nuovo disponibile. In
ogni istante, pertanto il gestore sa quanti sono i calcolatori liberi e quando ogni job che è stato lancia-
to sarà terminato. Processando un job per volta dal file di ingresso, quindi, è possibile capire qual è
l’avvenimento che accade per primo tra la presa in carico del nuovo job e un mutamento nello stato dei
calcolatori e/o dei job in lista d’attesa . . . .
La variazione rispetto all’esercizio 1 consiste solo nel client e nei campi della struttura job; la gestione
della coda prioritaria resta invariata.

# include < stdio .h >
2 # include < stdlib .h >
# include " Priority_queue . h "
4 # define jobList " joblist . txt "
# define n_computer 3
6 # define log_file " log . txt "

126
int execute_job ( job * computer [] , HOUR * h our_to _term inate [] , int *
pc_usati , int * cpu_to_terminate , job * to_add , HOUR * actual_moment ) ;
8 HOUR * terminate_job ( job * computer [] , HOUR * ho ur_to_ termin ate [] , int *
pc_usati , int * cpu_to_terminate ) ;
int main ()
10 {
job * tmp , * tmp_extract , * computer [ n_computer ]={0};
12 FILE * fin , * flog ;
Priority_Queue * job_list ;
14 HOUR * ho ur_to_ termin ate [ n_computer ]={0} , * h_tmp = NULL ;
int cpu_to_terminate = -1 , pc_usati =0;
16 if (( fin = fopen ( jobList , " r " ) ) == NULL || ( flog = fopen ( log_file , " w " ) ) ==
NULL )
return 1;
18 if (( job_list = q_init () ) == NULL )
return 2;
20 while (( tmp = j_read_by_stream ( fin ) ) != NULL )
{
22 if (! q_enqueue ( job_list , tmp ) )
{
24 printf ( " ERRORE inserimento job .\ n " ) ;
continue ;
26 }
if ( pc_usati !=0)
28 {
while ( H_compare ( tmp - > request , hour_ to_ter minate [
cpu_to_terminate ]) >0)
30 {
H_print ( ho ur_to_ termin ate [ cpu_to_terminate ] , stdout ) ;
32 printf ( " > Job % s ( p =% d ) terminato . " , j_getName (
computer [ cpu_to_terminate ]) , j_getPriority ( computer [
cpu_to_terminate ]) ) ;
H_print ( ho ur_to_ termin ate [ cpu_to_terminate ] , flog ) ;
34 fprintf ( flog , " > Job % s ( p =% d ) terminato . " , j_getName (
computer [ cpu_to_terminate ]) , j_getPriority ( computer [
cpu_to_terminate ]) ) ;
h_tmp = terminate_job ( computer , hour_to_terminate , &
pc_usati , & cpu_to_terminate ) ;
36 }
}
38 if ( pc_usati == n_computer )
{
40 H_print ( tmp - > request , stdout ) ;
printf ( " > Job % s ( p =% d ) acquisito . Job messo in attesa .\ n " ,
j_getName ( tmp ) , j_getPriority ( tmp ) ) ;
42 H_print ( tmp - > request , flog ) ;
fprintf ( flog , " > Job % s ( p =% d ) acquisito . Job messo in
attesa .\ n " , j_getName ( tmp ) , j_getPriority ( tmp ) ) ;
44 }
else
46 {
tmp_extract = q_dequeue ( job_list ) ;
48 if ( tmp == tmp_extract )
{
50 H_print ( tmp - > request , stdout ) ;
printf ( " > Job % s ( p =% d ) acquisito . " , j_getName ( tmp ) ,
j_getPriority ( tmp ) ) ;
52 H_print ( tmp - > request , flog ) ;
fprintf ( flog , " > Job % s ( p =% d ) acquisito . " , j_getName (
tmp ) , j_getPriority ( tmp ) ) ;
54 }
printf ( " Job % s ( p =% d ) lanciato su pc .\ n " , j_getName (

127
tmp_extract ) , j_getPriority ( tmp_extract ) ) ;
56 fprintf ( flog , " Job % s ( p =% d ) lanciato su pc .\ n " , j_getName (
tmp_extract ) , j_getPriority ( tmp_extract ) ) ;
if (! execute_job ( computer , hour_to_terminate , & pc_usati , &
cpu_to_terminate , tmp_extract , h_tmp != NULL ? h_tmp :
tmp_extract - > request ) )
58 {
printf ( " ERRORE esecuzuine job .\ n " ) ;
60 continue ;
}
62 if ( tmp != tmp_extract )
{
64 H_print ( tmp - > request , stdout ) ;
printf ( " > Job % s ( p =% d ) acquisito . Job messo in attesa
.\ n " , j_getName ( tmp ) , j_getPriority ( tmp ) ) ;
66 H_print ( tmp - > request , flog ) ;
fprintf ( flog , " > Job % s ( p =% d ) acquisito . Job messo in
attesa .\ n " , j_getName ( tmp ) , j_getPriority ( tmp ) ) ;
68 }
}
70 }
while ( pc_usati !=0)
72 {
H_print ( ho ur_to_ termin ate [ cpu_to_terminate ] , stdout ) ;
74 printf ( " > Job % s ( p =% d ) terminato . " , j_getName ( computer [
cpu_to_terminate ]) , j_getPriority ( computer [ cpu_to_terminate ])
);
H_print ( ho ur_to_ termin ate [ cpu_to_terminate ] , flog ) ;
76 fprintf ( flog , " > Job % s ( p =% d ) terminato . " , j_getName ( computer
[ cpu_to_terminate ]) , j_getPriority ( computer [ cpu_to_terminate
]) ) ;
h_tmp = terminate_job ( computer , hour_to_terminate , & pc_usati , &
cpu_to_terminate ) ;
78 if (( tmp_extract = q_dequeue ( job_list ) ) != NULL )
{
80 printf ( " Job % s ( p =% d ) lanciato su pc . " , j_getName (
tmp_extract ) , j_getPriority ( tmp_extract ) ) ;
fprintf ( flog , " Job % s ( p =% d ) lanciato su pc . " , j_getName (
tmp_extract ) , j_getPriority ( tmp_extract ) ) ;
82 if (! execute_job ( computer , hour_to_terminate , & pc_usati , &
cpu_to_terminate , tmp_extract , h_tmp != NULL ? h_tmp :
tmp_extract - > request ) )
{
84 printf ( " ERRORE esecuzuine job .\ n " ) ;
continue ;
86 }
}
88 printf ( " \ n " ) ;
fprintf ( flog , " \ n " ) ;
90 }
j_free ( tmp ) ;
92 j_free ( tmp_extract ) ;
q_free ( job_list ) ;
94 H_free ( h_tmp ) ;
fclose ( fin ) ;
96 fflush ( flog ) ;
fclose ( flog ) ;
98 return 0;
}
100 HOUR * terminate_job ( job * computer [] , HOUR * ho ur_to_ termin ate [] , int *
pc_usati , int * cpu_to_terminate )
{

128
102 HOUR * res ;
j_free ( computer [* cpu_to_terminate ]) ;
104 computer [* cpu_to_terminate ] = NULL ;
res = h our_to _termi nate [* cpu_to_terminate ];
106 hou r_to_t ermina te [* cpu_to_terminate ] = NULL ;
(* pc_usati ) - -;
108 int i ;
* cpu_to_terminate = -1;
110 for ( i =0; i < n_computer ; i ++)
if ( computer [ i ]!= NULL && (* cpu_to_terminate == -1 || H_compare (
hou r_to_t ermina te [ i ] , hou r_to_t ermina te [* cpu_to_terminate ])
<0) )
112 * cpu_to_terminate = i ;
return res ;
114 }
int execute_job ( job * computer [] , HOUR * h our_to _term inate [] , int *
pc_usati , int * cpu_to_terminate , job * to_add , HOUR * actual_moment )
116 {

if ( to_add == NULL || actual_moment == NULL || * pc_usati == n_computer ||


to_add == NULL || actual_moment == NULL )
118 return 0;
int i ;
120 for ( i =0; i < n_computer ; i ++)
{
122 if ( computer [ i ]== NULL )
{
124 if (( ho ur_to_ termin ate [ i ] = H_init_by_hour ( actual_moment ) ) ==
NULL )
return 0;
126 computer [ i ] = to_add ;
H_sum ( hour_t o_term inate [ i ] , computer [ i ] - > duration ) ;
128 if (* cpu_to_terminate == -1)
* cpu_to_terminate = i ;
130 if ( H_compare ( ho ur_to_ termin ate [ i ] , ho ur_to_ termin ate [*
cpu_to_terminate ]) <0)
* cpu_to_terminate = i ;
132 (* pc_usati ) ++;
return 1;
134 }
}
136 return 0;
}
 

Listing 11.14: Coda prioritaria - II (client.c)

1 typedef struct
{
3 int S ;
} HOUR ;
5 int H_compare ( HOUR * h1 , HOUR * h2 ) ;
HOUR * H_init ( int h , int m , int s ) ;
7 HOUR * H_read_by_stream ( FILE * stream ) ;
void H_print ( HOUR * Hour , FILE * stream ) ;
9 void H_free ( HOUR * H ) ;
HOUR * H_init_by_hour ( HOUR * H ) ;
11 int H_sum ( HOUR * H1 , HOUR * H2 ) ; /// H1 += H2
 

Listing 11.15: Coda prioritaria - II (Hour.h)

1 # include < stdio .h >
# include < malloc .h >
3 # include " Hour . h "

129
int H_compare ( HOUR * h1 , HOUR * h2 )
5 {
if ( h1 == NULL || h2 == NULL )
7 return 0;
return h1 - > S - h2 - > S ;;
9 }

HOUR * H_init ( int h , int m , int s )


11 {

HOUR * res ;
13 if (h <0 || h >=24 || m <0 || m >=60 || s <0 || s >=60)
return NULL ;
15 if (( res =( HOUR *) malloc ( sizeof ( HOUR ) ) ) == NULL )
return NULL ;
17 res - > S = h *3600+ m *60+ s ;
return res ;
19 }

HOUR * H_read_by_stream ( FILE * stream )


21 {

if ( stream == NULL )
23 return NULL ;
int h , m , s ;
25 if ( fscanf ( stream , " % d :% d :% d " , &h , &m , & s ) ==4)
return H_init (h , m , s ) ;
27 else
return NULL ;
29 }

void H_print ( HOUR * Hour , FILE * stream )


31 {

if ( Hour == NULL || stream == NULL )


33 return ;
fprintf ( stream , " %02 d :%02 d :%02 d " , ( Hour - > S ) /3600 , ( Hour - > S /60) %60 ,
Hour - > S %60) ;
35 }

void H_free ( HOUR * H )


37 {

if ( H == NULL )
39 return ;
free ( H ) ;
41 }

HOUR * H_init_by_hour ( HOUR * H )


43 {

if ( H == NULL )
45 return NULL ;
return H_init (H - > S /3600 , (H - > S /60) %60 , H - > S %60) ;
47 }

int H_sum ( HOUR * H1 , HOUR * H2 )


49 {

if ( H1 == NULL || H2 == NULL )
51 return 0;
H1 - > S += H2 - > S ;
53 return 1;
}
 

Listing 11.16: Coda prioritaria - II (Hour.c)

# include < stdio .h >
2 # include < malloc .h >
# include < string .h >
4 # include " Hour . h "
typedef unsigned int Priority ;
6 typedef struct
{

130
8 Priority priority ;
char * name ;
10 HOUR * request , * duration ;
} job ;
12 job * j_init ( Priority priority , char * name , HOUR * request , HOUR * duration
);
void j_print ( job * JOB , FILE * stream ) ;
14 void j_free ( job * JOB ) ;
job * j_read_by_stream ( FILE * stream ) ;
16 job * j_read_by_string ( char * string ) ;
char * j_getName ( job * JOB ) ;
18 int j_getPriority ( job * JOB ) ;
 

Listing 11.17: Coda prioritaria - II (job.h)

# include " job . h "
2 # define max_name_lenght 30
job * j_init ( Priority priority , char * name , HOUR * request , HOUR * duration
)
4 {

job * res ;
6 if ( priority <=0 || name == NULL || request == NULL || duration == NULL )
return NULL ;
8 if (( res =( job *) malloc ( sizeof ( job ) ) ) == NULL )
return NULL ;
10 if (( res - > name = strdup ( name ) ) == NULL )
{
12 free ( res ) ;
return NULL ;
14 }
res - > priority = priority ;
16 res - > request = request ;
res - > duration = duration ;
18 return res ;
}
20 void j_print ( job * JOB , FILE * stream )
{
22 if ( JOB == NULL || stream == NULL )
return ;
24 fprintf ( stream , " % s (% d ) " , JOB - > name , JOB - > priority ) ;
H_print ( JOB - > request , stream ) ;
26 fprintf ( stream , " " ) ;
H_print ( JOB - > duration , stream ) ;
28 }

void j_free ( job * JOB )


30 {

if ( JOB == NULL )
32 return ;
if ( JOB - > name != NULL )
34 free ( JOB - > name ) ;
if ( JOB - > request != NULL )
36 H_free ( JOB - > request ) ;
if ( JOB - > duration != NULL )
38 H_free ( JOB - > duration ) ;
free ( JOB ) ;
40 }

job * j_read_by_stream ( FILE * stream )


42 {

if ( stream == NULL )
44 return NULL ;
char name_tmp [ max_name_lenght +1];
46 Priority priority_tmp ;

131
int hr , mr , sr , hd , md , sd ;
48 if ( stream == stdin )
printf ( " Introduci : nome priorita ’ ora_richiesta durata \ n " ) ;
50 if ( fscanf ( stream , " % s % d % d :% d :% d % d :% d :% d " , name_tmp , & priority_tmp
, & hr , & mr , & sr , & hd , & md , & sd ) ==8)
return j_init ( priority_tmp , name_tmp , H_init ( hr , mr , sr ) , H_init
( hd , md , sd ) ) ;
52 else
return NULL ;
54 }
job * j_read_by_string ( char * string )
56 {

if ( string == NULL )
58 return NULL ;
char name_tmp [ max_name_lenght +1];
60 Priority priority_tmp ;
int hr , mr , sr , hd , md , sd ;
62 if ( sscanf ( string , " % s % d % d :% d :% d % d :% d :% d " , name_tmp , & priority_tmp
, & hr , & mr , & sr , & hd , & md , & sd ) ==8)
return j_init ( priority_tmp , name_tmp , H_init ( hr , mr , sr ) , H_init
( hd , md , sd ) ) ;
64 else
return NULL ;
66 }

char * j_getName ( job * JOB )


68 {

if ( JOB == NULL )
70 return NULL ;
return JOB - > name ;
72 }

int j_getPriority ( job * JOB )


74 {

if ( JOB == NULL )
76 return -1;
return JOB - > priority ;
78 }
 

Listing 11.18: Coda prioritaria - II (job.c)

132

Potrebbero piacerti anche