Quaderni di Informatica
Strutture ed Algoritmi
Luigino Calvi
I.I.S. Negrelli-Forcellini - Feltre
2014
ii
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
Indice
1 Sequenze
1.1 LE COMPOSIZIONI . . . . . . . . . . . . . . . . .
1.2 LE SEQUENZE . . . . . . . . . . . . . . . . . . . .
1.3 RAPPRESENTAZIONI DELLE SEQUENZE . . .
1.4 OPERAZIONI SULLE SEQUENZE . . . . . . . .
1.5 CLASSIFICAZIONE DELLE SEQUENZE . . . .
1.6 LE STRUTTURE . . . . . . . . . . . . . . . . . . .
1.7 LE SEQUENZE COMPOSTE . . . . . . . . . . . .
1.8 OPERAZIONI FUNZIONALI E PROCEDURALI
1.9 COSTRUZIONE DI SEQUENZE . . . . . . . . . .
1.10 ELABORAZIONE DI SEQUENZE . . . . . . . . .
1.11 ESERCIZI . . . . . . . . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1
2
3
5
6
8
9
9
10
11
14
16
2 Liste
2.1 LE LISTE . . . . . . . . . . . . . . . . . . . .
2.2 ELABORAZIONE DI LISTE . . . . . . . .
2.3 ALGORITMI SULLE LISTE SEMPLICI .
2.4 ALGORITMI SULLE LISTE COMPOSTE
2.5 ESERCIZI . . . . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
21
22
23
25
28
30
.
.
.
.
.
.
.
35
36
36
37
39
40
42
44
3 Array
3.1 GLI ARRAY . . . . . . . . . . . . . . . . . .
3.2 ELABORAZIONE DI ARRAY . . . . . . .
3.3 ARRAY E LISTE . . . . . . . . . . . . . . .
3.4 LE MATRICI . . . . . . . . . . . . . . . . . .
3.5 ELABORAZIONE DI MATRICI . . . . . .
3.6 SOLUZIONE DI UN SISTEMA LINEARE
3.7 ESERCIZI . . . . . . . . . . . . . . . . . . . .
iii
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
iv
4 Complessità Computazionale
4.1 LA COMPLESSITÀ COMPUTAZIONALE
4.2 OPERAZIONE DOMINANTE . . . . . . .
4.3 DIMENSIONE DI UN PROBLEMA . . . .
4.4 COMPLESSITÀ DI UN ALGORITMO . .
4.5 COMPLESSITÀ DI UN PROBLEMA . . .
4.6 COMPLESSITÀ ASINTOTICA . . . . . . .
4.7 ESEMPI VARI . . . . . . . . . . . . . . . . .
4.8 PROBLEMI INTRATTABILI . . . . . . . .
4.9 ESERCIZI . . . . . . . . . . . . . . . . . . . .
INDICE
.
.
.
.
.
.
.
.
.
53
54
55
56
56
57
57
58
60
63
5 Ricerca
5.1 ALGORITMI DI RICERCA . . . . . . . . . . . . . . . . . . . . .
5.2 ESERCIZI . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
67
67
70
6 Ordinamento
6.1 ALGORITMI DI ORDINAMENTO . . . . . . . . . . . . . . . .
6.2 ESERCIZI . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
71
71
72
7 Alberi
7.1 ALBERI . . . . . . . . . . . . . . . . . . . . . . .
7.2 RAPPRESENTAZIONI DEGLI ALBERI . . .
7.3 METODI DI VISITA DEGLI ALBERI . . . .
7.4 ALBERI BINARI . . . . . . . . . . . . . . . . .
7.5 OPERAZIONI SUGLI ALBERI BINARI . . .
7.6 VISITA DEGLI ALBERI BINARI . . . . . . .
7.7 RAPPRESENTAZIONE DI ALBERI BINARI
7.8 ALBERI BINARI DI RICERCA . . . . . . . .
7.9 ESERCIZI . . . . . . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
75
76
77
79
80
80
81
82
83
85
.
.
.
.
.
.
.
.
.
89
90
91
92
92
94
94
94
95
95
8 Grafi
8.1 DEFINIZIONI . . . . . . . . . . . . . . . . .
8.2 TIPOLOGIE DI GRAFI . . . . . . . . . . .
8.3 OPERAZIONI SUI GRAFI . . . . . . . . .
8.4 RAPPRRESENTAZIONI DEI GRAFI . . .
8.5 PROBLEMI SUI GRAFI . . . . . . . . . . .
8.5.1 Problema del cammino minimo . . .
8.5.2 Problema del collegamento minimo .
8.5.3 Problema del commesso viaggiatore
8.5.4 Problema dell’isomorfismo fra grafi .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
v
INDICE
8.6
8.5.5 Problema della planarità di un grafo . . . . . . . . . . . .
ESERCIZI . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
95
96
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
vi
INDICE
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
Capitolo 1
Sequenze
I programmi per computer usualmente
operano su tabelle d’informazioni. Nella maggioranza dei casi queste tabelle non
sono semplicemente masse amorfe di valori numerici; esse coinvolgono importanti
relazioni strutturali fra i dati elementi.
D. Knuth,
The Art of Computer Programming
Uno dei concetti fondamentali e trasversali a tutta l’informatica, e non solo
all’informatica, si fonda sul meccanismo della composizione di elementi di base,
raggruppandoli in un’unica entità che può essere considerata, denominata e
manipolata in modo unitario. Questo artificio sottende il concetto di costruire:
in fondo, questa non è una scoperta dell’informatica in quanto è la stessa
strategia che consente all’artigiano di costruire un cesto intrecciando un fascio
di vimini.
Nella composizione, in taluni casi è sufficiente considerare gli elementi che
vengono composti, mantenendosi al livello di astrazione degli insiemi; in altri
casi è importante considerare anche la struttura con cui gli elementi vengono
composti. Riprendendo l’esempio sopra, il peso del cesto vuoto può essere
determinato calcolando il peso del fascio di vimini con i quali è composto, ma
se si vuole utilizzare il cesto come contenitore, è necessario che i vimini siano
intrecciati opportunamente.
1
2
1.1
CAPITOLO 1. SEQUENZE
LE COMPOSIZIONI
Nel mondo fisico ogni cosa è composta da elementi fondamentali primitivi
non scomponibili (atomi) o, almeno, considerati tali nel contesto in cui ci si
pone; similmente in informatica esistono componenti elementari che sono i
più piccoli frammenti dotati di significato e non ulteriormente scomponibili.
Gli elementi fondamentali possono essere composti per formare oggetti più
complessi. Le composizioni possono essere formate da elementi che sono a
loro volta composti da elementi più elementari.
Questi elementi possono essere di diversa natura. Al livello di astrazione
predisposto dai tradizionali linguaggi di programmazione, gli atomi sono costituiti dai numeri, dalle stringhe, dalle istruzioni elementari del linguaggio,
dalle funzioni ed altro ancora; ad un livello più basso, gli atomi sono i byte
e le istruzioni in linguaggio macchina; ad un livello più alto, in un sistema
complesso, gli atomi del sistema sono le componenti del sistema stesso e a
loro volta possono essere costituite da altre componenti più elementari. La
potenza dei linguaggi di programmazione viene esplicata proprio mediante la
possibilità di comporre fra loro delle entità di base.
Il meccanismo di composizione può essere pensato come un’operazione che
raggruppa un insieme di elementi, secondo lo schema descritto nella figura 1.1.
●
●
●
●
●
●
●
●
●
composizione
-
●
●
●
●
●
●
●
●
●
Figura 1.1: Operazione di composizione di un insieme di elementi.
Ogni meccanismo di composizione comporta la definizione di meccanismi
di accesso e di manipolazione delle componenti. Per questo sulle composizioni
sono predisposti degli appositi operatori che sono classificati come segue:
costruttori : sono delle operazioni che generano composizioni
selettori : sono delle operazioni che permettono di accedere ad uno specifico
elemento di una composizione; in taluni casi un selettore può fornire,
come risultato, una sottocomposizione.
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
3
1.2. LE SEQUENZE
manipolatori : sono delle operazioni che hanno l’effetto di modificare la
struttura della composizione, mediante operazioni di inserimento, eliminazione e modifica di elementi.
Queste categorie di operatori (costruttori, selettori, manipolatori) sono
realizzate con diverse modalità, dando origine a diverse tipologie di composizioni.
1.2
LE SEQUENZE
Gli elementi che costituiscono una composizione possono essere organizzati e
strutturati in diversi modi; una delle forme più elementari di organizzazione
è costituita dalla sequenzializzazione che consiste nel disporre (fisicamente) o
considerare (virtualmente) gli elementi in fila, uno dopo l’altro, secondo lo
schema descritto nella figura 1.2.
●
●
●
●
●
●
●
●
●
sequenzializzazione
1 2 3 4 5 6 7 8 9
- ● ● ● ● ● ● ● ● ●
Figura 1.2: Operazione di sequenzializzazione di un insieme di elementi.
ne.
Il formalismo per denotare le sequenze è descritto nella seguente definizio-
DEFINIZIONE 1. Con il termine sequenza si indica una composizione di
elementi, detti atomi, disposti in fila, uno dopo l’altro; in generale, un atomo
o una sequenza viene chiamato elemento. Se a1 , . . . , an sono degli elementi,
la scrittura 1
[a1 , . . . , an ]
denota la sequenza costituita dagli elementi a1 , . . . , an , mentre la scrittura
[]
1
La scrittura [. . . ] usata per denotare una sequenza (oltre che essere sintatticamente
compatibile con la maggior parte dei linguaggi di programmazione) ha una valenza figurativa
che richiama la gestualità delle mani per raggruppare degli oggetti.
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
4
CAPITOLO 1. SEQUENZE
denota la sequenza vuota, ossia la sequenza composta da nessun elemento.
Una sequenza composta da n elementi viene detta n-sequenza.
Data una sequenza α = [a1 , . . . , an ] e degli indici i, j tali che 1 ≤ i ≤ j ≤ n,
la sequenza [ai , . . . , aj ] dicesi sottosequenza di α. Se i = 1 la sottosequenza è
detta prefisso (di α); se j = n la sottosequenza è detta suffisso (di α).
Se h e k sono due numeri naturali con h ≤ k, con
[h..k]
si denota la sequenza composta dai numeri naturali compresi fra h e k, estremi
compresi.
Esempio 1. Dalla definizione di sequenza, si può immediatamente riconoscere
che i seguenti sono degli esempi di sequenze:
[]
[7]
[3, 2, 5]
Le sequenze sono delle composizioni costruite mediante l’operazione di
prodotto cartesiano di insiemi precedentemente definiti. A seguire sono descritti i principali metodi per costruire insiemi strutturati mediante il ricorso
all’operazione di prodotto cartesiano.
Se A è un insieme,
An = A × A × ⋯ × A
è un nuovo insieme i cui elementi sono le sequenze [a1 , a2 , . . . , an ] di lunghezza
n di elementi di A. Si assume, per convenzione, A0 = [ ].
Se A è un insieme,
∞
A∗ = A0 ∪ A1 ∪ A2 ∪ ⋯ = ⋃ Ak
k=0
è un nuovo insieme i cui elementi sono tutte le sequenze (di qualsiasi lunghezza)
di elementi di A.
Se A1 , A2 , . . . , An sono insiemi,
A1 × A2 × ⋯ × An
è un insieme i cui elementi sono le sequenze [a1 , a2 , . . . , an ] non omogenee di
elementi con ai ∈ Ai .
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
5
1.3. RAPPRESENTAZIONI DELLE SEQUENZE
Osservazione. Il concetto di sequenza è più forte e pregnante del concetto
di composizione, in quanto una sequenza stabilisce anche una posizione d’ordine degli elementi componenti. Non si tratta di una semplice composizione
insiemistica degli elementi, ma di una composizione in sequenza in quanto ogni
elemento della composizione è caratterizzato da una sua specifica posizione.
Il meccanismo di sequenzializzazione fornisce il supporto per lo sviluppo di
moltissimi ed importantissimi algoritmi, come gli algoritmi di ricerca e di ordinamento. Inoltre, le sequenze costituiscono un meccanismo spesso utilizzato
per realizzare delle particolari strutture di dati astratte.
1.3
RAPPRESENTAZIONI DELLE SEQUENZE
Una sequenza [a1 , a2 , . . . , an ] viene spesso descritta graficamente mediante uno
schema simile a quello riportato nella figura 1.3.
a1
a2
...
an
Figura 1.3: Rappresentazione grafica della sequenza [a1 , a2 , . . . , an ].
In talune considerazioni, specialmente nel caso di sequenze composte, risulta utile descrivere una sequenza mediante uno schema ad albero: una generica
sequenza [a1 , . . . , an ] viene descritta come illustrato nella figura 1.4.
2
@
@
@
@
@
a1
a2
...
@
@
R
@
an
Figura 1.4: Rappresentazione grafica ad albero della sequenza [a1 , a2 , . . . , an ].
Esempio 2. La sequenza [a, [b], [ [ ], c, d] ] è descritta dall’albero riportato in
figura 1.5.
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
6
CAPITOLO 1. SEQUENZE
2
@
@
@
a
?
2
?
b
@
R
@
2
B
B
B
?BBN
⊡ c d
Figura 1.5:
Rappresentazione grafica ad albero della sequenza
[a, [b], [ [ ], c, d] ].
Il simbolo ⊡ denota l’operatore di sequenzializzazione se si trova in un nodo interno, mentre denota la sequenza vuota se si trova
su un nodo foglia.
1.4
OPERAZIONI SULLE SEQUENZE
La costruzione di una sequenza [a1 , . . . , an ] può essere vista come l’applicazione dell’operatore n-ario [ ] in notazione distribuita applicato agli operandi
a1 , . . . , an . Tale operazione viene detta sequenzializzazione.
Data una generica sequenza α, con
size α
si denota la lunghezza di α, ossia il numero di elementi che la compongono:
size[a1 , . . . , an ] = n
La sequenza vuota [ ] ha lunghezza 0.
L’operazione di concatenazione fra due sequenze produce come risultato
una sequenza costituita dagli elementi della prima sequenza seguiti dagli elementi della seconda; l’operazione di concatenazione viene solitamente denotata
mediante l’operatore binario infisso ∣ come segue:
[a1 , . . . , an ] ∣ [b1 , . . . , bm ] = [a1 , . . . , an , b1 , . . . , bm ]
Spesso, qualora non risulti ambiguo, il segno ∣ dell’operatore di concatenazione
viene omesso e le due sequenze da concatenare vengono semplicemente scritte
affiancate. La sequenza vuota [ ] costituisce l’elemento neutro (sinistro e
destro) per l’operazione di concatenazione. L’operazione di concatenazione
gode della proprietà associativa, ossia, per generiche sequenze α, β, γ, vale
l’identità (αβ)γ = α(βγ); l’operazione di concatenazione non gode incece della
proprietà commutativa, ossia, in generale, αβ ≠ βα.
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
1.4. OPERAZIONI SULLE SEQUENZE
7
Le operazioni di inserimento in testa ed aggiunta in coda di un elemento in una sequenza possono essere espresse tramite l’operatore [ ] di sequenzializzazione e l’operatore ∣ di concatenazione, come illustrato dai seguenti
esempi:
[x] ∣ [a, b, c] = [x, a, b, c]
[a, b, c] ∣ [x] = [a, b, c, x]
Sulle sequenze si può applicare l’operazione di ripetizione: il risultato della
ripetizione di una sequenza α = [a1 , . . . , an ] per una costante naturale k è
costituito da una sequenza ottenuta concatenando k sequenze α, ossia
[a1 , . . . , an ]k = [a1 , . . . , an , a1 , . . . , an , . . . ]
L’espressione naturale k è detta ripetitore. Il valore k = 1 costituisce l’elemento neutro per l’operazione di ripetizione di sequenze, ossia α1 = α. Per
convenzione si pone α0 = [ ].
L’operazione di ripetizione di sequenze realizza anche il meccanismo di
costruzione e dimensionamento iniziale: l’espressione
[a]n
produce una sequenza di n elementi; ciascun elemento della sequenza viene
inizializzato con il valore a.
L’operazione di ripetizione di una sequenza per un numero gode di alcune
proprietà; in particolare, se α è una sequenza e h e k sono due numeri naturali,
vale la proprietà
αh ∣ αk = αh+k
Esempio 3. A seguire sono riportati alcuni esempi di operazioni e corrispondenti calcoli sulle sequenze:
[a, b, c] ∣ [d, e] = [a, b, c, d, e]
[0]8 = [0, 0, 0, 0, 0, 0, 0, 0]
[1, 2]4 = [1, 2, 1, 2, 1, 2, 1, 2]
[ [4]3 ]2 = [ [4, 4, 4], [4, 4, 4] ]
[2 ∶ 4]3 = [2, 3, 4, 2, 3, 4, 2, 3, 4]
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
8
CAPITOLO 1. SEQUENZE
Esempio 4. Le operazioni di aggregazione e ripetizione di elementi possono
combinarsi in tutti i modi possibili in modo da costituire un’effettiva algebra
con proprie specifiche proprietà; segue qui un esempio di calcolo che illustra
come si opera in quest’algebra:
[a, b] ∣ ([a]2 ∣ [b])2 ∣ [a] → [a, b] ∣ ([a, a] ∣ [b])2 ∣ [a] →
→ [a, b] ∣ [a, a, b]2 ∣ [a] →
→ [a, b] ∣ [a, a, b] ∣ [a, a, b] ∣ [a] →
→ [a, b] ∣ [a] ∣ [a, b] ∣ [a] ∣ [a, b] ∣ [a] →
→ [a, b, a] ∣ [a, b, a] ∣ [a, b, a] →
→ [a, b, a]3
Esempio 5. Nel contesto dei linguaggi di programmazione le sequenze possono
essere denotate mediante degli identificatori e questi identificatori possono
essere usati come elementi costituenti altre sequenze; un esempio di questa
possibilità è riportato nella sequenza di assegnazioni che seguono.
a ← [3, 5]
b ← [4, 2]
c ← a∣b
d ← [a, b]
1.5
CLASSIFICAZIONE DELLE SEQUENZE
Le sequenze possono essere classificate in base a diversi criteri; i più frequentemente usati sono di seguito elencati:
in base al tipo degli elementi componenti : se tutti gli elementi sono
dello stesso tipo le sequenze vengono dette sequenze omogenee, altrimenti
vengono dette sequenze eterogenee o strutture
in base alla struttura degli elementi componenti : se tutti gli elementi
sono elementari le sequenze vengono dette sequenze semplici o piatte,
altrimenti vengono dette sequenze composte o strutturate
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
1.6. LE STRUTTURE
9
in base alle modalità di accesso agli elementi componenti : le operazioni di accesso agli elementi componenti una sequenza sono realizzate
con diverse modalità e danno luogo a diverse tipologie di sequenze; fra
queste le più importanti sono gli array e le liste
in base alle operazioni di modifica : le operazioni di inserimento, eliminazione e modifica degli elementi di una sequenza possono essere realizzate con diverse modalità; a seconda di quali operazioni (e con quali limitazioni) sono permesse, le sequenze assumono diversi nomi (pile, code,
insiemi, . . . ).
Queste diverse tipologie di sequenze saranno trattate nei prossimi capitoli.
1.6
LE STRUTTURE
Gli elementi che costituiscono una sequenza possono essere di tipi diversi. Per
evidenziare questa caratteristica, una sequenza viene detta sequenza eterogenea
o struttura.
Esempio 6. A seguire sono riportate alcune espressioni di tipo struttura:
[7, F ALSE]
[′ +′ , 8, 5, 3, 6, 2]
[′′ M ario′′ ,′′ Rossi′′ , 2,′′ maggio′′ , 2012]
1.7
LE SEQUENZE COMPOSTE
Un’ulteriore generalizzazione delle sequenze eterogenee è rappresentata dalle
sequenze costituite da elementi che sono a loro volta delle sequenze; in questi
casi si parla di sequenze composte. Di contrappunto, le sequenze formate da
elementi atomici (numeri, valori logici, caratteri, stringhe, . . . ) sono dette
sequenze piatte o sequenze elementari.
Esempio 7. A seguire sono riportati alcuni esempi di sequenze composte:
[ [ [ ] ], [ ] ]
[3, [4, 5], 2, [ [6], [ ] ] ]
[ [′′ M ario′′ ,′′ Rossi′′ ], [2,′′ maggio′′ , 2012] ]
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
10
CAPITOLO 1. SEQUENZE
[ [′′ rosso′′ , [255, 0, 0] ], [′′ verde′′ , [0, 255, 0] ] ]
Esempio 8. Una sequenza composta si presta a descrivere un’espressione, rappresentando in modo uniforme operatori di diverse arietà e di diverse notazioni,
mediante la generica notazione
[⊗, x1 , . . . , xn ]
dove ⊗ denota un generico operatore e gli argomenti xi sono delle generiche
espressioni. La seguente tabella descrive alcuni esempi di espressioni descritte in notazione algebrica e nella corrispondente notazione mediante sequenze
composte.
notazione algebrica
2+3
a/(b + c)
x cos(1/x)
x+1
ax+by
notazione mediante sequenza
[+, 2, 3]
[/, a, [+, b, c] ]
[∗, x, [cos, [/, 1, x] ] ]
[/, [+, x, 1], [+, [∗, a, x], [∗, b, y] ]
Esempio 9. Una sequenza può essere costituita da comandi; ad esempio, nel
contesto della grafica della tartaruga, una figura composta da 36 quadrati ruotati l’uno rispetto all’altro di 10 gradi può essere definita mediante la seguente
porzione di codice:
a ← [f orward 100, lef t 90]
q ← a4
f ← [q, lef t 10]36
1.8
OPERAZIONI FUNZIONALI E PROCEDURALI
Le sequenze, essendo degli oggetti composti, vengono elaborate mediante delle
operazioni che possono essere impostate secondo due diverse modalità:
• modalità funzionale: l’operazione genera una nuova sequenza che costituisce il risultato dell’operazione
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
11
1.9. COSTRUZIONE DI SEQUENZE
• modalità procedurale: l’operazione modifica la sequenza alla quale viene
applicata; tale modalità è applicabile solo se l’argomento dell’operazione
è una variabile che identifica una sequenza
Solitamente i due approcci sono entrambi disponibili nella maggior parte
dei linguaggi di programmazione e possono essere utilizzati interscambievolmente. Essi tuttavia sono profondamente diversi e rientrano in due paradigmi
di programmazione distinti; il primo (paradigma funzionale) suggerisce spesso
delle soluzioni ricorsive e non richiede l’uso di variabili ed assegnazioni (in questo contesto le sequenze vengono manipolate mediante uno specifico insieme
di operazioni e vengono dette liste); il secondo approccio (paradigma procedurale) si fonda sul fatto che un’operazione modifica direttamente la sequenza
alla quale viene applicata; in questi casi solitamente si fa ricorso ad algoritmi
basati su cicli e si fa uso di variabili e di assegnazioni che permettono di modificare selettivamente (in base ad un indice di posizione) un elemento della
sequenza; in questi casi le sequenze vengono dette array. Queste due tipologie
di sequenze saranno trattate nei prossimi capitoli.
1.9
COSTRUZIONE DI SEQUENZE
Molti problemi sono caratterizzati dall’avere delle sequenze come risultato.
In tutti questi casi l’algoritmo risolvente il problema costruisce, generalmente
mediante un adeguato controllo ciclico la cui forma dipende dallo specifico
problema, una sequenza che costituirà alla fine il risultato del problema. Tale
sequenza risultato viene spesso costruita accodando man mano degli elementi,
a partire dalla sequenza iniziale vuota. Il seguente esempio illustra la tecnica
appena descritta.
Esempio 10. Sviluppiamo un algoritmo per scomporre un numero naturale
in fattori primi. Facciamo riferimento all’usuale metodo di scomposizione
descritto dallo schema riportato nella figura 1.6 dal quale si ricava 126 = 2⋅32 ⋅7.
In questo caso il risultato potrà essere rappresentato dalla sequenza [2, 3, 3, 7].
126
63
21
7
1
2
3
3
7
Figura 1.6: Schema della scomposizione in fattori primi del numero 126.
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
12
CAPITOLO 1. SEQUENZE
Seguendo la traccia sopra, si arriva all’algoritmo 1.
Algoritmo 1 - Sequenza dei divisori primi di un numero naturale
Input: numero naturale n
Output: sequenza dei divisori primi di n
1: inizializza la sequenza vuota a dei divisori
2: considera d = 2 come primo potenziale divisore
3: while n > 1 do
4:
while n è divisibile per d do
5:
accoda d alla sequenza dei divisori
6:
dividi n per d
7:
end while
8:
considera il numero primo successivo di d
9: end while
10: return sequenza a dei divisori primi
Nell’algoritmo 1 rimangono ancora da esplicitare le soluzioni di alcuni
sottoproblemi; fra questi, l’unico di una qualche complessità è il seguente:
considera il numero primo successivo di d
In prima approssimazione (anche se non ottimale) questo problema può essere
risolto con l’istruzione
incrementa d di 1
La soluzione completa è riportata nell’algoritmo 2.
Algoritmo 2 - Sequenza dei divisori primi di un numero naturale
Input: numero naturale n
Output: sequenza dei divisori primi di n
1: a ← [ ]
2: d ← 2
3: while n > 1 do
4:
while (n%d) = 0 do
5:
a ← a ∣ [d]
6:
n ← n/d
7:
end while
8:
d ++
9: end while
10: return a
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
1.9. COSTRUZIONE DI SEQUENZE
13
Osservando che nessun numero pari, escluso 2, è primo, l’algoritmo di
scomposizione 2 risulta più efficiente se il divisore d viene incrementato di due
unità ad ogni ciclo, ossia se l’istruzione di incremento di d viene sostituita con
incrementa d di 2.
Esempio 11. L’algoritmo sviluppato nell’esempio precedente può essere adattato in modo che vengano determinati i fattori del numero n con le rispettive
molteplicità (vedi tabella 1.7). Una tabella di questo formato può essere
rappresentata mediante la sequenza di coppie [ [2, 1], [3, 2], [7, 1] ].
fattore
2
3
7
molteplicità
1
2
1
Figura 1.7: Tabella della scomposizione del numero 126.
A seguire è riportato l’algoritmo che generalizza la tavola di traccia riportata nella figura 1.7, in due versioni a diverso livello di dettaglio (algoritmi 3
e 4).
Algoritmo 3 - Sequenza dei divisori primi di un numero naturale
Input: numero naturale n
Output: sequenza dei divisori primi di n
1: inizializza la sequenza vuota dei divisori
2: considera d = 2 come primo potenziale divisore
3: while n > 1 do
4:
determina la molteplicità m del divisore d
5:
if m > 0 then
6:
accoda la coppia (d, m) alla sequenza dei divisori
7:
end if
8:
considera il numero primo successivo di d
9: end while
10: return sequenza dei divisori primi
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
14
CAPITOLO 1. SEQUENZE
Algoritmo 4 - Sequenza dei divisori primi di un numero naturale
Input: numero naturale n
Output: sequenza dei divisori primi di n
1: a ← [ ]
2: d ← 2
3: while n > 1 do
4:
m←0
5:
while (n%d) = 0 do
6:
m ++
7:
n ← n/d
8:
end while
9:
if m > 0 then
10:
a ← a ∣ [ [d, m] ]
11:
end if
12:
d ++
13: end while
14: return a
1.10
ELABORAZIONE DI SEQUENZE
Una volta costruite, le sequenze devono essere elaborate. La forma più elementare di elaborazione consiste nel passare in esame tutti gli elementi della sequenza ed elaborarli sequenzialmente mediante un controllo della forma
riportata nell’algoritmo 5.
Algoritmo 5 - Elaborazione sequenziale di una sequenza
Input: sequenza a
1: for each x of a do
2:
elabora elemento x
3: end for
Nell’elaborazione di una sequenza risultano utili i seguenti due predicati:
• operazione di controllo di atomicità:
isatom x
il risultato è T RU E se x è un atomo, F ALSE altrimenti
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
1.10. ELABORAZIONE DI SEQUENZE
15
• operazione di controllo di lista nulla:
isempty a
che è equivalente all’espressione a = [ ].
Esempio 12. Calcolo della somma degli atomi di una sequenza numerica piatta.
Algoritmo 6 - Somma degli elementi di una sequenza semplice numerica
Input: sequenza a
Output: somma degli elementi della sequenza a
1: s ← 0
2: for each x of a do
3:
s←s+x
4: end for
5: return s
Con riferimento all’algoritmo 5, se la sequenza a da elaborare è composta,
l’elemento x dell’elaborazione superficiale può essere a sua volta una sequenza.
Nel caso in cui sia necessaria una elaborazione in profondità fino ad arrivare
agli atomi componenti, è sufficiente adattare l’algoritmo 5 ricorrerendo al predicato isatom. Il questo caso l’algoritmo, per gestire in modo generico i livelli
di annidamento della sequenza, assume una forma ricorsiva, come evidenziato
nell’algoritmo 7.
Algoritmo 7 - elabora(a) : elaborazione in profondità di una sequenza
Input: sequenza a
1: for each x of a do
2:
if isatom(x) then
3:
elabora l’atomo x
4:
else
5:
elabora(x)
6:
end if
7: end for
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
16
CAPITOLO 1. SEQUENZE
Esempio 13. Calcolo della somma degli atomi di una sequenza numerica composta.
Algoritmo 8 - somma(a) : somma elementi di una sequenza composta
Input: sequenza a
Output: somma degli elementi della sequenza composta a
1: s ← 0
2: for each x of a do
3:
if isatom(x) then
4:
s←s+x
5:
else
6:
s ← s + somma(x)
7:
end if
8: end for
9: return s
Gli schemi di elaborazione riportati negli algoritmi 5 e algoritmi 7 non
sono adatti nel caso in cui debbano essere elaborati solo alcuni elementi della
sequenza (ad esempio nel caso in cui si debba ricercare se un dato valore è
presente in una sequenza).
1.11
ESERCIZI
1. Determinare il numero di sottosequenze che si possono estrarre, mantenendo l’ordine di posizione degli elementi, da una sequenza di lunghezza
n composta da elementi distinti.
2. Determinare il numero di sottosequenze di lunghezza k che si possono estrarre, mantenendo l’ordine di posizione degli elementi, da una
sequenza di lunghezza n composta da elementi distinti, con k ≤ n.
3. Determinare il numero di sottosequenze, composte da elementi adiacenti,
che si possono estrarre da una sequenza di lunghezza n composta da
elementi distinti.
4. Determinare il numero di sottosequenze, di lunghezza k, composte da
elementi adiacenti, che si possono estrarre da una sequenza di lunghezza
n composta da elementi distinti, con k ≤ n.
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
17
1.11. ESERCIZI
5. Sia x un atomo e m, n due numeri naturali.
espressioni sono equivalenti:
Discutere se le seguenti
(a) [x]mn
(b) ([x]m )n
(c) ([x]n )m
6. Dato l’atomo x ed il numero naturale k, scrivere un’espressione che
rappresenti la sequenza [x, x, . . . , x] costituita da k atomi x.
7. Date le sequenze a = [x], b = [y], scrivere un’espressione che rappresenti
la sequenza [x, x, . . . , x, y, y, . . . , b] costituita da k atomi x e da k atomi
y.
8. Date le sequenze a = [x], b = [y], scrivere un’espressione che rappresenti
la sequenza [x, y, x, y, . . . , x, y] costituita da k atomi x e da k atomi y
alternati.
9. Data la sequenza a = [x, y, z] calcolare le seguenti espressioni:
(a) a2
(b) [a]2
(c) [a2 ]
(d) [a ∶ a]
(e) a∣a
(f) [a]∣[a]
10. Scrivere delle espressioni che generino le seguenti sequenze:
(a) [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
(b) [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
(c) [1, 2, 1, 2, 1, 2, 1, 2, 1, 2]
(d) [1, 1, 1, 1, 1, 2, 2, 2, 2, 2]
(e) [1, 2, 3, 4, 5, 1, 2, 3, 4, 5]
11. Dato il numero naturale n, generare le seguenti sequenze:
(a) [1, 2, 3, . . . , n]
(b) [n, n − 1, . . . , 3, 2, 1]
(c) [1, 1, 2, 1, 2, 3, 1, 2, 3, 4, . . . , 1, 2, . . . , n]
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
18
CAPITOLO 1. SEQUENZE
(d) [1, 2, 2, 3, 3, 3, . . . , n, n, . . . , n]
12. Determinare le eventuali soluzioni naturali di un’equazione di secondo
grado.
13. Risolvere un’equazione di secondo grado, gestendo anche i casi in cui le
radici siano complesse non reali.
14. Determinare la sequenza delle cifre di un dato numero naturale.
15. Determinare la sequenza dei divisori di un dato numero naturale.
16. Determinare la sequenza dei primi n numeri primi.
17. Determinare tutte le coppie di numeri naturali che danno per prodotto
un dato numero naturale.
18. Dato un numero naturale rappresentante una quantità di denaro espressa
in centesimi di Euro, determinare il più piccolo insieme di monete (da 1,
2, 5, 10, 20, 50 centesimi di Euro) equivalente alla data cifra. Esprimere
il risultato mediante una sequenza di coppie.
19. Risolvere il problema della torre di Hanoi, determinando la sequenza
delle mosse da eseguire.
20. Il triangolo di Pascal è costituito da numeri naturali disposti in righe;
ogni riga è formata da un numero di numeri uguale al numero di riga
e, disponendo le righe in modo centrato, ogni numero è la somma dei
due numeri adiacenti della riga superiore. A seguire è riportata una
porzione del triangolo.
1
1
1
1
1
1
2
3
4
1
3
6
1
4
1
(a) Determinare il k-esimo numero dell’n-esima riga del triangolo di
Pascal (tale numero viene detto binomiale di n su k).
(b) Determinare l’n-esima riga del triangolo di Pascal.
(c) Determinare la sequenza delle prime n righe del triangolo di Pascal
([ [1], [1, 1], [1, 2, 1], . . . ])
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
19
1.11. ESERCIZI
21. Determinare tutte le possibili scomposizioni in addendi non nulli di un
dato numero naturale (indipendentemente dall’ordine); ad esempio, il
numero n = 4 può essere scomposto in addendi come segue:
4 = 4
4 = 1+3
4 = 2+2
4 = 1+1+2
4 = 1+1+1+1
e la soluzione può essere espressa mediante la sequenza
[ [4], [1, 3], [2, 2], [1, 1, 2], [1, 1, 1, 1] ]
22. Data una sequenza semplice di numeri naturali, determinare la sottosequenza dei numeri pari e quella dei numeri dispari. Analogamente per
una sequenza composta.
23. Data una sequenza semplice determinare la sottosequenza degli elementi
di posizione pari e quella degli elementi di posizione dispari.
24. Determinare il numero di atomi presenti in una sequenza composta.
25. Determinare la sequenza degli atomi di una sequenza composta; ad
esempio, data la sequenza composta [ [1, 2], 3, [4, [ ], [ [5], 6] ] ] si deve
determinare la sequenza semplice [1, 2, 3, 4, 5, 6].
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
20
CAPITOLO 1. SEQUENZE
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
Capitolo 2
Liste
Nella sua forma più semplice, una tabella può essere una lista lineare di elementi, quando le sue proprietà strutturali rilevanti includono la risposta a questioni come: quale elemento è il primo della lista?
qual è l’ultimo? quali elementi precedono
e seguono un dato elemento? quanti elementi ci sono nella lista? C’è molto da
dire sulla struttura perfino su questo caso
apparentemente semplice.
D. Knuth,
The Art of Computer Programming
Le liste sono delle particolari sequenze caratterizzate da specifici operatori
di accesso agli elementi che le compongono. Questi operatori sono costruiti
secondo l’impostazione del paradigma funzionale: il risultato di un’operazione
è sempre un atomo o una lista e non esistono operatori per modificare una
lista. Coerentemente con questa impostazione, le liste vengono generalmente
elaborate mediante degli algoritmi ricorsivi.
Le liste costituiscono una importante struttura dati, così potente e duttile
che si presta a supplire a tutte le altre strutture organizzative. Ne è conferma
il fatto che alcuni linguaggi di programmazione (Lisp, Prolog, Logo, Askell ed
altri) hanno le liste come unica struttura dati disponibile.
21
22
CAPITOLO 2. LISTE
2.1
LE LISTE
Dal punto di vista strutturale le liste sono delle sequenze caratterizzate da
specifici operatori per la creazione, la composizione e l’accesso agli elementi. Nella trattazione delle liste risulta spesso più utile riferirsi alla seguente
definizione alternativa:
DEFINIZIONE 2. Una lista è una sequenza vuota di elementi, denotata
con [ ], oppure è una coppia (x, a), dove x è un atomo ed a è una lista. Una
tale coppia viene denotata 1 con [x ∶ a]. L’elemento x viene detto f irst e la
lista a è detta rest.
Prendendo spunto da questa definizione ricorsiva, per gestire le liste si
fa uso alle seguenti operazioni di base (predisposte in alcuni linguaggi di
programmazione):
• operazione di costruzione prefissa: dato un elemento x (atomo o lista)
ed una lista a, l’operazione
[x ∶ a]
genera una lista avente x come primo elemento con a seguire gli elementi
di a. L’operazione di costruzione prefissa [x ∶ a] può essere espressa in
modo equivalente mediante l’operatore di concatenazione con [x]∣a
• operazione di primo: il risultato è costituito dal primo elemento della
lista:
f irst[a1 , . . . , an ] = a1
Se a = [ ] o a =⊥, allora f irst α =⊥.
• operazione di resto: il risultato è costituito dalla lista ottenuta escludendo il primo elemento:
rest[a1 , . . . , an ] = [a2 , . . . , an ]
Anche in questo caso, se a = [ ] o a =⊥, allora rest a =⊥.
Indicando con a una generica lista, le tre operazioni di costruzione prefissa
[◻ ∶ ◻], f irst e rest sono fra loro legate dalla proprietà invariante
[f irst a ∶ rest a] = a
1
Nel linguaggio Prolog viene utilizzata la notazione [x ∣ a].
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
2.2. ELABORAZIONE DI LISTE
23
Osservazione. L’operatore [◻ ∶ ◻] costituisce il meccanismo fondamentale per generare una lista a partire dalla lista vuota [ ]; gli operatori f irst
e rest permettono di accedere ai vari elementi di una lista, consentendone
l’elaborazione; il predicato isatom costituisce l’operatore per gestire la struttura composta di una lista, ai vari livelli di annidamento; l’elaborazione degli
elementi di una lista viene svolta in modo ricorsivo, dove il predicato isnull
costituisce il test per chiudere la ricorsione. Queste operazioni costituiscono
un insieme completo di operazione per elaborare le liste.
Esempio 14. Gli esempi che seguono illustrano le operazioni sopra descritte.
Si noti l’uso delle parentesi per forzare l’associatività a destra.
[a ∶ [b ∶ [c] ] ] → [a, b, c, d]
f irst(rest[a, b, c]) → b
rest[a, b, c, d] → [b, c, d]
size(rest[a, b, c]) → 2
isatom[ [a], b] → F ALSE
isempty(rest[a]) → T RU E
isatom(f irst[ [a], b]) → F ALSE
isatom(f irst(rest[ [a], b])) → T RU E
2.2
ELABORAZIONE DI LISTE
Le liste possono essere eleborate con due meccanismi formalmente distinti ma
computazionalmente equivalenti. Una prima modalità consiste nell’elaborazione degli elementi della lista in modalità iterativa, mediante un ciclo, come
descritto nell’algoritmo 9.
Algoritmo 9 - Elaborazione di una lista a (modalità iterativa)
Input: sequenza a
1: b ← a
2: while ¬(isempty b) do
3:
elabora l’elemento f irst b
4:
b ← rest b
5: end while
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
24
CAPITOLO 2. LISTE
Una modalità di elaborazione alternativa è descritta nell’algoritmo 10.
Tale algoritmo si fonda sulla definizione ricorsiva di lista.
Algoritmo 10 - Elaborazione di una lista a (modalità ricorsiva)
Input: sequenza a
1: if ¬(isempty a) then
2:
elabora l’elemento f irst a
3:
elabora la lista rest a
4: end if
Esempio 15. Applichiamo gli algoritmi 9 e 10 alla determinazione del k-esimo
elemento di una lista α. Nella modalità iterativa e ricorsiva i due algoritmi
si scrivono come descritto negli algoritmi 11 e 12. Come si può dedurre dal
confronto dei due algoritmi, la versione ricorsiva risulta più concisa e leggibile
della corrispondente versione iterativa.
Algoritmo 11 - item[a, k] : k-esimo elemento della lista a (versione iterativa)
Input: lista a, numero naturale k
Output: k-esimo elemento della lista a
1: t ← a
2: i ← 1
3: while i < k do
4:
t ← rest t
5:
i++
6: end while
7: return f irst t
Algoritmo 12 - item[a, k] : k-esimo elemento della lista a (versione ricorsiva)
Input: lista a, numero naturale k
Output: k-esimo elemento della lista a
1: if k = 1 then
2:
return f irst a
3: else
4:
return item[rest a, k − 1]
5: end if
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
2.3. ALGORITMI SULLE LISTE SEMPLICI
2.3
25
ALGORITMI SULLE LISTE SEMPLICI
Le liste, essendo delle strutture che si prestano ad essere definite in modo
ricorsivo, suggeriscono, in modo naturale, dei procedimenti ricorsivi per il loro
trattamento. A seguire ne sono riportati alcuni.
Esempio 16. Lista costituita dai primi n numeri naturali:
Algoritmo 13 - list n : lista [1, 2, . . . , n]
Input: numero naturale n
Output: lista [1, 2, . . . , n]
1: if n = 0 then
2:
return [ ]
3: else
4:
return list(n − 1) ∣ [n]
5: end if
Esempio 17. Somma degli elementi di una lista semplice di numeri naturali:
Algoritmo 14 - sum a : somma degli elementi della lista a
Input: lista a = [a1 , . . . , an ]
Output: a1 + ⋅ ⋅ ⋅ + an
1: if isempty a then
2:
return 0
3: else
4:
return f irst a + sum(rest a)
5: end if
Esempio 18. Lunghezza di una lista semplice:
Algoritmo 15 - len a : lunghezza della lista a
Input: lista a = [a1 , . . . , an ]
Output: n
1: if isempty a then
2:
return 0
3: else
4:
return 1 + len(rest a)
5: end if
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
26
CAPITOLO 2. LISTE
Esempio 19. Inversione di una lista semplice:
Algoritmo 16 - inv a : inverso della lista a
Input: lista a = [a1 , . . . , an ]
Output: lista [an , . . . , a1 ]
1: if isempty a then
2:
return [ ]
3: else
4:
return inv(rest a) ∣ [f irst a]
5: end if
Esempio 20. Fusione di due liste ordinate:
Algoritmo 17 - merge[a, b] : fusione di due liste ordinate a e b
Input: liste a e b ordinate (crescentemente)
Output: lista ottenuta dalla fusione di a e b
1: if isempty a then
2:
return b
3: else if isempty b then
4:
return a
5: else if f irst a < f irst b then
6:
return [(f irst a) ∶ merge[rest a, b] ]
7: else
8:
return [(f irst b) ∶ merge[a, rest b] ]
9: end if
Esempio 21. Date due n-liste a = [a, ..., an ] e b = [b1 , ..., bn ], determiniamo
la lista composta dalle coppie di elementi aventi la stessa posizione nelle due
liste:
[ [a1 , b1 ], [a2 , b2 ], ..., [an , bn ] ]
La soluzione di questo problema è riportata nell’algoritmo 18.
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
2.3. ALGORITMI SULLE LISTE SEMPLICI
27
Algoritmo 18 - coppie[a, b] : lista delle coppie delle due liste a e b
Input: liste a e b
Output: lista delle coppie formate combinando gli elementi di uguale
posizione delle due liste a e b
1: if (isempty a) ∨ (isempty b) then
2:
return [ ]
3: else
4:
return [ [f irst a, f irst b] ∶ coppie[rest a, rest b] ]
5: end if
Esempio 22. Il prodotto cartesiano fra due liste è costituito dalla lista di
tutte le possibili coppie che si possono formare prendendo il primo elemento
dalla prima lista ed il secondo elemento dalla seconda; ad esempio il prodotto
cartesiano fra le due liste [a, b, c] e [x, y] produce la lista di coppie
[ [a, x], [a, y], [b, x], [b, y], [c, x], [c, y] ]
.
Costruiamo dapprima l’algoritmo che esegue il prodotto cartesiano fra un
atomo ed una lista (algoritmo 19).
Algoritmo 19 - prod[x, a] : prodotto dell’atomo x per la lista a
Input: atomo x, lista a = [a1 , . . . , an ]
Output: lista delle coppie [ [x, a1 ], [x, a2 ], . . . , [x, an ] ]
1: if isempty a then
2:
return [ ]
3: else
4:
return [ [x, f irst a] ∶ prod[x, rest a] ]
5: end if
A questo punto il prodotto cartesiano fra due liste si esprime mediante il
seguente algoritmo 20.
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
28
CAPITOLO 2. LISTE
Algoritmo 20 - prodcart[a, b] : prodotto cartesiano fra le due liste a e b
Input: liste a e b
Output: lista delle coppie del prodotto cartesiano a × b
1: if isempty a then
2:
return [ ]
3: else
4:
return prod[f irst a, b] ∣ prodcart[rest a, b]
5: end if
2.4
ALGORITMI SULLE LISTE COMPOSTE
In modo analogo alle forme di elaborazione viste nel paragrafo precedente per
l’elaborazione di liste semplici, vengono presentati a seguire degli esempi di
elaborazione di liste composte.
Esempio 23. Determiniamo il massimo di una lista composta . A questo scopo
consideriamo dapprima l’algoritmo 21 che determina il massimo di una lista
semplice (in questo algoritmo max[x, y] denota il massimo fra i due elementi
della coppia [x, y]).
Algoritmo 21 - maxl a : massimo della lista semplice a
Input: lista semplice a
Output: massimo della lista semplice a
1: if isempty a then
2:
return N U LL
3: else if (size a) = 1 then
4:
return f irst a
5: else
6:
return max[f irst a, maxl(rest a)]
7: end if
L’algoritmo 21 può essere adattato al caso di una lista composta: è sufficiente applicare ricorsivamente l’algoritmo agli elementi che non sono atomici,
come descritto nell’algoritmo 22.
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
2.4. ALGORITMI SULLE LISTE COMPOSTE
29
Algoritmo 22 - maxlc a : massimo della lista composta a
Input: lista semplice a
Output: massimo della lista composta a
1: if isempty a then
2:
return N U LL
3: else if (size a) = 1 then
4:
if isatom(f irst a) then
5:
return f irst a
6:
else
7:
return maxlc(f irst a)
8:
end if
9: else
10:
return max[maxlc[f irst a], maxlc(rest a) ]
11: end if
Esempio 24. Una interessante operazione su una lista composta consiste nel
determinare la corrispondente lista semplice formata dagli atomi della lista
(operazione di flat di una lista); ad esempio la lista [ [ [x], 1, [y, z, t], 2, [3] ],
appiattita, produce la lista [x, 1, y, z, t, 2, 3]. Questa operazione è descritta
nell’algoritmo 23.
Algoritmo 23 - f lat a : lista degli atomi della lista a
Input: lista a
Output: lista degli atomi di a
1: if isempty a then
2:
return [ ]
3: else if isatom(f irst a) then
4:
return [f irst a ∶ f lat(rest a)]
5: else
6:
return f lat(f irst a) ∣ f lat(rest a)
7: end if
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
30
CAPITOLO 2. LISTE
2.5
ESERCIZI
1. Valutare le seguenti espressioni:
(a) f irst[1, 2, 3]
(b) rest[1, 2, 3]
(c) f irst(rest[1, 2 < 3, 4])
(d) rest(f irst[ [ [1] ], [2], 3])
(e) f irst[x] = [x]
(f) if [3 < 4, x, y]
(g) f irst(rest[1, 2, 3]) + if [2 = 3, 4, 5]
(h) [size[0] ∶ rest[1..4] ]
(i) if [isatom(rest[1]), f irst[2, 3], f irst(rest[4, 5])]
(j) ([ ] ∣ [x] = [x]) < (isatom[x])
2. Risolvere la seguente equazione (nell’incognita x):
[size x] = x
3. Decidere se una lista è formata da un solo elemento (eventualmente
composto).
4. Analizzare le operazioni relative alle liste, discutendo quali sono totali
e quali sono parziali, indicando per queste ultime la precondizione che
deve essere verificata affinché l’operazione sia applicabile. Discutere
quali strategie si possono adottare per il controllo che l’invocazione di
tali operazioni non generi errore.
5. Sia a = [a1 , . . . , an ] una generica lista di n elementi. Stabilire per
quali valori di n e per quale struttura della lista a risulta valutabile
l’espressione
f irst(rest(f irst(rest a)))
Determinarne il valore.
6. Sia a = [a1 , . . . , an ] una generica lista di n elementi. Stabilire il valore
delle seguenti espressioni:
(a) size(rest a)
(b) f irst(rest(rest a))
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
2.5. ESERCIZI
31
7. Data la lista a = [x, y, z], scrivere un’espressione che produca la lista
[z, y, x].
8. Data la lista [ [a, b], c, d, [e, [f ] ], g, [h] ], scrivere delle espressioni che
forniscano, rispettivamente, i valori c, f e g.
9. Data la lista a = [x], scrivere, in funzione di a, un’espressione che
rappresenti la lista [ [x], x, [ ] ].
10. Date le due liste a = [p, q, r] e b = [x, y], scrivere un’espressione coinvolgente solo a e b che, valutata, costituisca la lista [p, [x], [q, r] ].
11. Data la lista a = [a1 , . . . , an ], determinare la lista costituita dagli elementi
di a maggiori di un dato elemento x. Analogamente per una lista
composta.
12. Determinare la somma degli elementi di una lista semplice numerica.
Analogamente per una lista composta.
13. Data una lista semplice di numeri naturali, calcolare la sottolista dei
numeri pari. Analogamente per una lista composta.
14. Data una lista semplice determinare la sottolista degli elementi di posizione pari.
15. Date due liste semplici, determinare la lista degli elementi comuni delle
due liste.
16. Inserire in una lista semplice un dato elemento in una data posizione.
Ad esempio, inserendo nella lista [5, 2, 6, 1, 8] l’elemento 7 alla posizione
4 si ottiene la lista [5, 2, 6, 1, 4, 8].
17. Data una lista a = [a1 , . . . , an ], determinare la lista ottenuta da a mediante una rotazione a destra di una posizione in modo ciclico, ottenendo
una lista a′ = [an , a1 , . . . , an−1 ].
18. Data una lista a = [a1 , . . . , an ], determinare la lista ottenuta da a mediante una rotazione a sinistra di una posizione in modo ciclico, ottenendo
una lista a′ = [a2 , . . . , an , a1 ].
19. Descrivere, in modo iterativo ed in modo ricorsivo, un algoritmo per decidere se in una lista semplice è presente un dato elemento. Analogamente
per una lista composta.
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
32
CAPITOLO 2. LISTE
20. Decidere se una lista semplice è composta da elementi tutti uguali.
21. Decidere se due liste semplici sono uguali (basandosi sull’operatore di
uguaglianza fra atomi). Le due liste devono avere la stessa lunghezza
ed avere gli elementi uguali nelle stesse posizioni.
22. Decidere se una lista semplice è ordinata.
23. Eliminare i valori ripetuti in una lista semplice, mantenendo la prima occorrenza degli elementi che sono presenti più di una volta; ad
esempio, data la lista [4, 1, 3, 4, 1, 1, 2, 3, 7, , 3], si deve ottenere la lista
[4, 1, 3, 2, 7].
24. Eliminare i valori ripetuti in una lista semplice ordinata; ad esempio,
data la lista [3, 4, 4, 4, 6, 8, 9, 9], si deve ottenere la lista [3, 4, 6, 8, 9].
25. Determinare il rovescio di una lista composta; ad esempio, il rovescio
della lista [1, [2, 3], 4, [5] ] è la lista [ [5], 4, [2, 3], 1].
26. Determinare il numero di atomi di una lista; ad esempio, il numero di
atomi della lista [1, [2, 3], 4, [5] ] è 5.
27. Una lista semplice di numeri naturali contiene le cifre che rappresentano
un numero in notazione decimale. Determinare il numero corrispondente. Ad esempio la lista [3, 7, 4] rappresenta il numero 374.
28. Una lista semplice di numeri naturali contiene le cifre che rappresentano
un numero in notazione decimale. Determinare la lista corrispondente
al successivo. Ad esempio, data la lista [3, 5, 9] si dovrà determinare la
lista [3, 6, 0].
29. Determinare la distanza fra due n-liste a = [a1 , ...., an ] e b = [b1 , ..., bn ] di
numeri, sommando le distanze delle coppie di elementi corrispondenti:
dist(a, b) = Σ∣ai − bi ]
30. Comprimere e decomprimere una lista, secondo i seguenti schemi di
trasformazione di evidente interpretazione.
(a) [a, b, b, c, d, d, d, d] ↔ [ [a, 1], [b, 2], [c, 1], [d, 4] ]
(b) [a, b, b, c, d, d, d, d] ↔ [a, 1, b, 2, c, 1, d, 4]
(c) [a, b, b, c, d, d, d, d] ↔ [a, [b, 2], c, [d, 4] ]
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
33
2.5. ESERCIZI
31. Comprimere e decomprimere una lista semplice numerica, secondo lo
schema di trasformazione descritto nel seguente esempio:
[2, 3, 4, 5, 7, 9, 10] ↔ [ [2, 5], 7, [9, 10] ]
32. Determinare il livello (di annidamento) di una lista (ossia l’altezza dell’albero che la rappresenta); ad esempio: l’atomo 3 ha livello 0; la lista semplice [2, 4, 6] ha livello 1; la lista [1, 3, [4] ] ha livello 2; la lista
[1, [3, 2, 5], [7, [6, 7] ], 8] ha livello 3.
33. Con struttura di una lista si intende la lista ottenuta sostituendo ciascun
atomo con un simbolo convenzionale (∗); ad esempio la struttura della
lista [a, [b, c], d, [ [e], f ] ] è la lista [∗, [∗, ∗], ∗, [ [∗], ∗] ]. Data una lista,
determinarne la struttura. Due liste aventi la stessa struttura sono dette
isomorfe; ad esempio sono isomorfe le seguenti due liste: [ [a], b, [ ] ],
[ [1], 2, [ ] ]. Decidere se due date liste sono isomorfe.
34. Con scheletro di una lista si intende la lista in cui vengono eliminati
tutti gli atomi e vengono mantenute solo le parentesi di annidamento; ad
esempio lo scheletro della lista [a, [b, c], d, [ [e], f ] ] è la lista [ [ ], [ [ ] ] ].
Data una lista, determinarne lo scheletro.
35. Una lista è formata dalle coppie di elementi che sono in una relazione
di equivalenza. Determinare le liste formate dalle liste di elementi della
partizione indotta dalla relazione di equivalenza.
36. Decidere se una lista semplice è contenuta all’interno di un’altra lista
semplice. Ad esempio la lista [3, 1, 4] è contenuta nella lista [3, 2, 3, 1, 4]
ma non è contenuta nella lista [5, 3, 2, 1, 4]. Estendere al caso un cui le
due liste possano essere composte.
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
34
CAPITOLO 2. LISTE
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
Capitolo 3
Array
In situazioni più complicate, la tabella potrebbe essere un array bi-dimensionale, o
potrebbe essere un array n-dimensionale
per valori più elevati di n; potrebbe essere una struttura ad albero, rappresentante
relazioni gerarchiche o suddivisioni; o potrebbe essere una complessa struttura con
molti legami con un grande numero di interconnessioni come possiamo trovare in un
cervello umano.
D. Knuth,
The Art of Computer Programming
Gli array costituiscono le forme più frequenti ed utilizzate di sequenze.
Sono caratterizzati dal metodo di accesso ad indice nella forma a[i]. Costituiscono una forma di composizione molto duttile e molto utilizzata essendo
di supporto a molti importanti algoritmi (ricerca, ordinamento, . . . ).
35
36
3.1
CAPITOLO 3. ARRAY
GLI ARRAY
Qualora una sequenza venga elaborata mediante un particolare operatore di
accesso, come descritto nella seguente definizione, viene detta array.
DEFINIZIONE 3. Gli array sono delle sequenze caratterizzate dall’operatore
di selezione ad indice: se a = [a1 , . . . , an ] è una sequenza di elementi ed e è
un’espressione numerica naturale che, valutata, vale il valore naturale i, allora
a[e]
denota l’i-esimo elemento della sequenza a, ossia ai . Per indicare un array il
cui generico elemento è ai si usa spesso la notazione [ai ].
L’operatore di accesso mediante indice viene usato anche per modificare
una sequenza, secondo la seguente sintassi: se a è una sequenza, e un’espressione che, valutata, vale il numero naturale i ed esp è una generica espressione,
con la sintassi
a[e] ← esp
si ottiene l’effetto di modificare l’i-esimo elemento della sequenza a, assegnandogli il valore ottenuto dalla valutazione dell’espressione esp.
Esempio 25. Esempi di assegnazioni sugli array:
a ← [0]8
a[1] ← 4
a[a[1] ] ← 6 − a[1]
Per esercizio si chiede di descrivere il contenuto dell’array a alla fine della
sequenza di istruzioni sopra elencate.
3.2
ELABORAZIONE DI ARRAY
Lo schema generale di elaborazione di un array a di dimensione n è descritto
dall’algoritmo 24.
Algoritmo 24 - Elaborazione di un array a di dimensione n
Input: array a = [a1 , . . . , an ]
1: for i from 1 to n do
2:
elabora l’elemento ai
3: end for
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
3.3. ARRAY E LISTE
37
Gli esempi che seguono illustrano l’applicazione dell’algoritmo 24.
Esempio 26. Il seguente algoritmo calcola la somma degli elementi di un array.
Algoritmo 25 - somma(a) : somma degli elementi dell’array a
Input: array a = [a1 , . . . , an ]
Output: a1 + ⋅ ⋅ ⋅ + an
1: s ← 0
2: for i from 1 to size(a) do
3:
s ← s + ai
4: end for
5: return s
Esempio 27. A seguire è riportato un algoritmo per la determinazione del
massimo e del minimo di un array.
Algoritmo 26 - minmax(a) : minimo e massimo dell’array a
Input: array a = [a1 , . . . , an ]
Output: [min(a), max(a)]
1: min ← a[1]
2: max ← min
3: for i from 2 to size(a) do
4:
if a[i] < min then
5:
min ← a[i]
6:
else if a[i] > max then
7:
max ← a[i]
8:
end if
9: end for
10: return [min, max]
3.3
ARRAY E LISTE
Array e liste sono delle strutture aggregative equivalenti dal punto di vista
strutturale (sono entrambe delle sequenze), ma differenziate dal diverso meccanismo di accesso e manipolazione degli elementi componenti: gli array vengono elaborati modificandone direttamente gli elementi, secondo un’impostazione tipicamente procedurale-imperativa; le liste vengono elaborate in modo
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
38
CAPITOLO 3. ARRAY
funzionale (e spesso ricorsivo) mediante delle funzioni che ritornano liste come
risultato. Tale differenza fra array e liste non è solo formale e superficiale ma induce una profonda differenza sui meccanismi algoritmici per la loro
elaborazione.
Benché gli operatori di accesso agli elementi di un array e di una lista siano utilizzabili contemporaneamente, si preferisce utilizzarli in modo esclusivo.
Il tipico metodo di elaborazione di un array avviene come segue: si definisce
inizialmente l’array di una data dimensione; successivamente l’array viene elaborato mediante l’uso combinato dell’operatore di accesso e dell’operatore di
assegnazione. Invece, il tipico metodo di elaborazione di una lista può essere
descritto come segue: a partire dalla lista vuota [ ], mediante l’uso combinato
degli operatori di lista, si ottiene una lista risultato.
Gli operatori di accesso degli array e delle liste sono fra loro legati; ad
esempio:
a[1]
equivale a
f irst a
a[2]
equivale a
f irst(rest a)
a[2 .. size a]
equivale a
rest a
a[1] ←
equivale a
a ← [x] ∣ rest a)
In alcuni casi le operazioni su array e liste possono essere usate contemporaneamente, come evidenziato nel seguente esempio.
Esempio 28. Lista delle dimensioni di un array : 0 per elemento non array,
[n] per un array di dimensione n, [2, 3] per un array 2 × 3.
Algoritmo 27 - dimensioni(a) : lista delle dimensioni della lista a
Input: array a
Output: lista delle dimensioni di a
1: if isatom a then
2:
return [ ]
3: else
4:
return [size a] ∣ dimensioni(a1 )
5: end if
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
39
3.4. LE MATRICI
3.4
LE MATRICI
Gli array possono essere composti da elementi che sono a loro volta array
(solitamente tutti della stessa lunghezza). Tali array vengono comunemente
detti matrici (bidimensionali).
Esempio 29. L’array
[ [2, 3, 7], [1, 1, 4] ]
costituisce una matrice bidimensionale. Una tale matrice viene usualmente
descritta in forma tabellare come segue:
2
1
3
1
7
4
Se a denota una matrice bidimensionale, coerentemente con la notazione
usata per l’operatore di selezione ad indice, con la notazione
a[i]
si denota l’array costituito dall’i-esima riga della matrice a, mentre con
a[i][j]
si denota il j-esimo elemento dell’i-esima riga della matrice a. Per semplicità
di scrittura, l’elemento a[i][j] viene spesso indicato con la notazione aij . Per
indicare una matrice il cui generico elemento è aij si usa spesso la notazione
[aij ].
Con la scrittura
a[i][j] ← esp
si ottiene l’effetto di assegnare all’elemento a[i][j] il valore ottenuto dalla
valutazione dell’espressione esp.
Esempio 30. Esempi di operazioni sulle matrici:
a ← [ [0]4 ]3
a[1][2] ← 5
Per esercizio si chiede di descrivere il contenuto della matrice a alla fine delle
istruzioni sopra elencate.
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
40
3.5
CAPITOLO 3. ARRAY
ELABORAZIONE DI MATRICI
Lo schema generale di elaborazione di una matrice bidimensionale a di m righe
ed n colonne è descritto nell’algoritmo 28.
Algoritmo 28 - Elaborazione di una matrice a = [aij ] di dimensioni m × n
Input: matrice a di dimensione m × n
1: for i from 1 to m do
2:
for j from 1 to n do
3:
elabora l’elemento aij
4:
end for
5: end for
Consideriamo ora delle operazione fra matrici numeriche, operazioni che
si presentano in moltissime applicazioni e per questo frequentemente eseguite
sugli elaboratori.
DEFINIZIONE 4. Siano a = [aij ], b = [bij ] due matrici numeriche m × n.
Si definisce come somma a + b fra le due matrici a e b la matrice c = [cij ] di
dimensioni m × n il cui generico elemento cij è definito da
cij = aij + bij
L’operazione di somma fra matrici richiede che le due matrici abbiano le stesse
dimensioni. Quando due matrici soddisfano a questo vincolo sulle dimensioni
si dice che le due matrici sono somma-compatibili.
Adattando a questa definizione lo schema generale dell’algoritmo 28, il
calcolo della somma fra due matrici numeriche può essere descritto mediante
l’algoritmo 29.
Algoritmo 29 - Somma fra matrici
Input: matrici a e b di dimensione m × n
Output: matrice c = a + b
1: for i from 1 to m do
2:
for j from 1 to n do
3:
cij ← aij + bij
4:
end for
5: end for
6: return c = [cij ]
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
41
3.5. ELABORAZIONE DI MATRICI
L’operazione di moltiplicazione fra matrici si basa sulla seguente
DEFINIZIONE 5. Sia a = [aij ] una matrice numerica m × n e b = [bij ] una
matrice numerica n × p. Si definisce come prodotto a ∗ b fra le due matrici a e
b la matrice c = [cij ] di dimensioni m × p il cui generico elemento cij è definito
da
n
cij = ∑ aik bkj
k=1
L’operazione di prodotto richiede che il numero di colonne della prima matrice sia uguale al numero di righe della seconda. Due matrici che soddisfino
a questo vincolo sulle dimensioni vengono dette prodotto-compatibili. Spesso
l’operazione di moltiplicazione fra due matrici a e b viene indicata semplicemente giustapponendo le due matrici, con la notazione ab. Il calcolo del
prodotto fra due matrici numeriche, basato direttamente sulla definizione data
sopra, è descritto dall’algoritmo 30.
Algoritmo 30 - Prodotto fra matrici
Input: matrici a di dimensione m × n e b di dimensione n × p
Output: matrice c = a ∗ b (di dimensione m × p)
1: for i from 1 to m do
2:
for j from 1 to n do
3:
cij ← 0
4:
for k from 1 to n do
5:
cij ← cij + aik bkj
6:
end for
7:
end for
8: end for
9: return c = [cij ]
Esempio 31. Applicando l’algoritmo 30 si ricava il seguente prodotto:
3
1
0
2
2
4
∗
2
0
1
5
1
3
2
3
=
7
3
5 18
2
6
4
6
8 12 10 22
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
42
3.6
CAPITOLO 3. ARRAY
SOLUZIONE DI UN SISTEMA LINEARE
Sia A una matrice quadrata di dimensioni n × n e b un vettore n-dimensionale.
A seguire è riportato l’algoritmo per la soluzione di un sistema lineare A x = b
mediante il metodo di riduzione di Gauss con pivoting parziale. Il procedimento si basa sulla trasformazione del sistema Ax = b in un sistema equivalente
(avente la stessa soluzione) riducendo la matrice A ad una matrice avente tutti
0 nel triangolo inferiore sinistro. La seconda fase del procedimento consiste
nel calcolo a ritroso (xn , xn−1 , . . . , x1 ) delle incognite. Questo procedimento è
descritto nell’algoritmo 31.
Algoritmo 31 - Soluzione del sistema lineare Ax = b
Input: matrice A di dimensioni n × n, vettore b di dimensione n
Output: vettore x = [xi ] (di dimensione n) delle incognite
1: // riduzione a 0 degli elementi del triangolo inferiore sinistro
2: for j from 1 to n − 1 do
3:
ricerca della riga p del pivot
4:
scambio delle righe j ↔ p
5:
riduzione a 0 degli elementi della j-esima colonna
6: end for
7: // soluzione del sistema con valutazione a ritroso delle incognite xi
8: for i from n downto 1 do
9:
calcola xi a partire dalla conoscenza di xi+1 , . . . , xn
10: end for
11: return vettore x = [xi ]
L’algoritmo 31 può essere ulteriormente raffinato come descritto nell’algoritmo 32. In entrambi questi algoritmi si assume l’ipotesi che il sistema Ax = b
ammetta un’unica soluzione.
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
3.6. SOLUZIONE DI UN SISTEMA LINEARE
43
Algoritmo 32 - Soluzione del sistema lineare Ax = b
Input: matrice A di dimensione n × n, vettore b di dimensione n
Output: vettore x = [xi ] (di dimensione n) delle incognite
1: // riduzione a 0 degli elementi del triangolo inferiore sinistro
2: for j from 1 to n − 1 do
3:
// ricerca della riga p del pivot
4:
p←j
5:
for k from j + 1 to n do
6:
if ∣akj ∣ > ∣apj ∣ then
7:
p←k
8:
end if
9:
end for
10:
// scambio delle righe j ↔ p
11:
if j ≠ p then
12:
for k from 1 to n do
13:
scambia ajk ↔ apk
14:
end for
15:
scambia bj ↔ bp
16:
end if
17:
// riduzione a 0 degli elementi della j-esima colonna
18:
for i from j + 1 to n do
19:
f ← aij /ajj
20:
for k from 1 to n do
21:
aik ← aik − ajk ∗ f
22:
end for
23:
bi ← bi − bj ∗ f
24:
end for
25: end for
26: // soluzione del sistema con valutazione a ritroso delle incognite
27: for i from n downto 1 do
28:
s←0
29:
for k from i + 1 to n do
30:
s ← s + aik ∗ xk
31:
end for
32:
xi ← (bi − s)/aii
33: end for
34: return vettore x = [xi ]
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
44
CAPITOLO 3. ARRAY
3.7
ESERCIZI
1. Valutare le seguenti espressioni coinvolgenti gli array:
(a) [a, b, c, d] [3]
(b) [ [a, b, c], [x, y] ] [2] [1]
(c) [ [a, b, c]2 ][1] [5]
(d) [ [1, 2, 3]4 [5] ]6
2. Scrivere delle espressioni che producano i seguenti array di dimensione
16 (si consideri che viene assunta l’associatività a sinistra degli operatori
sugli array):
(a) [a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a]
(b) [a, b, a, b, a, b, a, b, a, b, a, b, a, b, a, b]
(c) [a, a, b, b, a, a, b, b, a, a, b, b, a, a, b, b]
(d) [a, a, a, a, a, a, a, a, b, b, b, b, b, b, b, b]
3. Determinare il massimo di un array di numeri.
4. Determinare le posizioni del massimo di un array di numeri.
5. Determinare, mediante un’unica scansione, il minimo ed il massimo
elemento presenti in un array di numeri.
6. Determinare il massimo e la sua frequenza, in un array di numeri.
7. Determinare il massimo e le sue posizioni, in un array di numeri.
8. Determinare il prodotto degli elementi di un array di numeri.
9. Determinare i numeri primi presenti in un array di numeri naturali.
10. Determinare il massimo comune divisore ed il minimo comune multiplo
di un array di numeri naturali.
11. Decidere se all’interno di un dato array è presente un dato valore.
12. Determinare la frequenza con la quale compare un dato valore in un
array.
13. Determinare il risultato dell’operazione and eseguita su tutti gli elementi
di un array di valori booleani. Analogamente per l’operatore or.
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
45
3.7. ESERCIZI
14. Determinare la media degli elementi di un array di numeri. Si assuma
l’ipotesi che l’array contenga almeno un elemento.
15. Determinare la media degli elementi di un array numerico, escludendo
dalla media i valori uguali a zero.
16. Determinare il valore dell’elemento più vicino alla media degli elementi
di un array di numeri. Ad esempio, l’elemento più vicino alla media 3.2
dell’array [2, 7, 1, 4, 2] è 4.
17. Valutare la media k-filtrata, dei valori di un array numerico di dimensione n ottenuta facendo la media aritmetica degli n − k elementi dell’array scartando i k elementi più lontani dalla media aritmetica dell’array
originale.
18. Dato un array numerico a di dimensione n, valutare lo scarto quadratico
medio di a, definito dalla seguente espressione:
¿
Á n
Á
À( ∑ (ak − m)2 ) /n
k=1
essendo m la media dei valori dell’array.
19. Sia a un array di n di numeri reali positivi e w un array di n numeri
reali non negativi (detti pesi) tali che ∑nk=1 wk = 1. Calcolare i valori
delle seguenti medie pesate:
n
A[a, w] = ∑ ak wk
(media aritmetica)
k=1
H[a, w] =
1
w
∑nk=1 akk
n
G[a, w] = ∏ (ak )wk
(media armonica)
(media geometrica)
k=1
Q[a, w] =
¿
Án
Á
À ∑ a k wk
(media quadratica)
k=1
Dimostrare che valgono le disuguaglianze H ≤ G ≤ A ≤ Q.
20. Ridurre una frazione ai minimi termini; ad esempio: 18/24 → 3/4.
Suggerimento: rappresentare la frazione n/d mediante un array [n, d].
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
46
CAPITOLO 3. ARRAY
21. Scomporre una frazione in una frazione mista; ad esempio: 17/3 → 5+2/3.
22. Date due frazioni, determinare la frazione somma, riducendo il risultato
ai minimi termini.
23. Dato un numero decimale (anche periodico), determinare la sua frazione
generatrice.
24. Data una frazione, determinare se essa rappresenta un numero decimale finito, un numero decimale periodico semplice oppure un numero
decimale periodico misto.
25. Un array a contiene i distacchi parziali dei vari concorrenti in una gara,
ossia a[i] rappresenta il distacco del concorrente i-esimo dal concorrente
(i − 1)-esimo (a[1] = 0 per il primo concorrente). Determinare l’array
contenente i distacchi complessivi dal primo concorrente.
26. Rovesciare un array in modo che il primo elemento venga scambiato con
l’ultimo, il secondo con il penultimo e così via.
27. Definire, in modalità funzionale ed in modalità procedurale, l’interfaccia
di un blocco equivalente all’assegnazione
s[k] ← c
essendo s una stringa, k un numero naturale, s[k] il k-esimo carattere
della stringa s e c un generico carattere. È consentita l’assegnazione fra
stringhe.
28. Dire qual è l’effetto della seguente porzione di algoritmo agente su una
matrice quadrata a di dimensione n:
1:
2:
3:
4:
5:
for i from 1 to n do
for j from 1 to n do
aij ← aji
end for
end for
29. Completare la seguente porzione di algoritmo, mettendo delle opportune
espressioni naturali (in funzione di i) al posto degli indici h e k, in modo
da elaborare tutti gli elementi di una matrice a di dimensioni m × n:
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
47
3.7. ESERCIZI
for i from 1 to m ∗ n do
2:
elabora l’elemento ahk
3: end for
1:
30. Costruire delle matrici rettangolari secondo i seguenti schemi:
0
0
0
0
0
0
1
1
1
0
0
1
1
1
0
0
1
1
1
0
0
1
1
1
0
0
0
0
0
0
0
1
0
1
0
1
0
1
0
1
0
1
0
1
0
1
0
1
0
1
0
1
0
1
0
1
0
1
0
1
31. Costruire una matrice quadrata di ordine n con gli n2 numeri naturali
1, 2, 3, . . . , n2 , secondo gli schemi sotto riportati:
1
3
4
10
11
2
5
9
12
19
6
8
13
18
20
7
14
17
21
24
15
16
22
23
25
21
20
19
18
17
22
7
6
5
16
23
8
1
4
15
24
9
2
3
14
25
10
11
12
13
32. Costruire delle matrici di numeri secondo i seguenti formati, riferiti a
matrici di dimensioni m = 5, n = 6:
11
21
31
41
51
12
22
32
42
52
13
23
33
43
53
14
24
34
44
54
15
25
35
45
55
16
26
36
46
56
1
7
13
19
25
2
8
14
20
26
3
9
15
21
27
4
10
16
22
28
5
11
17
23
29
6
12
18
24
30
33. Costruire una matrice di numeri come illustrato dallo schema che segue,
riferito ad una matrice di dimensioni m = 5, n = 14:
0
1
2
3
4
1
2
3
4
5
2
3
4
5
6
3
4
5
6
7
4
5
6
7
8
5
6
7
8
9
6
7
8
9
0
7
8
9
0
1
8
9
0
1
2
9
0
1
2
3
0
1
2
3
4
1
2
3
4
5
2
3
4
5
6
3
4
5
6
7
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
48
CAPITOLO 3. ARRAY
34. Costruire una matrice 5 × 10 con numeri casuali secondo le regole delle
estrazioni del lotto.
35. Data una matrice quadrata, scrivere delle porzioni di algoritmo per riempire la matrice con i valori 0 e 1, mettendo il valore 1 (e 0 altrove) secondo
i seguenti schemi:
(a) sulla diagonale principale
(b) sulla diagonale secondaria
(c) su entrambe le diagonali
(d) sui bordi della matrice
(e) sul triangolo superiore destro
(f) sul triangolo superiore sinistro
(g) sul triangolo inferiore destro
(h) sul triangolo inferiore sinistro
(i) sulle righe di indice pari
(j) sulle colonne di indice pari
36. Costruire una matrice quadrata n × n con i numeri naturali costituenti
la tavola pitagorica della moltiplicazione.
37. Costruire una matrice quadrata n × n che costituisca la tabella di addizione modulo n.
38. Costruire una matrice quadrata di ordine n dispari con i numeri con
i numeri naturali 1, 2, ..., n2 posizionando il numero 1 al centro e poi
proseguendo a spirale in senso antiorario.
39. Costruire una matrice quadrata di numeri naturali con i numeri 1, 2, ..., n2
in modo da formare un quadrato magico.
40. Realizzare le seguenti operazioni matematiche sulle matrici: addizione, moltiplicazione, opposta, inversa, trasposta, determinante, discutendo nei vari casi quali devono essere le dimensioni delle matrici affinché
l’operazione sia possibile.
41. Data una tabella bidimensionale 24 × 365 contenente le temperature di
ogni ora dei giorni di un dato anno, determinare il giorno dell’anno avente
la temperatura media più bassa; determinare il giorno avente la massima
escursione termica.
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
49
3.7. ESERCIZI
42. Sia m una matrice numerica di dimensioni 12 × 31 in cui nel generico
elemento m[i][j] è registrato il fatturato di una azienda relativo al mese
i-esimo ed al giorno j-esimo (ad esempio, m[3][27] contiene il fatturato
relativo al giorno 27 marzo). Determinare il mese in cui si è avuto il
maggior fatturato complessivo.
43. Determinare l’indice di riga di una matrice per il quale la somma degli
elementi è massima. Questo problema costituisce una generalizazzione
del problema precedente.
44. Su un quotidiano economico si trova la seguente tabella, dove la prima colonna indica il nome della provincia, la seconda il miglioramento/declassamento in classifica rispetto all’anno precedente. L’indice di
posizione denota la posizione di classifica attuale.
città
Belluno
Aosta
Bolzano
posizione
+1
+1
−2
...
Figura 3.1: Tabella della classifica.
Definire un tipo di dato per rappresentare la tabella. Decidere se una
tabella è compatibile. Nell’ipotesi di tabella compatibile, ricostruire
in-loco la posizione di classifica dell’anno precedente.
45. Determinare in una matrice bidimensionale binaria il rettangolo di maggior superficie composto da tutti 1.
46. Determinare il valore minimo ed il valore massimo fra quelli presenti in
una matrice numerica.
47. Determinare il minimo ed il massimo e la loro frequenza, in una matrice
numerica.
48. Decidere se all’interno di una matrice è presente un dato elemento.
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
50
CAPITOLO 3. ARRAY
49. Determinare quante volte un dato elemento è presente all’interno di una
matrice.
50. Decidere se una matrice è composta da elementi tutti uguali.
51. Determinare l’indice di riga di una matrice numerica, la cui somma degli
elementi è massima.
52. Decidere se in una data matrice esistono due righe uguali.
53. Determinare il prodotto degli elementi di una matrice di numeri. Ottimizzare il procedimento, terminando il calcolo quando si incontra un
elemento di valore zero.
54. Contare il numero di numeri primi presenti in una matrice.
55. Costruire una matrice quadrata ad anelli concentrici, mettendo 1 sull’anello esterno, 2 nel secondo anello e così via.
56. Ricercare se e dove è presente un dato valore in una matrice.
57. Determinare l’elemento che compare con maggiore frequenza in una
matrice.
58. Determinare la frequenza dell’elemento che compare con maggiore frequenza in una matrice.
59. Una matrice è ordinata per righe se ogni riga, indipendentemente dalle
altre, è ordinata. Decidere se una data matrice è ordinata per righe.
Ordinare per righe una matrice numerica.
60. Una matrice rettangolare contiene numeri naturali estratti da un’urna; il
valore 0 rappresenta la non presenza di un valore non estratto. Decidere
se due dati numeri formano un ambo, ossia se sono entrambi presenti in
una stessa riga della matrice.
61. Ricercare se in una matrice di caratteri è presente (orizzontalmente o
verticalmente) una data parola.
62. Decidere se una data matrice quadrata costituisce un quadrato magico.
63. Decidere se una data matrice numerica 9 × 9 è corretta in base alle regole
del gioco del sudoku.
64. Contare il numero di elementi distinti presenti in una matrice.
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
3.7. ESERCIZI
51
65. Ruotare una matrice quadrata di 90o a destra rispetto al suo centro.
66. Data una matrice di caratteri composta dai soli caratteri ’ ’ (spazio) e
’*’ (muro), decidere se esiste un percorso che attraversa la matrice da
una casella del bordo ad un’altra casella del bordo (eventualmente anche
sullo stesso lato).
67. Data una matrice di caratteri ’ ’ (blanc)’ e ’*’, determinare il numero di
isole presenti, interpretando il carattere ’*’ come terra ed il carattere ’ ’
come acqua.
68. Realizzare un risolutore del gioco del sudoku.
69. Data una matrice 3 × N contenenti le dimensioni (larghezza,altezza,
profondità di N scatole a forma di parallelepipedo, determinare le dimensioni della più piccola scatola in grado di contenerle tutte (una alla
volta).
70. Risolvere con un metodo a piacere un sistema lineare della forma A x = b,
essendo A la matrice quadrata dei coefficienti e b il vettore dei termini
noti.
71. In una matrice a un elemento aij dicesi punto di sella se esso è maggiore
di tutti gli elementi dell’i-esima riga e minore di tutti gli elementi della
j-esima colonna, oppure, simmetricamente, minore di tutti gli elementi
dell’i-esima riga e maggiore di tutti gli elementi della j-esima colonna.
Determinare i punti di sella di una matrice numerica.
72. In un’aula rettangolare si devono predisporre in file allineate delle postazioni per svolgere un esame. Per evitare possibili copiature vengono predisposte 4 prove distinte, individuate mediante i numeri 1, 2, 3, 4. Per
rappresentare la disposizione dell’aula ed il numero della prova corrispondente a ciascuna postazione viene utilizzata una matrice. Riempire
una matrice rettangolare con i numeri 1, 2, 3, 4 in modo che caselle contigue non contengano valori uguali; preferibilmente fare in modo tale che,
se due elementi sono uguali, le due corrispondenti caselle abbiano una
distanza, secondo la metrica Manhattan, la maggiore possibile.
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
52
CAPITOLO 3. ARRAY
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
Capitolo 4
Complessità Computazionale
Come la teoria della ricorsività, tracciando una linea di separazione tra ciò che una macchina può
fare e ciò che non può fare, costituisce un primo livello di approssimazione allo studio delle proprietà
del calcolo automatico, così la teoria della complessità costituisce un secondo livello di approssimazione poiché consente di caratterizzare ciò che una
macchina può fare in una determinata quantità di
tempo e di memoria.
G.Ausiello, Complessità di calcolo delle funzioni
Il tempo di esecuzione di un dato programma dipende da diversi fattori:
velocità di elaborazione del calcolatore, quantità dei dati da elaborare, grandezza della memoria a disposizione, linguaggio di programmazione utilizzato.
La complessità computazionale si pone l’obiettivo di analizzare in generale la
qualità degli algoritmi, a prescindere da tutti i fattori tecnici sopra elencati che
risultano marginali; in particolare lo studio è finalizzato a ripartire gli algoritmi
in algoritmi efficienti ed algoritmi non efficienti. Questa classificazione degli
algoritmi induce un’equivalente ripartizione dei problemi: problemi trattabili,
ossia risolvibili in tempi ragionevoli e problemi intrattabili, ossia problemi che,
qualsiasi algoritmo si adotti, superano la potenza di calcolo di qualsiasi calcolatore esistente e futuro. La vaghezza di questa linea di ripartizione è solo
apparente in quanto esiste, in realtà, un abisso di separazione fra le due classi.
53
54
4.1
CAPITOLO 4. COMPLESSITÀ COMPUTAZIONALE
LA COMPLESSITÀ COMPUTAZIONALE
I criteri in base ai quali stabilire la qualità di un algoritmo possono basarsi
sulle caratteristiche intrinseche dell’algoritmo (lunghezza, leggibilità) oppure
sulle risorse richieste dall’algoritmo (tempo di CPU impiegato, memoria RAM
occupata).
La complessità computazionale studia gli algoritmi in relazione alla quantità di risorse impiegate. Il parametro più caratterizzante e significativo nella
valutazione della complessità di un algoritmo è costituito dal tempo di elaborazione impiegato per l’esecuzione dell’algoritmo. Il parametro tempo risulta
ancora poco espressivo in quanto fa riferimento ad una particolare esecuzione
di una istanza del problema su una data macchina. È chiaramente poco significativa, infatti, una frase del tipo: L’algoritmo di ordinamento A impiega 6.2
secondi ad essere eseguito in quanto non è precisato né il numero di elementi
da ordinare, né la loro disposizione iniziale, né la macchina sulla quale viene
eseguito l’algoritmo. Inoltre, un tale approccio al problema richiederebbe
sempre delle prove per poter confrontare due algoritmi. Quando il tempo di
esecuzione è elevato (un’ora o addirittura un secolo!) tale approccio pragmatico non è più chiaramente attuabile. Ed è proprio in questi casi estremi che
è utile sapere (senza provare!) la complessità di un algoritmo per stimare il
tempo di esecuzione, senza eseguire l’algoritmo. Ad esempio, se si dispone di
un algoritmo per il calcolo del determinante di una matrice si vorrebbe sapere in anticipo quant’è approssimativamente il tempo che sarà necessario per
eseguire l’algoritmo.
Dalle osservazioni precedenti si deduce che il problema della valutazione
della complessità degli algoritmi non ammette una soluzione di tipo pragmatico della forma ”prova e vedi” ma deve essere affrontato sulla carta. È
necessario pertanto astrarre le considerazioni senza fare riferimento ad alcuna
macchina particolare. Questo passaggio è facilmente attuabile se si assume
l’ipotesi (plausibile) che il tempo di esecuzione di un imprecisato numero k di
operazioni elementari è proporzionale a k e che il numero k sia una funzione f crescente della misura n dei dati da elaborare. Pertanto, tale funzione
f (n) può essere assunta come misura del tempo impiegato, per le varie istanze
del problema aventi valori diversi di n. Per quanto riguarda la misura della
complessità dell’input su cui deve lavorare l’algoritmo è sufficiente definire un
valore che esprime la dimensione del problema. Per quanto riguarda i valori
particolari che può assumere un input di data lunghezza, si considerano le
seguenti situazioni: caso ottimo, caso medio, caso pessimo.
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
4.2. OPERAZIONE DOMINANTE
4.2
55
OPERAZIONE DOMINANTE
La complessità computazionale di un algoritmo rappresenta una stima del
tempo di elaborazione impiegato per l’esecuzione dell’algoritmo; tale stima del
tempo viene indirettamente ottenuta contanto il numero di passi elementari
svolti nell’esecuzione dell’algoritmo. Questa valutazione indiretta permette
di svincolare la misura del tempo impiegato senza riferirsi ad una particolare
macchina. Questa trasposizione dalla misura del tempo al conteggio dei passi
elementari non costituisce comunque un concetto assoluto in quanto dipende
da cosa si intende per passo elementare; ed inoltre dipende anche dal modo
con cui si rappresentano i dati sui quali opera l’algoritmo. Il concetto di
passo elementare risulta perfettamente definito qualora si consideri l’esecutore
costituito da una macchina di Turing: in questo caso il passo elementare è rappresentato da una transizione di stato dell’automa ossia dall’esecuzione di una
quintupla. Poiché gli algoritmi vengono descritti con formalismi diversi dalle
quintuple di una macchina di Turing, risulta necessario estendere il concetto
di passo elementare ad altri formalismi. Con algoritmi ad un più alto livello si
incontrano dei passi elementari di diversa natura e computazionalmente non
uniformabili; ci possono essere delle assegnazioni, delle addizioni, delle moltiplicazioni, delle valutazioni di condizioni. Per semplificare il calcolo della
complessità è stato introdotto il concetto di operazione dominante.
DEFINIZIONE 6. Si chiama operazione dominante o caratteristica di
un algoritmo l’operazione che viene eseguita con maggiore frequenza; nel caso
di operazioni eseguite con la stessa frequenza si considera quella più onerosa
in termini di tempo impiegato per la sua esecuzione.
Esempio 32. Nella tabella che segue sono riportati alcuni problemi e la corrispondente operazione dominante.
Problema
calcolo del fattoriale
scomposizione di un numero
ricerca in un array
ordinamento di un array
moltiplicazione fra due matrici
torre di Hanoi
commesso viaggiatore
Operazione dominante
moltiplicazione fra due numeri
calcolo del resto della divisione
confronto fra due elementi
confronto fra due elementi
moltiplicazione fra due numeri
spostamento di un disco
calcolo della lunghezza di un percorso
Tabella 4.1: Problemi e corrispondenti operazioni dominanti.
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
56
CAPITOLO 4. COMPLESSITÀ COMPUTAZIONALE
4.3
DIMENSIONE DI UN PROBLEMA
Un problema, o più propriamente una classe di problemi è costituita da molte istanze di problemi il cui tempo di risoluzione dipende dalla grandezza
dell’input, come precisato nella definizione che segue.
DEFINIZIONE 7. La dimensione di un problema è un numero naturale
che esprime un’opportuna (rispetto all’operazione dominante) misura dei dati
di input del problema.
Nonostante l’apparente vaghezza del termine opportuna che compare nella
definizione precedente, la dimensione di un problema è facilmente identificabile, come emerge dall’esempio che segue.
Esempio 33. La seguente tabella descrive la dimensione di alcuni importanti
problemi.
Problema
calcolo del fattoriale
scomposizione di un numero
ricerca in un array
ordinamento di un array
moltiplicazione fra due matrici
torre di Hanoi
commesso viaggiatore
Dimensione del problema
numero di cui si calcola il fattoriale
numero di cifre del numero
numero di elementi dell’array
numero di elementi dell’array
dimensione delle matrici
numero di dischi da spostare
numero di città da visitare
Tabella 4.2: Problemi e corrispondenti dimensioni.
4.4
COMPLESSITÀ DI UN ALGORITMO
In funzione dell’operazione dominante e della dimensione di un problema è
possibile definire una misura del tempo impiegato da un algoritmo. Il concetto
è precisato dalla seguente definizione.
DEFINIZIONE 8. La complessità di un algoritmo è una funzione che
esprime il numero di operazioni dominanti in funzione della dimensione del
problema.
Esempio 34. Il problema della moltiplicazione fra due matrici quadrate di
lato n ha dimensione n. L’operazione dominante è la moltiplicazione. Il
tradizionale algoritmo di moltiplicazione righe-per-colonne ha complessità n3
in quanto richiede l’esecuzione di n moltiplicazioni per il calcolo di ciascuno
degli n2 elementi della matrice risultante.
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
4.5. COMPLESSITÀ DI UN PROBLEMA
4.5
57
COMPLESSITÀ DI UN PROBLEMA
In funzione del concetto di complessità di un algoritmo è possibile stabilire la
complessità di un problema, come precisato nella seguente definizione.
DEFINIZIONE 9. La complessità di un problema è la complessità del
miglior algoritmo che risolve il dato problema.
Esempio 35. Il problema dell’ordinamento di un array di dimensione n ha
complessità, nel caso medio, pari a n log n, in quanto esistono algoritmi di
ordinamento che hanno una tale complessità; d’altra parte si può dimostrare
che, in generale, non si può ordinare un array con meno di n log n confronti.
4.6
COMPLESSITÀ ASINTOTICA
La complessità di un algoritmo e di un problema risulta importante e decisiva per valori grandi della dimensione del problema. Si danno le seguenti
definizioni.
DEFINIZIONE 10. Date due funzioni f e g, reali a valori reali, si dice che
la funzione f è O grande di g, e si scrive f è O(g), se esiste una costante reale
c > 0 ed un numero reale x0 tale che per x ≥ x0 si abbia f (x) ≤ c g(x).
DEFINIZIONE 11. La complessità asintotica di un algoritmo o di un
problema è la complessità per valori grandi della dimensione del problema;
viene denotata con la notazione O (o grande).
Esempio 36. L’affermazione: Il problema della Torre di Hanoi ha una complessità computazionale asintotica pari a O(2n ). significa che per spostare n dischi
da un piolo ad un altro, per n grande, si esegue un numero di spostamenti
proporzionale a 2n .
Esempio 37. Il problema della moltiplicazione di due matrici quadrate di ordine n ha una complessità non nota. Il tradizionale algoritmo di moltiplicazione
righe-per-colonne ha una complessità asintotica pari a n3 ; il migliore algoritmo
attualmente conosciuto ha una complessità asintotica pari a n2,78 ma attualmente non si conosce una limitazione inferiore per la complessità asintotica
degli algoritmi di moltiplicazione di matrici.
Esempio 38. La seguente tabella riporta una lista di problemi e le loro corrispondenti complessità asintotica nel caso medio.
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
58
CAPITOLO 4. COMPLESSITÀ COMPUTAZIONALE
Problema
ricerca in un array ordinato
ricerca in un array non ordinato
ordinamento di un array
moltiplicazione fra due matrici
commesso viaggiatore
4.7
Complessità asintotica del problema
O(log n)
O(n)
O(n log n)
non è nota
O(n!)
ESEMPI VARI
Esempio 39. Consideriamo il problema di decidere se un array è composto
da elementi tutti distinti. Una soluzione di questo problema può basarsi
sulla seguente idea: Confrontare ciascun elemento con tutti gli elementi che
lo precedono. L’algoritmo basato su questa idea è il seguente:
Algoritmo 33 - Decisione se un array è composto da elementi distinti
Input: array a
Output: T RU E se e solo se (a[i] ≠ a[j] per ogni i, j (i ≠ j))
1: distinti ← T RU E
2: i ← 2
3: while distinti AN D i ≤ size a do
4:
ricerca se a[i] è presente nella porzione di array a[1...i − 1]
5:
if è presente then
6:
distinti ← F ALSE
7:
else
8:
i++
9:
end if
10: end while
11: return distinti
Il problema di ricerca della linea 4 può essere risolto efficientemente adottando una ricerca con sentinella sulla porzione di array a[1..i].
Si lascia per esercizio il calcolo della complessità.
Esempio 40. Analizziamo la complessità del seguente schema di algoritmo,
dove n denota la dimensione del problema ed op l’operazione dominante.
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
59
4.7. ESEMPI VARI
1:
2:
3:
4:
5:
6:
7:
8:
for i from 1 to n do
for j from i + 1 to n do
op
end for
end for
for i from 1 to 2 ∗ n do
op
end for
La complessità consiste nel numero di operazioni op che vengono eseguite
dall’algoritmo ed è pari a
((n − 1) + (n − 2) + ⋯ + 1) + 2n = n(n − 1)/2 + 2n = (n2 + 3n)/2
Di conseguenza la complessità asintotica è pari a
O(n2 )
Esempio 41. Consideriamo il problema di determinare l’ampiezza del più piccolo intervallo contenente tutti i numeri di un array. Per risolvere il problema
è sufficiente determinare la differenza fra il valore massimo ed il valore minimo;
a seguire è riportato l’algoritmo basato su questa semplice idea.
Algoritmo 34 - Calcolo dell’ ”ampiezza” di un array
Input: array a
Output: ampiezza dell’intervallo dei valori di a
1: min ← a[1]
2: max ← a[1]
3: for i from 2 to size a do
4:
if a[i] < min then
5:
min ← a[i]
6:
else if a[i] > max then
7:
max ← a[i]
8:
end if
9: end for
10: return max − min
In questo caso n costituisce la dimensione del problema ed il confronto
costituisce l’operazione dominante. Nel caso pessimo la complessità vale 2(n−
1) e quindi la complessità asintotica vale O(n).
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
60
CAPITOLO 4. COMPLESSITÀ COMPUTAZIONALE
Esempio 42. Scriviamo un algoritmo per determinare la frequenza degli elementi di un array aventi la massima frequenza.
Algoritmo 35 - Calcolo della frequenza massima
Input: array a di dimensione n
Output: massima frequenza degli elementi di a
1: f max ← 0
2: for i from 1 to n do
3:
f ← frequenza di a[i]
4:
if f > f max then
5:
f max ← f
6:
end if
7: end for
8: return f max
La complessità della funzione frequenza è n; essendo che questa funzione
viene richiamata n volte all’interno del ciclo for, si conclude che l’algoritmo
ha complessità n2 . Si lascia per esercizio la valutazione della complessità del
problema.
4.8
PROBLEMI INTRATTABILI
Il più potente calcolatore che si possa immaginare di costruire non potrà essere
più grande dell’universo, non potrà essere fatto di componenti di elaborazione più piccoli dei protoni, non potrà trasmettere le informazoni fra le sue
componenti ad una velocità superiore alla velocità della luce. Un semplice
calcolo porta alla conclusione che un tale ipotetico calcolatore non potrebbe
essere composto da più di 10126 componenti. Anche ammettendo che le informazioni di questo calcolatore venissero trasmesse fra le sue componenti ad
una velocità uguale a quella della luce, esistono numerosi problemi di grande
interesse pratico che, anche per moderati valori della dimensione del problema, tengono in scacco questo calcolatore estremo per 20 miliardi di anni, ossia
per un tempo paragonabile a quello dell’universo. Esistono problemi che, pur
risolvibili sul piano teorico in quanto si conoscono degli algoritmi risolutivi,
non sono effettivamente risolvibili in quanto richiederebbero tempi esagerati
anche per il più potente calcolatore immaginabile. Questi problemi vengono
detti problemi intrattabili.
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
61
4.8. PROBLEMI INTRATTABILI
L’esistenza di problemi intrattabili può sembrare, a prima vista, un aspetto
negativo, un’evidenziazione di un limite della scienza e della tecnica. Sembra
paradossale, ma, invece, questi problemi costituiscono l’elemento fondante di
alcune tecnologie: l’intrattabilità del problema della fattorizzazione in primi
di un numero naturale è alla base di tutti i sistemi basati sulla crittografia;
l’esplosione dell’albero delle mosse del gioco degli scacchi comporta l’intrattabilità del problema di determinare una strategia vincente nella conduzione del
gioco in una partita a scacchi a causa dell’intrattabilità del problema di un’analisi esaustiva dell’albero delle mosse e questo, nonostante l’eccezionale velocità
e complessità dei calcolatori attuali, lascia margini di battaglia alla pari all’uomo che gioca a scacchi contro il computer, almeno ai grandi maestri degli
scacchi; ed anche se un computer batte agevolmente anche un buon scacchista,
le mosse brillanti, ingegnose, artistiche rimangono prerogativa dell’uomo.
La linea di demarcazione fra problemi trattabili e problemi intrattabili
dipende dalla tipologia della funzione di complessità: i problemi trattabili
sono caratterizzati da funzioni di complessità polinomiali, mentre i problemi
intrattabili hanno funzioni di complessità di categoria esponenziale. La figura 4.1 dispone in linea di complessità crescenti un insieme di funzioni che si
incontrano spesso nell’analisi degli algoritmi.
complessità polinomiali
log n
√
n
n
n log n
n2
n3
complessità esponenziali
n10
2n
n! nn
Figura 4.1: Complessità polinomiali ed esponenziali di alcune funzioni che si
incontrano frequentemente nell’analisi degli algoritmi.
Nella tabella 4.3 è riportato il tempo in secondi che impiega un calcolatore
che esegue 100 milioni di operazioni al secondo a risolvere un problema di
dimensione n.
Nella tabella 4.4 è riportata la dimensione del problema risolvibile in una
data quantità di tempo, sempre nell’ipotesi di disporre di un calcolatore che
esegue 100 milioni di operazioni al secondo.
Un’analisi, anche superficiale, delle due tabelle 4.3 e 4.4 evidenzia che
l’evoluzione tecnologica dei calcolatori non potrà intaccare il carattere di intrattabilità dei problemi.
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
62
f (n)
log n
√
n
n
n log n
n2
n3
2n
n!
nn
CAPITOLO 4. COMPLESSITÀ COMPUTAZIONALE
n = 10
3.3 µs
3.1 µs
10 µs
33 µs
100 µs
1 ms
17 min
42 g
317 a
n = 20
4.3 µs
4.5 µs
20 µs
86 µs
400 µs
8 ms
12 g
8 ⋅ 1010 a
3 ⋅ 1018 a
n = 30
4.9 µs
5.5 µs
30 µs
147 µs
900 µs
27 ms
34 a
8 ⋅ 1024 a
6 ⋅ 1036 a
n = 40
5.3 µs
6.3 µs
40 µs
213 µs
1600 µs
64 ms
34865 a
3 ⋅ 1040 a
4 ⋅ 1056 a
50
5.6 µs
7.0 µs
50 µs
282 µs
2500 µs
125 ms
3.5 ⋅ 107 a
9 ⋅ 1056 a
4 ⋅ 1077 a
n = 100
6.6 µs
10 µs
100 µs
664 µs
10000 µs
1 sec
4 ⋅ 1022 a
3 ⋅ 10150 a
3 ⋅ 10192 a
Tabella 4.3: Tempi delle funzioni di complessità di alcune funzioni. Le unità
di misura dei tempi sono: µs = microsecondi, ms = millisecondi, sec = secondi,
min = minuti, g = giorni, a = anni.
f (n)
1 sec
1 min
log n
√
n
n
n log n
n2
n3
2n
n!
nn
10400000
1012
106
9 ⋅ 104
103
100
20
10
7
1010
4 ⋅ 1015
6 ⋅ 107
106
8 ⋅ 103
392
26
11
8
7
1 ora
9
1010
1019
4 ⋅ 109
2 ⋅ 108
6 ⋅ 104
1533
32
13
9
1 giorno
10
1010
7 ⋅ 1021
9 ⋅ 1010
4 ⋅ 109
3 ⋅ 105
4414
36
14
10
1 mese
12
1010
7 ⋅ 1024
3 ⋅ 1012
1011
2 ⋅ 106
13715
41
15
11
1 anno
13
1010
1027
3 ⋅ 1013
1012
6 ⋅ 106
31551
45
16
12
1 secolo
15
1010
1031
3 ⋅ 1015
1014
6 ⋅ 107
146452
51
18
13
Tabella 4.4: Dimensioni dei problemi risolvibili in una data quantità di tempo
in funzione della complessità f (n) dei problemi.
Osservazione. Da tutte le precedenti considerazioni risulta coerente designare come polinomiali i problemi intrattabili e come esponenziali i problemi
intrattabili. La macchina di Turing costituisce lo strumento per tracciare
la linea di demarcazione fra problemi risolvibili e problemi irrisolvibili. La
distinzione fra problemi trattabili e problemi intrattabili viene invece stabilita
in base alla funzione di complessità del problema. La figura 4.2 descrive la
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
63
4.9. ESERCIZI
classificazione dei problemi, evidenziando, all’interno dei problemi risolvibili,
i problemi trattabili e quelli intrattabili. Ci sono dei problemi per i quali non
è attualmente noto a quale di queste due categorie appartengono.
'
problemi
risolvibili
problemi
intrattabili
problemi
trattabili
&
$
problemi
non risolvibili
%
Figura 4.2: Problemi non risolvibili, risolvibili, trattabili ed intrattabili.
4.9
ESERCIZI
1. Ordinare le seguenti funzioni di complessità:
√
n log n10 n! n log n nn 10n 10n log n
n10
10100
log2 n
2. Siano A1 ed A2 due algoritmi funzionalmente√equivalenti aventi rispettivamente complessità c1 (n) = n log n e c2 = n 2 . Stabilire quale dei due
algoritmi è preferibile, per n sufficientemente grande.
3. Determinare per quale valore n0 (punto di taglio) della dimensione n
del problema un algoritmo A1 di complessità polinomiale f (n) = n10
risulta preferibile rispetto ad un equivalente algoritmo A2 di complessità
esponenziale g(n) = (1.1)n . Fare delle considerazioni in merito alla
conclusione.
4. Descrivere mediante dei diagrammi di Venn le seguenti classi di problemi: problemi risolvibili, problemi non risolvibili, problemi polinomiali,
problemi esponenziali.
5. Valutare la complessità e la complessità asintotica delle seguenti porzioni
di algoritmi, essendo op l’operazione dominante ed n la dimensione del
problema.
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
64
1:
2:
3:
4:
5:
CAPITOLO 4. COMPLESSITÀ COMPUTAZIONALE
for i from 1 to n2 do
for j from 1 to 2 n do
op
end for
end for
for i from 1 to n do
for j from i to n do
3:
op
4:
end for
5: end for
1:
2:
1:
2:
3:
4:
5:
6:
7:
1:
2:
3:
4:
5:
6:
7:
k←n
while k > 0 do
for i from 1 to n do
op
end for
k ← k/2
end while
k ← n2
while k > 0 do
for i from 1 to 2 n do
op
end for
k ← k/2
end while
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
4.9. ESERCIZI
65
6. Scrivere e valutare la complessità degli algoritmi che risolvono i seguenti
problemi:
(a) calcolo del fattoriale di un numero naturale
(b) ricerca di un elemento in un array
(c) moltiplicazione fra due matrici
(d) problema della torre di Hanoi
7. Si consideri il problema P : Decidere se un array contiene elementi tutti
uguali fra loro. Spiegare perchè il problema P ′ : Decidere se un array
contiene elementi tutti diversi fra loro. è sostanzialmente diverso dal
problema P .
8. Dimostrare che il problema Decidere se due array hanno elementi in
comune ha una complessità asintotica nel caso pessimo non superiore a
O(n log n).
Nota. Per ciascuno dei seguenti problemi svolgere i seguenti punti: descrivere un algoritmo risolutivo; stabilire in cosa consistono i casi ottimo, medio, pessimo; valutare la complessità e la complessità asintotica
dell’algoritmo nel caso pessimo; stabilire la complessità del problema.
9. Determinare quante volte è presente un dato elemento in un array.
10. Determinare quante volte è presente un dato elemento in un array ordinato.
11. Determinare il più piccolo numero non appartenente ad un dato array
di numeri naturali.
12. Decidere se un array di numeri interi contiene tutti i valori compresi fra
il minimo ed il massimo degli elementi contenuti nell’array.
13. Decidere se un dato array di numeri naturali è ordinato crescentemente
ed è composto da valori contigui; ad esempio l’array di numeri naturali
a = [5, 6, 7, 8, 9] è ordinato crescentemente ed ha gli elementi contigui,
mentre non lo sono gli array b = [5, 7, 8, 6, 9], c = [4, 6, 7, 8, 9].
14. Decidere se due array (di uguali dimensioni) sono una permutazione uno
dell’altro, ossia se contengono gli stessi valori, con la stessa frequenza,
indipendentemente dalla posizione.
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
66
CAPITOLO 4. COMPLESSITÀ COMPUTAZIONALE
15. Decidere se un array contiene elementi tutti uguali fra loro.
16. Decidere se un dato array è composto da elementi tutti distinti.
17. Determinare il numero di elementi distinti presenti in un array.
18. Determinare la massima distanza fra tutte le possibili coppie di numeri
di un array.
19. Determinare la coppia di elementi di un array aventi la massima distanza
reciproca.
20. Determinare la distanza della coppia di punti più distanti presenti in un
array di punti.
21. Determinare il valore dell’elemento mediano in un array numerico di
dimensione dispari. L’elemento mediano è il valore che in un ipotetico
ordinamento dell’array verrebbe a trovarsi nella posizione mediana.
22. Decidere se due date stringhe soddisfano alla relazione d’ordine lessicografico sulle stringhe, ossia decidere se la prima precede la seconda,
secondo l’usuale ordine del vocabolario.
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
Capitolo 5
Ricerca
5.1
ALGORITMI DI RICERCA
La ricerca di informazioni costituisce la forma più frequente di elaborazione
in diversi ambiti; di più, questa forma di elaborazione costituisce elemento
strategico per diverse attività: si pensi, ad esempio, ai motori di ricerca nel
web ed alla ricerca nelle basi di dati.
Ad un livello molto generale i problemi di ricerca si inquadrano nelle
seguenti due forme:
R1 : Ricercare in un insieme A se esistono elementi soddisfacenti ad una data
proprietà P .
R2 : Ricercare in un insieme A tutti gli elementi soddisfacenti ad una data
proprietà P .
La proprietà P alla quale devono soddisfare gli elementi cercati spesso coincide
con l’essere uguale ad un elemento fissato.
Nonostante l’apparente somiglianza, queste due forme di ricerca vengono
risolte con strategie sostanzialmente diverse in quanto nel primo caso la ricerca termina non appena viene trovato un elemento soddisfacente, mentre nel
secondo caso bisogna comunque analizzare tutti gli elementi dello spazio di
ricerca. La forma di ricerca R1 è quella più interessante e quella che viene
usata più frequentemente. Per questa forma di ricerca, lo schema generale è
descritto nell’algoritmo 36.
67
68
CAPITOLO 5. RICERCA
Algoritmo 36 - Ricerca di un elemento
Input: insieme A in cui cercare, proprietà P da soddisfare
Output: T RU E se e solo se esiste un elemento x ∈ A tale che P (x)
1: while ¬ (trovato alcun elemento) ∧ (lo si può ancora trovare) do
2:
ricerca l’elemento
3: end while
4: return esito della ricerca
Questo è un algoritmo così generale che può essere considerato uno schema
di algoritmo che può essere adattato in tanti modi, a seconda dello specifico
problema di ricerca che si sta considerando, a seconda della struttura dell’insieme A ed a seconda della particolare strategia di ricerca attuata; in particolare
sono frequentemente adottate le seguenti due strategie:
a) passare in rassegna sequenzialmente tutti gli elementi di A
b) restringere ripetutamente l’insieme A ad un suo sottoinsieme
La prima strategia di ricerca (a)) si traduce nell’algoritmo 37. Nel caso
in cui l’insieme A sia enumerabile, l’algoritmo 37 assume la forma riportata
nell’algoritmo 38. Questo algoritmo di ricerca viene utilizzato nel caso di
array, matrici, catene ed altre strutture dati che ammettono un meccanismo
di enumerazione degli elementi. Nel caso in cui l’insieme A soddisfi a dei
requisiti più forti (ad esempio, sia ordinato) si possono usare degli algoritmi
di ricerca più specifici e più efficienti.
Algoritmo 37 - Ricerca di un elemento (caso a)
Input: insieme A in cui cercare, proprietà P da soddisfare
Output: T RU E se e solo se esiste un elemento x ∈ A tale che P (x)
1: trovato ← F ALSE
2: trovabile ← ∣A∣ > 0
3: while (¬ trovato) ∧ trovabile do
4:
considera un elemento x ∈ A non ancora esaminato
5:
if P (x) then
6:
trovato ← T RU E
7:
else if non ci sono più elementi da esaminare then
8:
trovabile ← F ALSE
9:
end if
10: end while
11: return trovato
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
5.1. ALGORITMI DI RICERCA
69
Algoritmo 38 - Ricerca di un elemento in un insieme enumerabile
Input: insieme A in cui cercare, proprietà P da soddisfare
Output: T RU E se e solo se esiste un elemento x ∈ A tale che P (x)
1: trovato ← F ALSE
2: i ← 1
3: while (¬ trovato) ∧ (i ≤ ∣A∣) do
4:
if P (ai ) then
5:
trovato ← T RU E
6:
else
7:
i←i+1
8:
end if
9: end while
10: return trovato
La seconda strategia di ricerca (b)) si traduce nell’algoritmo 39. La restrizione dell’insieme T ad un suo sottoinsieme proprio (istruzione 8 dell’algoritmo 39) può essere realizzata, ad esempio, eliminando l’elemento x dall’insieme
T stesso, oppure delimitando lo spazio di ricerca.
Algoritmo 39 - Algoritmo di ricerca (caso b)
Input: insieme A in cui cercare, proprietà P da soddisfare
Output: T RU E se e solo se esiste un elemento x ∈ A tale che P (x)
1: trovato ← F ALSE
2: T ← A
3: while (¬ trovato) ∧ (T ≠ ∅) do
4:
considera un elemento x ∈ T non ancora esaminato
5:
if P (x) then
6:
trovato ← T RU E
7:
else
8:
restringi T ad un suo sottoinsieme proprio
9:
end if
10: end while
11: return trovato
La forma di ricerca R2 può essere svolta passando in rassegna tutti gli elementi e registrando quelli soddisfacenti, che costituiranno, alla fine, il risultato
della ricerca.
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
70
CAPITOLO 5. RICERCA
5.2
ESERCIZI
1. Adattare l’algoritmo 38 al caso in cui A sia un array.
2. Adattare l’algoritmo 39 al caso in cui A sia un array ordinato e la proprietà P sia costituita da un controllo di uguaglianza con un elemento
prefissato k. Suggerimento: controllare se l’elemento di posizione centrale nell’array è uguale a k; in caso contrario restringere lo spazio di
ricerca a una delle due metà dell’array (algoritmo di ricerca binaria o
ricerca dicotomica).
3. Analizzare la complessità della ricerca su un array, distinguendo il caso
in cui l’array sia ordinato e nel caso in cui non sia ordinato.
4. Spiegare cosa si intende con la frase L’algoritmo di ricerca binaria in
un array ordinato ha complessità asintotica logaritmica. Motivare perchè l’algoritmo di ricerca binaria su un array ordinato ha complessità
asintotica O(log n).
5. Determinare quante volte un dato valore è presente in un array. Valutare
la complessità dell’algoritmo. Spiegare perché in questo caso ha poco
senso parlare di complessità nel caso pessimo, medio, ottimo.
6. Decidere se in un dato array a è presente un dato elemento x nelle
posizioni comprese fra due dati indici naturali p e q (p ≤ q).
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
Capitolo 6
Ordinamento
6.1
ALGORITMI DI ORDINAMENTO
Il problema dell’ordinamento di un array può essere enunciato come segue:
Dato un array a di dimensione n, permutarne gli elementi in
modo tale che
a[1] ≤ a[2] ≤ ⋯ ≤ a[n]
Per ordinare un array si possono adottare diverse strategie. Alcune sono
descritte a seguire.
Ordinamento a bolle (bubble-sort): Far salire successivamente verso la fine
dell’array gli elementi maggiori, attraverso degli scambi fra elementi successivi,
seguendo la seguente strategia: si confrontano i primi due: se sono fuori ordine
vengono scambiati; si ripete il procedimento con il secondo ed il terzo e così
via, fino alla fine dell’array. Alla fine del primo ciclo l’elemento maggiore è
sicuramente posizionato alla fine dell’array. Si inizia allora un altro ciclo per
far salire al penultimo posto l’elemento immediatamente minore e così via fino
ad aver ordinato tutto l’array.
Ordinamento per selezione (selection-sort): Determina il minimo fra gli
elementi dell’array e scambialo con il primo. Determina il minimo fra gli
elementi dell’array, escluso il primo, e scambialo con il secondo. Determina il
71
72
CAPITOLO 6. ORDINAMENTO
minimo dell’array, esclusi i primi due, e scambialo con il terzo e così via, fino
a quando la porzione di array da ordinare si è ridotta ad un unico elemento.
Ordinamento per inserzione (del giocatore di carte) (insertion-sort): A
partire dal secondo elemento fino all’ultimo si inserisce l’elemento al posto
giusto fra quelli compresi fra il primo e quello dello stesso elemento che si
inserisce.
Ordinamento veloce (quick-sort): Scegliere un valore a caso (perno); ripartire l’array in due porzioni: nella prima mettere tutti gli elementi minori o
uguali al perno, nella seconda elementi maggiori o uguali al perno. Ripetere
poi ricorsivamente l’ordinamento su entrambe le porzioni dell’array. La chiusura della ricorsione è costituita dalla condizione di avere un array di un solo
elemento. La partizione rispetto al perno avviene ponendo inizialmente due
indici alle estremità della porzione di array da ordinare, avvicinandoli verso il
centro, scambiando gli elementi quando si arriva ad elementi da scambiare, in
quanto fuori ordine rispetto al perno.
6.2
ESERCIZI
1. Scrivere un algoritmo per ciascuna delle strategie di ordinamento sopra
descritte.
2. Implementare in un linguaggio di programmazione ciascuno degli algoritmi descritti nell’esercizio precedente e verificarne la correttezza.
3. Descrivere la situazione dell’array [5, 6, 8, 1, 2, 7, 5, 3, 9, 6, 8, 4] dopo aver
eseguito un ciclo dell’algoritmo quicksort prendendo come perno l’elemento di posizione 1.
4. Spiegare cosa significa la frase: L’algoritmo di ordinamento quicksort ha
una complessità asintotica nel caso medio pari a O(n log n).
5. Fare una tabella che riporti la complessità degli algoritmi di ordinamento
bubblesort, insertionsort, selectionsort, quicksort, mergesort, heapsort nei
diversi casi ottimo, medio, pessimo.
6. Su una rivista si trova la seguente frase: Gli algoritmi di ordinamento
della classe A hanno, nel caso pessimo, una complessità asintotica pari
a n2 . Spiegare cosa significa la frase. Dire quali, fra gli algoritmi di
ordinamento studiati, appartengono alla classe A di cui si parla nella
rivista citata.
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
6.2. ESERCIZI
73
7. Su una rivista si legge: Il prof. Dalla Balla ha scoperto un algoritmo
di ordinamentodi array avente, nel caso medio, complessità uguale a
n log (logn). Dire se il prof. Dalla Balla ha fatto una scoperta degna di
nota e se è credibile.
8. Decidere se un array è ordinato (crescentemente o decrescentemente).
9. Descrivere un algoritmo di complessità lineare per ordinare un array di
valori booleani. Motivare perché tale algoritmo non contraddice la teoria
che afferma che il problema dell’ordinamento di un array ha complessità
asintotica pari a O(n log n).
10. Decidere se due array sono uno una permutazione dell’altro.
11. Decidere se due array contengono gli stessi elementi. Valutarne la complessità. Dimostrare che la complessità asintotica del problema è non
superiore a O(n log n).
12. Decidere se un array è composto da elementi tutti distinti. Valutare, nel
caso ottimo e nel caso pessimo, la complessità, dell’algoritmo descritto.
Motivare perché la complessità asintotica del problema in questione è
non superiore a O(n log n).
13. Determinare la frequenza massima con la quale compare l’elemento avente la più alta frequenza all’interno di un array. Determinare la complessità dell’algoritmo. Motivare perché il problema in questione ha una
complessità asintotica non superiore a O(n log n).
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
74
CAPITOLO 6. ORDINAMENTO
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
Capitolo 7
Alberi
Ogni genere posto ad un nodo alto dell’albero comprende le specie che ne dipendono, ogni specie subordinata a un genere è
un genere per la specie che le è subordinata, sino all’estremità inferiore dell’albero,
dove sono collocate le specie specialissime o
sostanze seconde, come ad esempio uomo.
U. Eco, Dall’albero al labirinto
Gli alberi costituiscono una tipologia di contenitori particolarmente importante, per il loro largo uso che ne viene fatto in informatica, per la ricchezza e
profondità dei problemi che permettono di descrivere e trattare e per l’eleganza
delle soluzioni che suggeriscono.
75
76
7.1
CAPITOLO 7. ALBERI
ALBERI
DEFINIZIONE 12. Un albero (radicato) è un insieme vuoto oppure un insieme non vuoto di elementi detti nodi in cui esiste un nodo speciale detto
radice e gli altri nodi sono ripartiti in una sequenza ordinata di n ≥ 0 sottoinsiemi T1 , . . . , Tn , detti sottoalberi o figli della radice, ciascuno dei quali è un
albero. Una sequenza di alberi viene detta foresta. Si dicono foglie di un
albero gli elementi dell’albero che non hanno sottoalberi. Diconsi discendenti
di un nodo tutti gli elementi che appartengono ai sottoalberi del nodo stesso. Un nodo dicesi padre dei nodi (figli) che costituiscono la radice dei suoi
sottoalberi. Tali nomenclature derivate per analogia con i gradi di parentela
possono essere estese. Ad esempio, due nodi figli di uno stesso padre vengono
detti fratelli. Dicesi cammino da un nodo p ad un nodo discendente q una
successione di nodi (n1 , . . . , nk ) tali che n1 = p, nk = q, ni è padre di ni+1 ,
per ogni i = 1, k − 1. Un arco è il cammino che unisce due nodi adiacenti
(padre-figlio). La lunghezza di un cammino (n1 , . . . , nk ) è rappresentata dal
numero di archi che uniscono il primo nodo all’ultimo nodo, ossia k − 1. La
profondità di un nodo è uguale alla lunghezza del cammino dalla radice al nodo stesso. L’altezza di un albero è pari alla massima propondità dei suoi nodi
foglia. Un livello di un albero è costituito dai nodi aventi la stessa profondità.
Il numero massimo dei figli di uno stesso nodo viene detto grado (dell’albero).
Un albero di grado k dicesi completo se ogni nodo, esclusi i nodi foglia ed al
più i nodi padri delle foglie, ha esattamente k figli.
Il termine albero è mutuato direttamente dalla botanica, anche se usualmente, per questioni di praticità grafica, si usa rappresentare gli alberi con la
radice in alto. La figura 7.1 illustra un esempio di albero.
el
bl
@
@
@ l
f
al
H
H
cl
J
J
lJ l
l
m
i
l
HH
H
H
H l
d
@
@
@ l
gl
h
nl
Figura 7.1: Albero di grado 3, altezza 3, costituito da 12 nodi (di cui 5 sono
nodi interni e 7 sono foglie), strutturati in 4 livelli.
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
7.2. RAPPRESENTAZIONI DEGLI ALBERI
77
Osservazione. Gli alberi vengono solitamente definiti in modo ricorsivo, come descritto nella precedente definizione; ciò suggerisce in molti casi la
struttura dati (ricorsiva) per la loro rappresentazione e la forma degli algoritmi
(ricorsivi) che si adottano per trattare queste strutture.
7.2
RAPPRESENTAZIONI DEGLI ALBERI
Un metodo per rappresentare un albero T , vuoto oppure costituito da una radice r e dalla sequenza di sottoalberi [T1 , . . . , Tn ], consiste nella rappresentazione
a struttura, ossia:
rap(T ) = {
[]
se T è vuoto
[r, rap(T1 ), . . . , rap(Tn )] altrimenti
Graficamente un albero non vuoto composto dalla radice r e dalla sequenza di sottoalberi [T1 , . . . , Tn ] può essere rappresentato come descritto nella
figura 7.2.
rjP
PP
P
PP
PP
PP
PP
P
B
B
B
B
B
B
B
B
B
...
B
B
B
B
B
B
T1 BB
T2 BB
Tn BB
Figura 7.2: Rappresentazione ricorsiva di un albero composto dalla radice r e
dalla sequenza di sottoalberi [T1 , . . . , Tn ].
Esempio 43. L’albero descritto nella figura 7.1 viene rappresentato mediante
la struttura
[a, [b, [e], [f, [i], [l], [m] ] ] , [c], [d, [g], [h, [n] ] ] ]
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
78
CAPITOLO 7. ALBERI
Una rappresentazione alternativa consiste nell’utilizzo dei diagrammi di
Venn, come descritto nella figura 7.3.
'
a
'
b
'
e
$
'
$
$
f
m
i
c
$
d g
h n
l &
%
&
%
&
%
&
%
Figura 7.3: Rappresentazione di un albero mediante diagrammi di Venn.
Un’altra rappresentazione mediante parentesi adotta la sintassi del linguaggio Lisp, come descritto nella figura 7.4.
(a(b(e)(f (i)(l)(m)))(c)(d(g)(h(n))))
Figura 7.4: Rappresentazione di un albero mediante parentesi.
Un albero può essere descritto anche mediante una scrittura indentata dei
nodi, come illustrato nella figura 7.5. Questa notazione risulta utile quando
si voglia rappresentare un albero in modo testuale ed in modo che risulti
facilmente decifrabile la relazione di gerarchia fra i nodi stessi.
a
b
e
f
i
l
m
c
d
g
h
n
Figura 7.5: Rappresentazione di un albero mediante indentazione.
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
7.3. METODI DI VISITA DEGLI ALBERI
79
La rappresentazione indentata suggerisce un’altra possibilità di rappresentazione: un albero viene rappresentato mediante una sequenza di coppie [x, n]
dove x denota l’elemento ed n il grado di indentazione. Questa modalità di
rappresentazione è descritta nella figura 7.6.
[ [a, 0], [b, 1], [e, 2], [f, 2], [i, 3], [l, 3], [m, 3], [c, 1], [d, 1], [g, 2], [h, 2], [n, 3] ]
Figura 7.6: Rappresentazione di un albero mediante sequenza di coppie.
7.3
METODI DI VISITA DEGLI ALBERI
Visitare un albero significa attraversarlo e produrre una sequenza piatta degli elementi memorizzati nei nodi dell’albero. Si possono adottare diverse
strategie di visita:
• visita in ordine anticipato:
1. visita la radice
2. visita i sottoalberi in ordine anticipato
• visita in ordine differito:
1. visita i sottoalberi in ordine differito
2. visita la radice
Esempio 44. Le visite in ordine anticipato e differito dell’albero descritto
figura 7.1 producono rispettivamente le seguenti sequenze:
[a, b, c, f, i, l, m, c, d, g, h, n]
[e, i, l, m, f, b, c, g, n, h, d, a]
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
80
CAPITOLO 7. ALBERI
7.4
ALBERI BINARI
DEFINIZIONE 13. Dicesi albero binario un un albero nel quale ogni nodo
ha al più due figli.
Un’equivalente definizione ricorsiva, più utile, è la seguente:
DEFINIZIONE 14. Dicesi albero binario un insieme vuoto oppure una terna
[r, TL , TR ] in cui TL e TR sono alberi binari detti, rispettivamente, figlio sinistro
e figlio destro.
La figura 7.7 illustra un esempio di albero binario.
am
m
b
Z
Z
Z
ZZ
cm
@
@
dm
gm
@
@
em
J
J
J
hm
fm
J
J
J
im
Figura 7.7: Rappresentazione di un albero binario composto da 9 nodi
strutturati in 4 livelli.
7.5
OPERAZIONI SUGLI ALBERI BINARI
Denotando con BE l’insieme degli alberi binari composti da elementi di tipo E, gli operatori sugli alberi binari possono essere descritti, in notazione
funzionale, dal seguente elenco:
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
81
7.6. VISITA DEGLI ALBERI BINARI
BE
BE
BE
lef t
right
root
isEmpty
7.6
:
:
:
:
:
:
:
∅
E
2
E × BE
BE
BE
BE
BE
→
→
→
→
→
→
→
BE
BE
BE
BE
BE
E
Bool
//
//
//
//
//
//
//
costruttore (albero vuoto)
costruttore (albero radice)
costruttore (albero con figli)
selettore del figlio sinistro
selettore del figlio destro
selettore della radice
test se l’albero è vuoto
VISITA DEGLI ALBERI BINARI
Un albero binario può essere visitato secondo le strategie di seguito descritte:
• visita in ordine preordine:
1. visita la radice
2. visita il figlio sinistro in preordine
3. visita il figlio destro in preordine
• visita in ordine inordine o in ordine simmetrico:
1. visita il figlio sinistro in inordine
2. visita la radice
3. visita il figlio destro in inordine
• visita in postordine:
1. visita il figlio sinistro in postordine
2. visita il figlio destro in postordine
3. visita la radice
Esempio 45. Visitando l’albero riportato nella figura 7.7 mediante una visita
in preordine, inordine e postordine si ottengono, rispettivamente, le seguenti
sequenze:
[a, b, d, g, h, c, e, i, f ]
[g, d, h, b, a, e, i, c, f ]
[g, h, d, b, i, e, f, c, a]
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
82
7.7
CAPITOLO 7. ALBERI
RAPPRESENTAZIONE DI ALBERI BINARI
Gli alberi binari, essendo dei casi particolari di liste non lineari, possono essere
rappresentati derivando il metodo di rappresentazione delle liste non lineari.
Poiché questa rappresentazione è poco efficiente, viene usualmente scelta una
rappresentazione specifica (in tale modo si perde però, in un linguaggio orientato agli oggetti, la possibilità di derivare la classe degli alberi binari dalla
classe delle liste lineari).
Un albero binario vuoto può essere rappresentato graficamente da un simbolo convenzionale, ad esempio ○. Un albero binario non vuoto T = (r, TL , TR )
può essere rappresentato graficamente come descritto nella figura 7.8.
rj
@
@
B
B
B
B
TL
B
BB
@
@
B
B
B
B
B
TR B
B
Figura 7.8: Rappresentazione ricorsiva di un albero binario.
Questo schema di rappresentazione può essere immediatamente ricondotto
ad una rappresentazione mediante una struttura ricorsiva:
[r, rap(TL ), rap(TR )]
chiudendo la ricorsione rappresentando l’albero vuoto con [ ].
Esempio 46. L’albero binario riportato nella figura 7.7 viene rappresentato
mediante la seguente struttura:
[a, [b, [d, [g, [ ], [ ] ], [h, [ ], [ ] ], [ ] ], [c, [e, [ ], [i, [ ], [ ] ] ], [f, [ ], [ ] ] ] ]
Un albero binario completo o quasi completo può essere efficientemente
rappresentato mediante una strutturazione sequenziale dei suoi elementi. In
altri termini, un albero binario è rappresentabile mediante una sequenza. L’albero viene completato con dei nodi contenenti il valore nullo (indicato con ♢);
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
83
7.8. ALBERI BINARI DI RICERCA
successivamente si memorizzano per livelli tutti i nodi dell’albero completo
così costruito. La rappresentazione sequenziale di un albero di altezza h richiede una sequenza di al più 2h+1 − 1 elementi. Serve inoltre un particolare
valore, non appartenente al tipo di base E, per rappresentare l’elemento nullo,
ossia non esistente.
Esempio 47. La figura 7.9 descrive la rappresentazione sequenziale per livelli
dell’albero binario riportato nella figura 7.7.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
a
b
c
d
♢
e
f
g
h
♢
♢
♢
i
♢
♢
Figura 7.9: Rappresentazione sequenziale dell’albero binario riportato nella
figura 7.7
Si ricava facilmente che, dato un elemento di posizione k nella sequenza
a che rappresenta l’albero, gli elementi che costituiscono la radice del figlio
sinistro, del figlio destro e del padre (se k > 1) sono individuati mediante le
seguenti relazioni:
root(lef t(a[k])) = a[2k]
root(right(a[k])) = a[2k + 1]
root(f ather(a[k])) = a[⌈k/2⌉]
7.8
ALBERI BINARI DI RICERCA
L’obiettivo degli alberi binari di ricerca è quello di permettere degli algoritmi di ricerca con complessità logaritmica, non degradando al contempo la
complessità dell’inserimento ordinato di nuovi elementi, mantenendo questa
operazione ad una complessità logaritmica (gli array e le liste concatenate non
permettono di mantenere entrambe queste performances sia per la ricerca che
per l’inserimento).
DEFINIZIONE 15. Un albero binario di ricerca (ABR) è un albero binario
in cui ogni nodo è maggiore o uguale dei nodi del sottoalbero sinistro e minore
o uguale dei nodi del sottoalbero destro.
Risulta più utile la seguente definizione:
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
84
CAPITOLO 7. ALBERI
DEFINIZIONE 16. Un albero binario di ricerca (ABR) è un albero binario
vuoto oppure è un albero binario [r, TL , TR ] dove TL è un albero binario vuoto
oppure è un albero binario di ricerca avente la radice minore o uguale di r e
TR è un albero vuoto oppure è un albero binario di ricerca avente la radice
maggiore o uguale di r.
5l
Z
Z
Z
Z
Z
4l
8l
@
2l
1l
@
@
6l
J
J
J
3l
9l
J
J
J
7l
Figura 7.10: Rappresentazione di un albero binario di ricerca.
Gli ABR vengono solitamente impiegati come struttura dati sulla quale
eseguire efficientemente delle ricerche. L’algoritmo 40 costituisce la tipica
forma della ricerca in un ABR.
Algoritmo 40 - ricercaABR(a, x) - Algoritmo di ricerca in un ABR
Input: ABR a, elemento x
Output: T RU E se e solo se x è presente in a
1: if isEmpty(a) then
2:
return F ALSE
3: else if root(a) = x then
4:
return T RU E
5: else if root(a) < x then
6:
return ricercaABR(lef t(a), x)
7: else
8:
return ricercaABR(right(a), x)
9: end if
Se l’ABR a è perfettamente bilanciato, la complessità della ricerca è O(log n),
dove n è il numero dei nodi; nel caso in cui l’ABR sia completamente sbilanciato, la complessità della ricerca è lineare, ossia pari a O(n).
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
85
7.9. ESERCIZI
7.9
ESERCIZI
1. Ricostruire graficamente l’albero binario rappresentato dalla seguente
struttura:
[7, [4, [ ], [8, [3, [ ], [ ] ], [5, [ ], [ ] ] ] ], [9, [ ], [6, [ ], [ ] ] ] ]
2. Una visita in preordine ed una in inordine di un albero binario producono
rispettivamente le seguenti sequenze:
abdcef
bdaecf
Ricostruire graficamente l’albero binario.
3. Determinare il numero di nodi di un albero binario.
4. Determinare il numero di foglie di un albero binario.
5. Determinare il numero di nodi interni di un albero binario.
6. Determinare la somma dei valori presenti in un albero binario.
7. Determinare il massimo valore presente in un albero binario.
8. Determinare il massimo valore delle foglie di un albero binario.
9. Decidere se un dato elemento è presente in un albero binario.
10. Determinare quante volte è presente un dato elemento in un albero
binario.
11. Determinare il numero di elementi distinti di un albero binario.
12. Decidere se due alberi binari sono uguali.
13. Determinare l’altezza di un albero binario.
14. Determinare la larghezza (massimo numero di nodi che si trovano su uno
stesso livello) in un albero binario.
15. Determinare l’insieme dei valori delle foglie di un albero binario.
16. Decidere se un albero binario è perfettamente bilanciato.
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
86
CAPITOLO 7. ALBERI
17. Determinare l’altezza di un albero binario.
18. Determinare il valore minimo ed il valore massimo assumibile dall’altezza
di un albero binario costituito da n nodi.
19. Definire un coefficiente di bilanciamento di un albero binario che esprima
il grado di bilanciamento dell’albero.
20. Un albero binario si dice completamente sbilanciato se ogni suo nodo
ha al più un figlio. Decidere se un albero binario è completamente
sbilanciato.
21. Decidere se due dati alberi binari sono isomorfi (stessa forma), ossia se
hanno la stessa struttura, indipendentemente dai valori dei loro nodi.
22. Descrivere una strategia per elaborare un albero binario senza ricorrere
alla ricorsione.
23. Decidere se in un ABR è presente un dato elemento.
24. Studiare la complessità della ricerca in un ABR in funzione del numero n dei nodi, dell’altezza h dell’albero e del grado di bilanciamento.
Distinguere i casi ottimo, medio e pessimo.
25. Determinare quante volte in un ABR è presente un dato elemento.
26. Determinare l’elemento più grande presente in un ABR.
27. Determinare in un ABR il numero di elementi compresi fra due dati
estremi.
28. Costruire un ABR a partire da una sequenza di elementi, prendendo
il primo elemento della sequenza come radice dell’albero ed inserendo
nell’albero gli elementi dal secondo in poi senza spostare gli elementi già
inseriti.
29. Illustrare, con un esempio, la seguente proprietà: Una visita inordine
(in ordine simmetrico) di un ABR produce una sequenza ordinata dei
nodi. Utilizzando questa proprietà, costruire un ABR a partire da una
generica sequenza di nodi.
30. Ordinare una sequenza basandosi sulla proprietà che afferma che la visita
preordine di un ABR genera una sequenza ordinata.
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
7.9. ESERCIZI
87
31. Un’espressione aritmetica composta da operatori binari infissi espressa
nella usuale notazione algebrica con parentesi, può essere rappresentata
sotto forma di albero binario (detto albero sintattico) dove i nodi non
terminali contengono gli operatori e le foglie gli operandi. Descrivere
mediante un albero sintattico l’espressione (7 + (6 − 2)) ∗ 3. Valutare
un’espressione rappresentata mediante un albero sintattico.
32. Determinare la distanza di due nodi di un albero binario definita come
la lunghezza del minimo cammino che li congiunge.
33. Decidere se un albero binario è incluso in un altro.
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
88
CAPITOLO 7. ALBERI
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
Capitolo 8
Grafi
I grafi sono strutture matematiche che rivestono interesse per un’ampia gamma
di campi applicativi. I grafi costituiscono una struttura matematica discreta
che viene studiata dal punto di vista algebrico, indipendentemente dalle specifiche aree applicative. La teoria dei grafi costituisce un’importante parte
della combinatoria; i grafi inoltre sono utilizzati in aree come topologia, teoria
degli automi, funzioni speciali, geometria dei poliedri, algebre di Lie.
Intuitivamente un grafo è una rete di nodi (rappresentati mediante dei
punti) e di collegamenti fra di essi (rappresentati mediante delle linee di congiunzione). I grafi vengono utilizzati per descrivere modelli di sistemi e processi studiati nell’informatica (programmi, circuiti, reti di computer, mappe
di siti), nell’ingegneria (sistemi fluviali, reti stradali, trasporti), nella chimica,
nella biologia molecolare, nella ricerca operativa, nella organizzazione aziendale, nella linguistica strutturale, nella storia (alberi genealogici, filologia dei
testi). I seguenti esempi danno un’idea di alcuni campi applicativi dei grafi:
- una rete stradale può essere rappresentata mediante un grafo in cui i
nodi sono le città e le linee rappresentano i collegamenti fra le città
- le reti logiche sono rappresentabili mediante dei grafi in cui i nodi sono
le porte logiche and, or e not e le linee corrispondono ai collegamenti fra
le porte
- la struttura di un ipertesto è rappresentabile mediante un grafo in cui i
nodi sono le pagine e le linee sono i collegamenti fra le pagine
- le molecole possono essere rappresentate mediante dei grafi in cui i nodi
sono gli atomi che le compongono e le linee esprimono i legami fra gli
atomi
89
90
8.1
CAPITOLO 8. GRAFI
DEFINIZIONI
I termini fondamentali della teoria dei grafi sono riportati nella seguente
definizione.
DEFINIZIONE 17. Un grafo è costituito da un insieme di elementi detti
vertici e da un insieme di lati che collegano coppie di vertici 1 . Una coppia
non ordinata di vertici è detta lato, mentre una coppia ordinata è detta arco.
Un grafo composto solo da lati è detto grafo non-orientato; un grafo composto
solo da archi è detto grafo orientato (o diretto) o digrafo. Due vertici u, v
connessi da un lato vengono detti estremi del lato (arco); un lato (arco) risulta
identificato dalla coppia formata dai suoi estremi (u, v). Un lato (arco) che
ha due estremi coincidenti si dice cappio.
Un cammino (catena nel caso di grafo orientato) da un vertice u ad un
vertice v è una successione di vertici (v1 , . . . , vn ) tali che v1 = u, vn = v ed
inoltre per ogni i = 1, . . . , n − 1, (vi , vi+1 ) è un lato del grafo. Se uno dei vertici
è ripetuto il cammino viene detto semplice; se il cammino passa per tutti i
vertici viene detto euleriano; se v1 = vn il cammino viene detto circuito o ciclo.
Un circuito è detto hamiltoniano se passa una sola volta per tutti i vertici del
grafo (escluso il primo). Un grafo non orientato si dice connesso se esiste un
cammino congiungente ogni coppia di vertici.
t
H
H
t
@
@
HH
H
HH
H
t
t
@
@t
t
HH
Ht
@
@
t
Figura 8.1: Grafo composto da 8 vertici e 12 lati.
1
Una coppia di vertici può essere unita da più lati o archi; in questi casi si parla di lati
(archi) multipli o multilati (multiarchi) ed il grafo viene detto multigrafo. In caso contrario
si parla di grafo semplice. In questo capitolo si considerano solo grafi semplici.
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
91
8.2. TIPOLOGIE DI GRAFI
In alcune argomentazioni risulta utile riferirsi alla seguente definizione
formale.
DEFINIZIONE 18. Un grafo è una coppia G = (V, E) dove
V è un insieme di elementi detti vertici
E è un insieme di elementi detti lati costituiti da coppie di elementi di
V , ossia E ⊆ V × V
Il grafo G = (∅, ∅), privo di vertici e di lati, è detto grafo nullo.
8.2
TIPOLOGIE DI GRAFI
In un grafo si possono memorizzare informazioni in diversi modi:
• orientando il lato, ottenendo un grafo orientato
• memorizzando delle informazioni nei nodi; tali informazioni vengono
dette label ed il grafo che si ottiene viene detto grafo labellato
• memorizzando delle informazioni nei lati; tale informazioni vengono dette pesi e il grafo che si ottiene viene detto grafo pesato
La figura 8.2 descrive un grafo orientato, pesato e labellato.
h
6HH
φ
λ -e µ
d
γ
HH
Hπ
HH
6@
I
H
jg
H
f ν H
6
δ@
η
κ
@
a
α @
-b β
c
Figura 8.2: Grafo orientato, pesato e labellato.
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
92
CAPITOLO 8. GRAFI
8.3
OPERAZIONI SUI GRAFI
Le operazioni fondamentali che si possono eseguire su un grafo grafo G sono
sinteticamente descritte nel seguente elenco:
• creare un grafo vuoto
• aggiungere un vertice u
• aggiungere un lato a congiungente due nodi u e v
• eliminare un dato vertice u (e i lati esso collegati)
• eliminare l’arco congiungente due dati nodi u e v
• ottenere la lista degli archi che escono da un dato vertice u
• ottenere la lista degli archi entranti in un dato vertice u
8.4
RAPPRRESENTAZIONI DEI GRAFI
Un grafo viene rappresentato mediante delle strutture dati elementari. Vengono abitualmente utilizzate le strutture predisposte dagli usuali linguaggi di
programmazione: array, liste, matrici. La particolare scelta dipende dalla
tipologia del grafo e dalle operazioni che si intendono realizzare e, quindi, dal
particolare problema da risolvere.
• rappresentazione mediante insiemi:
basandosi direttamente sulla defini-zione 18, un grafo può essere rappresentato mediante l’insieme dei vertici e dei lati.
Ad esempio il grafo
-e
d
6@
I
6
f
@
@
a
@
-b
c
viene rappresentato mediante la seguente coppia di insiemi:
({a, b, c, d, e, f }, {(a, b), (a, d)(b, d), (b, e), (b, f ), (c, b), (d, e), (f, e)})
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
93
8.4. RAPPRRESENTAZIONI DEI GRAFI
• rappresentazione mediante matrice di adiacenza:
un grafo di n nodi può essere rappresentato mediante una matrice binaria, identificando ciascun nodo mediante un valore naturale progressivo,
1, 2, 3, . . . , n.
Ad esempio il grafo
4t
@
@
@
5t
6t
@
@t
2
t
3
@
t
1
viene rappresentato mediante la matrice binaria
0
1
0
1
0
0
1
0
1
1
1
1
0
1
0
0
0
0
1
1
0
0
1
0
0
1
0
1
0
1
0
1
0
0
1
0
La rappresentazione di un grafo mediante una matrice di adiacenza è
utilizzabile nel caso di grafi orientati e non orientati; in quest’ultimo
caso la matrice risulta simmetrica. Una matrice di adiacenza è utilizzabile anche nel caso di grafi pesati nel quale caso al posto dell’1 viene
messo il valore del peso dell’arco corrispondente. Spesso, nella matrice
di adiacenza vengono indicati solo i valori non nulli e vengano lasciati
non definiti i valori corrispondenti allo 0. Nel caso di un grafo labellato,
alla matrice binaria bisogna aggiungere un array dove sono registrati le
informazioni dei vertici.
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
94
CAPITOLO 8. GRAFI
• rappresentazione mediante lista di adiacenza:
la rappresentazione mediante una lista di adiacenza si basa sull’idea di
rappresentare ciascun nodo con ad esso associata una lista i cui elementi
rappresentano gli archi uscenti dal nodo stesso; l’elemento corrispondente
ad un generico nodo u ha la struttura
[u, lista degli archi uscenti da u]
Ad esempio il grafo
λ -e µ
d
γ
6@
I
f
6
δ@
η
@
a
α @
-b β
c
viene rappresentato mediante la lista composta
[ [a, [ [b, α], [d, γ] ] ], [b, [ [d, δ], [e, ], [f, η] ] ], [c, [ [b, β] ] ], [d, [ [e, λ] ] ], [f, [ [e, µ] ] ] ] ]
8.5
8.5.1
PROBLEMI SUI GRAFI
Problema del cammino minimo
Dato un grafo orientato e pesato determinare un cammino di peso minimo da
un nodo di partenza ad un nodo di arrivo, ossia un cammino per il quale è
minima la somma dei pesi degli archi che lo compongono. Si tratta di un
tipico problema dei trasporti. Si pensi ad esempio al problemi di minimizzare
i consumi per il trasporto da una città ad un’altra. L’algoritmo risolutivo più
noto è attribuito a E. W. Dijkstra ed ha una complessità O(n2 ) dove n è il
numero dei nodi.
8.5.2
Problema del collegamento minimo
Dato un grafo non orientato pesato che rappresenta una rete di collegamenti
fra i vertici, si vuole eliminare i collegamenti superflui e mantenere solo quelli
indispensabili in modo da minimizzare il costo totale. Ciò corrisponde a voler
trovare un albero di supporto minimo di un grafo non orientato connesso.
Una della applicazioni più significative è quella del progetto di una rete di
comunicazioni in cui i nodi rappresentano delle città. Per questo problema
sono noti algoritmi di complessità polinomiale.
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
8.5. PROBLEMI SUI GRAFI
8.5.3
95
Problema del commesso viaggiatore
Il problema del commesso viaggiatore fu considerato per la prima volta agli
inizi degli anni ’30 del secolo scorso, anche se, sotto altra veste, comparve
all’interno della teoria dei grafi già nel diciannovesimo secolo. Il problema può
essere enunciato come segue: Dato un grafo orientato e pesato, determinare
un circuito hamiltoniano (ossia passante una sola volta per tutti i nodi del
grafo) di costo minimo. Nella sua formulazione più caratteristica il problema
si enuncia come segue: Un commesso viaggiatore deve visitare un dato numero
di città. Ogni città è collegata a tutte le altre da una strada di cui si conosce
la lunghezza. Determinare il percorso più breve che passa per ogni città una
sola volta e ritorna alla città di partenza.
8.5.4
Problema dell’isomorfismo fra grafi
Dati due grafi G1 e G2 si dicono isomorfi (stessa forma) se sono sostanzialmente
lo stesso grafo, cioè se è possibile trovare una corrispondenza biunivoca f fra
i nodi di G1 e G2 in modo tale che esiste un arco da u a v in G1 allora esiste
un arco f (u) in G2 e viceversa. A tutt’oggi, non si conoscono algoritmi non
esponenziali rispetto al numero dei nodi; nel caso peggiore si è infatti sempre
costretti a provare n! permutazioni possibili dei nodi di uno dei due grafi.
8.5.5
Problema della planarità di un grafo
Un grafo non orientato dicesi planare se è possibile disegnarlo su un piano
senza sovrapporre i lati. Il problema della planarità si enuncia come segue:
Dato un grafo non orientato e connesso, stabilire se è planare o meno, ed eventualmente farlo vedere. Il problema ha interesse pratico nella progettazione
di reti stradali, circuiti stampati, ecc. in cui si vuole evitare di sovrapporre dei
collegamento. Già Eulero aveva trovato una semplice condizione necessaria:
m ≤ 3n−6 dove n indica il numero dei nodi ed m il numero dei lati. Nel 1974 è
stato scoperto un algoritmo lineare rispetto ad n che risolve questo problema.
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014
96
CAPITOLO 8. GRAFI
8.6
ESERCIZI
1. Definire cos’è un grafo pesato, non orientato e labellato. Fare un esempio
di un tale grafo, contenente 5 vertici e 7 lati. Descrivere una struttura
dati adeguata a rappresentare il grafo.
2. Descrivere una situazione in cui risulta idoneo ricorrere ad un grafo pesato, non orientato e non labellato. Fare un esempio di un tale grafo,
composto da 5 vertici e 8 lati. Descrivere una struttura dati adeguata a
rappresentare il grafo.
3. Descrivere un grafo orientato, non pesato e non labellato, avente 6 vertici
e 11 lati. Descrivere una struttura dati adeguata a rappresentare il grafo.
4. Descrivere un grafo orientato, pesato e non labellato, avente 6 vertici e 7
archi. Descrivere una struttura dati adeguata a rappresentare il grafo.
5. Descrivere un grafo non orientato, pesato e labellato, avente 6 vertici e
11 lati. Descrivere una struttura dati adeguata a rappresentare il grafo.
6. Descrivere un grafo orientato e pesato, composto da 6 vertici e 8 lati.
Descrivere una struttura dati adeguata a rappresentare il grafo.
7. Disegnare il grafo rappresentato dalla seguente matrice di adiacenza:
1
0
0
1
1
0
0
0
1
1
0
0
0
0
1
0
Modificare la matrice in modo da aggiungere un lato dal vertice 1 al
vertice 4, scambiare il verso al lato congiungente i vertici 2 e 3, aggiungere
un vertice (5) e collegarlo al vertice 3.
L. Calvi - Quaderni di Informatica - Strutture ed Algoritmi - Feltre - 2014