...

Algoritmi e Strutture Dati Capitolo 16

by user

on
Category: Documents
13

views

Report

Comments

Transcript

Algoritmi e Strutture Dati Capitolo 16
Bactracking
Introduzione
Algoritmi e Strutture Dati
✦
✦
Alberto Montresor
✦
This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike License. To view a
copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/2.5/ or send a letter to Creative
Commons, 543 Howard Street, 5th Floor, San Francisco, California, 94105, USA.
1
© Alberto Montresor
Problemi: commenti
✦
✦
Contare le soluzioni ammissibili
✦
Costruire almeno una o tutte le soluzioni ammissibili
Trovare le soluzioni ammissibili “più grandi”, “più piccole” o in generale “ottimali”
Esempio:
Elencare tutti i sottoinsiemi di k elementi di un insieme S
✦
u
Elencare algoritmicamente tutte le possibili soluzioni ammissibili (spazio di ricerca)
Trovare le soluzioni ottimali
u
Si costruiscono tutte le soluzioni e si valuta una funzione di costo
u
Si possono utilizzare altre tecniche:
Costruire almeno una soluzione
Si utilizza l'algoritmo per elencare tutte le soluzioni, fermandosi alla prima
u
Contare le soluzioni
✦
In molti casi, è possibile contare in modo analitico
✦
✦
Esempio: |S| = n, # sottoinsiemi di k elementi:
2
© Alberto Montresor
Problemi: commenti
Enumerazione
✦
✦
Problemi “tipici”
✦
Università di Trento
✦
Definizioni basate sul concetto di soluzione ammissibile: una soluzione che
soddisfa un certo insiemi di criteri
✦
Capitolo 16 - Backtrack
✦
Classi di problemi (decisionali, di ricerca, di ottimizzazione)
Programmazione dinamica, greedy
u
Tecniche risolutive per problemi intrattabili
Esempi:
u
n!
(n k)!k!
u
Circuito hamiltoniano (commesso viaggiatore)
In altri casi, si costruiscono le soluzioni e si contano
© Alberto Montresor
3
© Alberto Montresor
4
Costruire tutte le soluzioni
✦
Per costruire tutte le possibili soluzioni, si utilizza un approccio “brute-force”: si
esamina interamente lo spazio delle possibili soluzioni
✦
✦
✦
Backtracking
✦
A volte è l'unica strada possibile
La potenza dei computer moderni rende “affrontabili” problemi
di discrete dimensioni
✦
10! = 3.63 · 106
permutazione di 10 elementi
✦
220
sottoinsieme di 20 elementi
= 1.05 ·
106
✦
5
✦
✦
C insieme generico, possibili soluzioni sottoinsiemi di C
✦
C mosse di gioco, possibili soluzioni sequenze di mosse
✦
C archi di un grafo, possibili soluzioni percorsi sul grafo
© Alberto Montresor
Un metodo sistematico per iterare su tutte le possibili istanze di uno spazio di
ricerca
E' una tecnica algoritmica che, come altre, deve essere personalizzata per ogni
applicazione individuale
6
Come procedere: ad ogni passo, partiamo da una soluzione parziale S[1..k]
✦
Se S[1..k] è una soluzione ammissibile, la “processiamo” in qualche modo e
abbiamo finito
Altrimenti:
✦
Esempi:
✦
Come funziona?
✦
Il contenuto degli elementi S[i] è preso da un insieme di scelte C dipendente dal problema
C insieme generico, possibili soluzioni permutazioni di C
“Ritenta, e sarai più fortunato”
© Alberto Montresor
✦
Una soluzione viene rappresentata come un vettore S[1..n]
✦
✦
Backtracking
Organizzazione generale
✦
“Prova a fare qualcosa, e se non va bene, disfalo e prova qualcos’altro”
✦
Backtracking
✦
✦
✦
Inoltre, a volte non è necessario analizzare l'intero spazio delle soluzioni
© Alberto Montresor
Filosofia:
✦
✦
7
© Alberto Montresor
Se è possibile, estendiamo S[1..k] con una delle possibili scelte in una
soluzione S[1..k+1], e valutiamo la nuova soluzione ricorsivamente
Altrimenti, “cancelliamo” (ritornando indietro nella ricorsione) l'elemento S[k] (backtrack) e ripartiamo dalla soluzione S[1..k-1]
Si può fare backtrack su più passi di ricorsione
8
tematici, come il Sudoku. Questo problema può essere risolto semplicemente terminando
l’enumerazione alla prima occorrenza di una soluzione.
Backtracking
Backtracking
✦
Spazio di ricerca ≡ albero di decisione
✦
Soluzioni ≡ foglie in un albero di decisione
✦
Soluzioni parziali ≡ nodi interni dell'albero di decisione
✦
Radice ≡ soluzione parziale vuota
Lo
di ricerca
essere
ridotto: si utilizza un approccio a “forza bruta”, basato
Perspazio
enumerare
tutte le può
soluzioni
ammissibili,
✦ “Rami”
su backtrack,
che
esplora loche
spazio
delle soluzioni
in modo
sistematico.
Una soluzione
viene
dell'albero
sicuramente
non portano
a soluzioni
ammissibili
possono
rappresentata
come
un
vettore
S[1
.
.
.
n].
Il
contenuto
degli
elementi
S[i]
è
preso
da
un
insieme
essere “potati” (pruned)
di scelte dipendente dal particolare problema. Ad ogni passo, si parte da una soluzione parziale
✦ La valutazione viene fatta nelle soluzioni parziali radici del sottoalbero S[1 . . . i 1]; se è possibile, si estende tale soluzione con una delle possibili scelte in una
da potare
soluzione parziale S[1 . . . i] e si valuta la nuova soluzione parziale ricorsivamente; altrimenti,
si opera un passo di backtrack (ritornando indietro nella ricorsione) e si effettua una nuova
scelta fra quelle possibili a partire da S[1 . . . i 1].
✦
La procedura enumerazione() realizza questo schema. Ad ogni chiamata della procedura,
l’insieme C delle possibili opzioni per la scelta i-esima viene calcolato a partire dalle scelte
precedenti S[1 . . . i 1]. Tutte le possibili strade vengono quindi intraprese: uno dopo l’altro,
tutti i valori di C vengono scelti e memorizzati in S[i]; se S[1, . . . , i] è una soluzione ammissibile, tale soluzione viene elaborata in qualche modo (stampata, contata, valutata in base ad
una funzione di costo, etc.) dalla procedura processSolution(). Infine, la procedura enumerazione() chiama ricorsivamente se stessa, dando per assodate le prime i scelte e spostandosi
sull’(i + 1)-esima. La ricorsione termina quando l’albero è stato visitato completamente.
9
© Alberto Montresor
Backtracking
✦
boolean enumerazione(I TEM[ ] S, integer n, integer i, . . . )
Due possibili approcci:
✦
S ET C ⇥ choices(S, n, i, . . .)
% Determina C in funzione di S[1 . . . i
foreach c ⇤ C do
S[i] ⇥ c
if S[1 . . . i] è una soluzione ammissibile then
if processSolution(S, n, i, . . .) then return true
if enumerazione(S, n, i + 1, . . .) then return true
return false
Ricorsivo:
✦
✦
Per supportare il caso in cui è sufficiente generare una e una sola soluzione ammissibile, le10
procedure enumerazione() e processSolution() restituiscono un valore booleano, vero se una
soluzione ammissibile è stata trovata, falso altrimenti.
Algoritmo generico
© Alberto Montresor
lavora tramite una visita in profondità nell'albero delle scelte, basata su un
approccio ricorsivo
Iterativo: utilizza un approccio greedy, eventualmente tornando
sui propri passi
✦
Esempio: Inviluppo convesso
✦
Esempio: String matching
© Alberto Montresor
1]
l C insieme di possibili Ritorna true per bloccare
Applichiamo
lo
schema
precedente
all’Esempio
16.13. In questo
caso, la scelta possibile è
candidati
per estendere
l'esecuzione, ad esempio
la
soluzione
dopo laoprima
se inserire
meno soluzione
un elemento; l’insieme delle scelte è quindi dato da {true,false} e il vettore
Ritorna false
Sl costituisce
unaper
rappresentazione
booleana dell’insieme generato. L’insieme delle scelte è
le
soluzioni
parziali
l S:sono
tuttavia vuoto quando tutti gli elementi
già contenente
stati esaminati,
vettore
la ovvero quando i > n.
soluzione
parziale
S[1..i]
Dato un insieme di n elementi, l’elemento i-esimo può essere presente oppure no. In
l ...: informazioni addizionali
altre parole, l’insieme delle scelte è dato da {true,false} e il vettore S è una rappresentazione booleana dell’insieme generato. Una prima versione dell’algoritmo potrebbe essere la
l
11
© Alberto Montresor
12
S ET A
Esempio 1
✦
;
while A non forma un albero di copertura do
S ET mst-generico
(Gsicuro
RAPH G,
] w)
trova un arco
[u,integer[
v]
Problema
SElencare
ETA
A tutti
;[
A
{[u, v]} di un insieme S
i sottoinsiemi
while AAnon forma un albero di copertura do
return
Esempio 1: commenti
u
Commenti
u
✦
✦
Problemi
trova un arco sicuro [u, v]
✦
u
Quali
A sonoAle[scelte
{[u,possibili?
v]}
u
return A
subsets(integer[ ] S, integer n, integer i)
Non c'è pruning. Tutto lo spazio possibile viene esplorato.
Ma questo avviene per definizione
In che ordine vengono stampati gli insiemi?
E' possibile pensare ad una soluzione iterativa, ad-hoc?
(non-backtracking)
S ET C
iif(i  n, {0, 1}, ;)
foreach
c 2 C ]do
subsets
(boolean[
S, integer n, integer i)
S[i]
c
S ET C
iif(i  n, {true, false}, ;)
if i = n then
foreach c 2 C do
(S, n)
S[i] processSolution
c
subsets
if
i = n (S,
thenn, i + 1)
processSolution(S, n)
© Alberto Montresor
subsets(S, n, i + 1)
13
Esempio 1: versione iterativa
© Alberto Montresor
14
Esempio 2
subsets(integer n)
for j
0 to 2n 1 do
subsets(integer n)
print ’{’
for jfor i0 to 02nto n1 do1 do
print ’{’
if (j and 2i ) 6= 0 then
for i
0 to n i 1 do
print
✦
Problema
✦
✦
Versione iterativa
✦
if (j and 2i ) 6= 0 then
print ’}’
print i
✦
Qual è il costo?
Soluzione basata su backtracking
✦
print ’}’
Elencare tutti i sottoinsiemi di dimensione k di un insieme S
Possiamo potare?
subsets(integer[ ] S, integer n, integer k, integer i)
subsets
] S,n,integer
S ET C(boolean[
iif(i 
{0, 1},n,
;)integer k, integer i)
foreach
2(iCdo
S ET C c iif
n, {true, false}, ;)
S[i] c 2 cC do
foreach
S[i]
if i = nc then
© Alberto Montresor
15
© Alberto Montresor
16
subsets(S, n, k, i + 1, count)
Esempio
2: 1° tentativo
count
count S[i])
Esempio 2:essere
2° tentativo
potrebbe
la seguente:
subsets(integer[ ] S, integer n, integer k, integer i)
subsets(integer[ ] S, integer n, integer k, integer i, integer count)
Questo algoritmo, invocato da subsets(S, n, k, 1, 0), genera tutti i sottoinsiemi di un insieS ET
C
iif(i
 n, {0,
1}, ;)
iif(i  n, {0, 1}, ;)
me di
n elementi,
e invoca
processSolution() sui soli sottoinsiemi di k elementi. A tal scopo, S ET C
foreachcount
c 2 conta
C doquanti elementi sono presenti nel sottoinsieme corrente. Il costo della foreach c 2 C do
la variabile
S[i]
c
S[i]
c
procedura
è ovviamente
O(2n ).
i = n(Albero
then delle scelte per sottoinsieme). L’enumerazione
Esempioif16.14
visualizzata
Qualpuò
è il essere
problema?
integer
count
0
come la visita in profondità di un albero di scelte, dove ad ogni nodo corrisponde una soluzione
for jno). 1Nel
to caso
n dodi subsets(), l’albero è binario e completo, con tutte le sue
(parziale oppure
count
+ S[j]
foglie al livello n.
La figuracount
Fig. 16.5
mostra l’albero con n = 4, k = 2. I sottoinsiemi con
esattamente kif =
2 nodi
evidenziati in grigio.
count
=sono
k then
(S, n) completamente l’albero. Ci sono tanti casi in cui
In realtà, nonprocessSolution
è necessario analizzare
count
count + S[i]
if i = n and count = k then
processSolution(S, n)
subsets(S, n, k, i + 1, count)
count
count S[i]
Qual è il problema?
una soluzione
parziale
potrà
subsets
(S, n, non
k, i +
1) mai generare una soluzione ammissibile: ad esempio, se
un insieme ha già k elementi, oppure se le scelte ancora da prendere non sono sufficienti a Questo algoritmo, invocato da subsets(S, n, k, 1, 0), genera tutti i sotto
ottenere un insieme di k elementi. In questo caso, si dice che l’albero di decisione viene
me di n elementi, e invoca processSolution() sui soli sottoinsiemi di k elem
“potato” (in inglese pruned): i rami che non possono generare (ulteriori) soluzioni possibili
la variabile count conta quanti elementi sono presenti nel sottoinsieme corr
9
17
18
vengono
© Alberto
Montresor ignorati.
© Alberto Montresor
n
procedura
è
ovviamente
O(2
).
Il nostro algoritmo può essere semplicemente modificato cambiando l’insieme delle scelte,
Esempio
2: Tentativo
corretto
Esempio 2: vantaggi
e modificando
la condizione
di ammissibilità, ma il tempo resta comunque O(2n ).
Esempio
16.6 (Albero delle scelte per sottoinsieme). L’enumerazione può
come la visita in profondità di un albero di scelte, dove ad ogni nodo corrisp
(parziale oppure no). Nel caso di subsets(), l’albero è binario e comple
foglie al livello n. La figura Fig. 16.5 mostra l’albero con n = 4, k = 2.
esattamente k = 2 nodi sono evidenziati in grigio.
subsets(integer[ ] S, integer n, integer k, integer i, integer count)
S ET C = iif(count < k and count + (n
foreach c 2 C do
S[i]
c
count
count + S[i]
if count = k then
processSolution(S, i)
else
subsets(S, n, k, i + 1, count)
count
count S[i]
© Alberto Montresor
i + 1)
k, {0, 1}, ;)
In realtà, non è necessario analizzare completamente l’albero. Ci son
una soluzione parziale non potrà mai generare una soluzione ammissibi
un insieme ha già k elementi, oppure se le scelte ancora da prendere non
ottenere un insieme di k elementi. In questo caso, si dice che l’albero
“potato” (in inglese pruned): i rami che non possono generare (ulteriori)
vengono ignorati.
Il nostro algoritmo può essere semplicemente modificato cambiando l’in
e modificando la condizione di ammissibilità, ma il tempo resta comunque
19
subsets(integer[ ] S, integer n, integer k, integer i, integer count)
© Alberto Montresor
20
Esempio 2: sommario
✦
Esempio 3
Cosa abbiamo imparato?
✦
u
“Specializzando” l'algoritmo generico, possiamo ottenere una versione più
efficiente
u
u
✦
Versione efficiente per
✦
valori di k “piccoli” (vicini a 1)
✦
valori di k “grandi” (vicini a n)
✦
Miglioramento solo parziale verso n/2
✦
E' difficile ottenere la stessa efficienza con un algoritmo iterativo
© Alberto Montresor
✦
✦
foreach c ⌅ A do
S[i] ⇤ c
A.remove(c)
if A.isEmpty() then processSolution(S, n)
permutations(A, n, S, i + 1)
A.insert(c)
21
Idea: ci sono
n2 caselle16.7
dove piazzare
una ne
regina
Soluzione
Esercizio
Una regina
può minacciare un’altra se si trovano
riga, colonna o diagonale. Data una scacchiera n ⇥ n, è possibile rappresentare la
✦ Algoritmo:
vari modi,2 molti dei quali poco efficienti. Ad esempio,
✦
Il problema classico, con n=8, è stato studiato da Gauss (fra gli altri)
S[1..n ] array binario
S[i] = true ⇒ “regina in S[i]”
–✦ secontrollo
l’insieme
delle possibili scelte
soluzione
se i = èn2metto / non metto una regina in una partico
la soluzione è rappresentata da una matrice n ⇥ n di booleani e la dimensione
✦ choices(S, n, i)
{ true, false} 2
da analizzare (salvo potature) è pari a 2n ;
✦
se la nuova
minacciain
una
– sepruning
l’insieme delle possibili verifica
scelte contiene
le regina
n2 posizioni
cui una delle n
delle regine esistenti, nel qual caso restituisce { }
essere collocata, la soluzione è rappresentata da un vettore di n interi in {1, .
✦ # soluzioni per n=8
264 ~ 1.84 · (salvo
1019 potature) è pari a (n2 )n .
dimensione dello spazio da analizzare
Metodo
Partiamo dall'approccio più stupido,
e mano a mano raffiniamo la soluzione.
✦
© Alberto Montresor
22
Soluzione Esercizio 16.5 La soluzione è simile a quella vista per le permu
nl’unico
Regineaccorgimento che l’elemento c scelto nel ciclo foreach deve essere diverso
© Alberto Montresor
✦
Commenti storici:
✦
daRispetto
A l’elemento
scelto,precedente:
e lo reinseriamo al momento di fare un passo di backtrack.
al problema
iniziale
è permutations(A, n, S, 1).
u
permutations(S ET A, integer n, I TEM[ ] S, integer i)
Problema: posizionare n regine in una scacchiera n·n, in modo tale che nessuna
regina ne “minacci” un'altra.
✦
Stampa di tutte le permutazioni di un insieme A
L'insieme dei candidati dipende dalla soluzione parziale corrente
n Regine
✦
Problema 16. BACKTRACK
CAPITOLO
23
Commenti:
Queste
dimensioni sono molto grandi; ma è possibile semplificare le cose notando c
✦ Forse abbiamo un problema di rappresentazione?
deve
contenere esattamente una regina, e lo stesso vale per le colonne. Quindi,
di ✦ognuna
n molto
reginesparsa
può essere rappresentata da un numero in {1, . . . , n},
Matricedelle
binaria
delle posizioni deve essere una permutazione di tale insieme. Lo spazio da analizz
grande n!. Per completare la soluzione, lo spazio deve essere ulteriormente pot
© Alberto Montresor
24
n Regine
n regine
✦
Idea: Dobbiamo piazzare n regine, ci sono n2 caselle
✦
Algoritmo
✦
✦
S[1..n] coordinate in 1..n2
S[i] coordinata della regina i
✦
controllo soluzione
se i = n
✦
choices(S, n, i)
{ 1 … n2 }
✦
pruning
ritorna il sottoinsieme di mosse legali
✦
# soluzioni per n=8
(n2)n = 648 = 248 ~ 2.81 · 1014
✦
✦
✦
C'è un miglioramento, ma lo spazio è ancora grande...
✦
Problema: una soluzione “1-7-....” come si distingue da “7-1-....” ?
✦
25
S[1..n] coordinate in 1..n2
S[i] coordinata della regina i
✦
controllo soluzione
se i = n
✦
choices(S, n, i)
{ 1 … n2 }
✦
pruning
ritorna posizioni legali, S[i-1] < S[i]
✦
# soluzioni per n=8
(n2)n / n! = 648 / 40320 ~ 6.98 · 109
✦
Algoritmo
S[1..n] valori in 1..n
S[i] colonna della regina i, dove riga = i
controllo soluzione
se i = n
✦
choices(S, n, i)
{1 … n }
✦
pruning
ritorna le colonne “legali”
✦
# soluzioni per n=8
nn = 88 ~ 1.67 · 107
✦
Ottimo, abbiamo ridotto molto, ma si può ancora fare qualcosa
26
n regine
Idea: ogni riga della scacchiera deve contenere esattamente una regina. Infatti
non ne può contenere 2 o più, e se ne contenesse 0 un'altra riga dovrebbe
contenerne 2
✦
Commenti:
© Alberto Montresor
✦
✦
✦
✦
n regine
✦
Algoritmo
Commenti
© Alberto Montresor
✦
Idea: la regina i non può stare in una casella “precedente” alla regina i-1 (S[i-1] < S[i])
Idea: anche ogni colonna deve contenere esattamente una regina;
i numeri 1..n devono apparire in S[1..n] come permutazione
Algoritmo
✦
Modifichiamo l'algoritmo delle permutazioni per verificare anche le diagonali
✦
# soluzioni per n=8
✦
# soluzioni effettivamente visitate: 15720
n! = 40320 ~ 4.03 · 104
Commenti
✦
Quasi alla fine
© Alberto Montresor
27
© Alberto Montresor
28
n regine
✦
Giro di cavallo
Non è la soluzione migliore per ottenere una soluzione
✦
Minimum-conflicts heuristic
✦
✦
✦
✦
✦
✦
Problema:
✦
Si muove il pezzo con il più grande numero di conflitti nella casella delle
stessa colonna con il numero minimo di conflitti
Per n=106, circa 50 passi in media
✦
Assume che la soluzione iniziale sia “ragionevolmente buona”.
✦ Ogni regina viene messa nella colonna che ha il numero minimo di
conflitti con le regine già sulla scacchiera
Si consideri ancora una scacchiera n·n; lo scopo è trovare un “giro di cavallo”,
ovvero un percorso di mosse valide del cavallo in modo che ogni casella venga
visitata al più una volta
Soluzione:
✦
Tramite backtrack
Al contrario, se tutte le regine sono sulla stessa riga, ci vogliono almeno n-1
spostamenti
Questo algoritmo non garantisce che la terminazione sia sempre corretta
10
29
© Alberto Montresor
Giro di cavallo
✦
Soluzione
✦
✦
✦
✦
Matrice n·n contenente le cui celle contengono
✦
0
se la cella non è mai stata visitata
✦
i
se la cella è stata visitata al passo i
Alg
10
10
Algoritmi e Strutture di Dati
Algoritmi e Strutture di Dati
boolean cavallo(integer[ ][ ] S, integer i, integer x, integer y)30
S ET C ⇤ mosse(S, x, y)
foreach c ⌅ C do
Giro di cavallo
cavallo(boolean[ ][ ] S, integer i, integer x, integer y)S[x, y] ⇤ i
if i = 81 then
S ET C cavallo
⇥ mosse
(S, x,][y)] S, integer i, integer x, integerprocessSolution
boolean
(integer[
y)
(S)
foreach c ⇤ C do
return
true
S ET C
mosse(S, x, y)
S[x, y] ⇥ i
if cavallo(S, i + 1, x + mx [c], y + my [c]) then
foreach c 2 C do
return true;
if
i =y]81 then
S[x,
i
processSolution(S)
return false
© Alberto Montresor
if i = 64 then
return
true (S)
processSolution
if cavallo
(S,
i + 1, x + mx [c], y + my [c]) then
return true
return true;
if cavallo(S, i + 1, x + mx [c], y + my [c]) thenmx ⇤ { 1, +1, +2, +2, +1, 1, 2, 2}
return true;
return false
my ⇤ { 2, 2, 1, +1, +2, +2, +1, 1}
S[x, y]
0
return false
# soluzioni: 64! ~ 1089
Ma: ad ogni passo ho al massimo 8 caselle possibili, quindi ne visito al più 864 ~1057
mx ⇥ { 1, +1, +2, +2, +1, 1, 2, 2}
my ⇥ { 2, 2, 1, +1, +2, +2, +1, 1}
In realtà, grazie al pruning ne visito molto meno
mx
my
{ 1, +1, +2, +2, +1, 1, 2, 2}
{ 2, 2, 1, +1, +2, +2, +1, 1}
S ET mosse(integer[ ][ ] S, integer x, integer y)
S ET C ⇤ Set()
for integer i ⇤ 1 to 8 do
nx ⇤ x + mx [i]
ny ⇤ y + my [i]
if 1 ⇥ nx ⇥ 8 and 1 ⇥ ny ⇥ 8 and S[nx , ny ] = 0 then
C.insert(i)
return C
© Alberto Montresor
31
S ET mosse(integer[ ][ ] S, integer x, integer y)
S ET C
Set()
for
integer
i
1 to 8 do
© Alberto Montresor
32
sto capitolo, vediamo un gioco matematico che può essere risolto agil5 6 sud7 3
k. Nel
Sudoku è data una tabella formata da 9 righe e29 colonne,
Sudoku
3 con 2 1
di 3 righe e 3 colonne ciascuna, dove alcune caselle sono 8riempite
9 1 e 97
e 9. ✦Si“Suuji
devonowa
riempire
le caselle
vuote con numeri compresi tra
dokushin
ni
kagiru”
Algoritmi
3 7tutti i numeri
8tra e Strutture
9 di2Dati
ga,302
ciascuna colonna e ciascuna sottotabella contenga
la tabella in ingressola soluzione è:
inizializzato a tutti zero e x, y è una posizione qualsiasi. Al termine, se la risposta è true, la
2 5
9
7 6
2 5 3 8 9 1 4 7 6
matrice A contiene il percorso del cavallo.
8 9 7 2 6 4
2
4
4 1 x, 5integer
7 y)
3
1 5(integer[3][ ] A,9 integer n, integer6 k, integer
boolean cavallo
3
9
1
2
5
8
A[x,8y] ⇤
7 8 9 4 3 5 2
9 k 4
5 2 6
2
if
k
=
n
then
return
true
1 3 6 7 2 9 8
1
2
4
for 2
integer
i
⇤
1
to
8
do
4 2 5 6 1 8 7
5 6
7 3
nx ⇤ x + X[i]
9 6 8 3 5 2 1
8 y +3Y [i] 2 1
ny ⇤
9 ny]4 = 70 then
6
if nx ⇥ 19and nx 7 n and ny ⇥ 1 and ny5 n1and2A[nx,
3 true
7 4 1 8 6 5
3 7 if cavallo(A,
8 n, k + 1, nx,9ny)2then return
6
5
3
1
4
9
4
8
9
7
3
2
A[x, y] = 0
return false
2 5 3
8 9 7
© Alberto Montresor
6 4 1
processSolution(S, n)
return(partrue
L’algoritmo risolutivo è mostrato con la procedura sudoku(). La tabella del Sudoku
if sudoku(S, i + 1) then return true
interi, presa in
8 zialmente
9 1 inizializzata)
4 7 6 viene memorizzata in una matrice S[0 . . . 8][0 . . . 8] di
S[x, y]
old
2 6 4 3 1 5
return false
33
© Alberto Montresor
5 7 3 9 2 8
1 3 6 7 2 9 8 5 4
boolean
4 2 check
5 (integer[
6 1 ][8] S, 7integer
3 x,9 integer y, integer c)
9for 6integer
8 j 3⇤ 05to 8 2do 1 4 7
5 1if S[x,
2 j] =
9 c then
4 7return
6 false
8 3
if S[j, y] = c then return false
7
4
boolean sudoku(integer[ ][ ] S, integer i)
S ET C
Set()
integer x
i mod 9
integer y
bi/9c
if i  80 then
if S[x, y] 6= 0 then
C.insert(S[x, y])
else
for integer c
1 to 9 do
if check(S, x, y, c) then C.insert(c)
integer old
S[x, y]
foreach c 2 C do
S[x, y]
c
if i = 80 then
Soluzione Esercizio 16.9 La procedura check(S, x, y, c) controlla che l’aggiunta del numero
7 casella
8 9 S[x,
4 y] 3non sia
5 in2contrasto
6 1con i valori già presenti.
c Sudoku
nella
3
{1, 2, . . . , n2 }, dove il Sudoku classico corrisponde a n = 3. In questo caso il costo della
procedura
Sudokuè superpolinomiale.
1
8
6
5
9
2
34
Generazione labirinti
16.5 Esercizi
u
Problemi
Esercizio 16.1 (Angoli ↵). Se dx e dy sono le differenze tra le ascisse e le ordinate dei punti
pi e p1u, calcolare
l’angolo un
↵ =
tan 1 dy/dx
la funzione
di libreria per l’arcotangente
Come generare
labirinto
in unacon
scacchiera
n·n?
richiede
parecchio tempo. Dato che ↵ serve solo nella fase di ordinamento dei punti dell’algou
Come uscire da un labirinto?
ritmo di Graham, si può utilizzare una quantità diversa dall’angolo ↵, più veloce da calcolare
euche produca lo stesso ordinamento. Si dimostri che dy/(dy + dx) è adatta allo scopo e si
% Controllo sulla colonna
% Controllo sulla riga
integer bx ⇤ ⌅x/3⇧
vo è mostrato
la procedura sudoku(). La tabella del Sudoku (parinteger bcon
y ⇤ ⌅y/3⇧
viene for
memorizzata
in 0una
S[0 . . . 8][0 . . . 8] di interi, presa in
integer ix ⇤
to 2matrice
do
for integer iy ⇤ 0 to 2 do
% Controllo sulla sottotabella
if S[bx · 3 + ix , by · 3 + iy ] = c then return false
Esempio:
return true
© Alberto Montresor
35
© Alberto Montresor
36
x y b x = 0, dalla quale è possibile ricavare b x se sono date
x ottenendo y x
Inviluppo
le
coordinateconvesso
di un punto p = (xp , yp ) che appartiene alla retta: b x = yp x xp y .
Consideriamo uno dei due semipiani, per esempio quello dato dalla disequazione
Un ultimo puzzle
✦
Problema
✦
✦
✦
✦
Si consideri ancora una scacchiera n·n, con n=2k
Trovare un algoritmo che trovi una possibile
ricopertura della scacchiera
✦
284
37
© Alberto Montresor
Inviluppo convesso
?
Un algoritmo banale ma inefficiente
✦
✦
Un poligono può essere rappresentato per mezzo dei suoi spigoli
a)
b) passa per una coppia
c)
d) che divide il piano in due
Si consideri
la retta che
di punti i,j,
semipiani chiusi
Figura 16.1: a) Un poligono non convesso; b) un poligono convesso; c) un insieme di punti; d) il loro
Se tutti i rimanenti n-2 punti stanno dalla stessa parte, allora lo spigolo
inviluppo ✦convesso.
Sij fa parte
dell’inviluppo convesso
✦
i
a) OK
x
xq
x
+ xp
y
⇤ 0.
y
yp
x
+ xp
y
⇤ 0,
Dati n punti p1, . . . , p(yn qnel ypiano,
con
3,pAlgoritmi
più piccolo
(xqn ≥ x
)trovare
eilStrutture
di Datipoligono
p) x
y ⇤ 0.
convesso che li contiene tutti
Pertanto, per ogni punto q che appartiene a tale semipiano, la precedente quantità (yq
yp ) x (xq xp ) y deve essere negativa o nulla, mentre per ogni punto q appartenente
all’altro semipiano la stessa quantità deve essere positiva o nulla. Ne consegue che due punti
q e q stanno dalla stessa parte se le rispettive quantità hanno lo stesso segno, ovvero se il loro
? o nullo.
prodotto è positivo
Consideriamo adesso l’operazione stessaparte(), che prende in input una retta (rappresentata da due punti p1 e p2 ) e due punti p e q, e restituisce true se essi stanno dalla stessa parte
rispetto alla retta (incluso il caso in cui almeno uno dei punti giaccia su di essa), e false se stana)
b)
c)
d)
no da parti opposte rispetto ad essa. I punti sono realizzati come record contenenti due campi
reali
x ea) y.
unanonretta
individuata
dai due
punti c)p1uned
p2 , edidue
punti
p e q, la funzione
Figura
16.1:
Un Dati
poligono
convesso;
b) un poligono
convesso;
insieme
punti;
d) il loro
© Alberto
Montresor
stessaparte
inviluppo
convesso.(p1 , p2 , p, q) verifica in tempo O(1) se le quantità discusse precedentemente hanno
lo
stesso
segno,
cioè se il loro prodotto è non negativo.
Inviluppo
convesso
38
boolean stessaparte(POINT p1 , POINT p2 , POINT p, POINT q)
real dx ⇧ p2 .x p1 .x
real dy ⇧ p2 .y p1 .y
i
real dx1 ⇧ p.x p1 .x
real dy1 ⇧ p.y p1 .y
S ij
real dx2 ⇧ q.x p2 .x
real dyi2 ⇧ q.y p2 .y
ij
return ((dx ·Sdy
dy · dx2 ) ⌅ 0)
j dy · dx1 ) · (dx · dy2
j
1
se i rimanenti n
S ij
© Alberto Montresor
yq
yp
y
b) KO
Figura 16.2:
a) Il segmento
Sij funzione,
è uno spigoloè dell’inviluppo
convesso;scrivere
b) il segmento
Sij non è unoper
Utilizzando
questa
un facile esercizio
una procedura
spigolo
dell’inviluppo
convesso.
viluppo convesso. Infatti, fissati due punti qualsiasi p e p , cioè un segmento S
i
j
x
INVILUPPO CONVESSO (CONVEX HULL)
a) OK
S ij
x
dalla quale segue che
Algoritmi e Strutture di Dati
✦
y
Un poligono nel piano bidimensionale è convesso se ogni segmento di retta che
Se un punto
q = (xdue
) appartiene
a tale semipiano,
allora deve
valere stesso, incluso il
congiunge
del poligono
sta interamente
nel poligono
q , yqpunti
bordo.
✦
Qualsiasi scacchiera di questo tipo con una cella rimossa può essere ricoperta da
triomini a forma di L
284
Definizione
j
b) KO
39
trovare l’ini
j
ij , per vedere
2 punti stanno dalla stessa parte rispetto alla retta che passa per Sij basta
Un algoritmo banale per trovare l’inviluppo convesso si basa sulla seguente caratterizzazione della soluzione. Un poligono può essere rappresentato per mezzo dei suoi spigoli, cioè
dei segmenti di retta che giacciono sul suo bordo. Si consideri allora un generico segmento
Sij che unisce due punti distinti pi e pj , 1 ⇥ i < j ⇥ n, e si consideri la retta che include
Sij . Tale retta divide il piano in due parti (dette semipiani chiusi, ognuno dei quali comprende
anche
laMontresor
retta). Se tutti i rimanenti n 2 punti stanno dalla stessa parte rispetto alla retta che
© Alberto
40
ramente termine, al più, quando si arriva a considerare la retta che passa per p1 e p2 , poiché
tali punti sono
vertici dell’inviluppo convesso di p1 , . . . , pi per ogni i, 2 ⇥ i ⇥ n. Queste
Algoritmo
di Graham
osservazioni suggeriscono il seguente algoritmo informale:
Algoritmo di Graham
✦
Algoritmo di Graham (1972)
✦
✦
S ET graham(POINT p1 , . . . , pn )
{ trova il punto “più basso a destra” e scambialo con p1 }
{ riordina p2 , . . . pn in base all’angolo formato rispetto all’asse orizzontale
quando sono connessi con p1 }
{ elimina gli eventuali punti “allineati” tranne i più lontani da p1 , aggiornando n }
{ inserisci p1 nell’inviluppo “corrente” e , se n ⇤ 2, inserisci anche p2 }
for integer i ⌅ 3 to n do
{ siano ph e pj , con h < j = i 1, gli ultimi due vertici dell’inviluppo “corrente” }
{ scandisci “a ritroso” i punti nell’inviluppo “corrente” ed elimina pj se
stessaparte(pj , ph , p1 , pi ) = false;
termina tale scansione se pj non deve essere eliminato }
{ aggiungi pi all’inviluppo “corrente” }
return inviluppo “corrente”
Il punto con ordinata minima fa parte dell’inviluppo convesso
Si ordinano i punti in base all’angolo formato dalla retta passante per il punto con
ordinata minima e la retta orizzontale
286
Algoritmi e Strutture di Dati
12
8
10
9
13
11
14
5
6
7
4
3
15
2
α
41
1
© Alberto Montresor
288 Figura 16.3: Ordinamento dei punti in base all’angolo
Algoritmo di Graham
Strutture diquando
Dati sono
formatoAlgoritmi
con l’assee orizzontale
connessi al punto p1 .
12
12
tenere fisso uno di tali punti, per
esempio p, ed eseguire stessaparte
(pi , pj , p, q) per n 3
8
8
10
10
volte al variare di q per ciascuno dei rimanenti n 3 punti. Pertanto verificare se un seg9
9
13
mento Sij è uno13spigolo dell’inviluppo
convesso
richiede O(n) tempo.
Poiché ci sono in
5
5
6
14
6
14
11
11
7 ij distinti, si possono individuare
totale n(n 1)/2 segmenti S
tutti gli spigoli dell’inviluppo
7
4
4
3
convesso con complessità O(n3 ).3
15
2
1
16.1.1 Algoritmo di
i = 3Graham
© Alberto Montresor
2
1
i=4
Figura 16.4: Esecuzione dell’algoritmo di Graham fino ad i = 8.
42
for integer i ⌅ 2 to n do
if p[i].y < p[min].y then min ⌅ i
p[1] ⇧ p[min]
{ riordina p[2, . . . n] in base all’angolo formato rispetto all’asse orizzontale
quando sono connessi con p[1] }
{ elimina gli eventuali punti “allineati” tranne i più lontani da p1 , aggiornando n }
integer j ⌅ iif(n ⇤ 2, 2, 1)
for integer i ⌅ 3 to n do
while not stessaparte(p[j], p[j 1], p[1], p[i]) do
j⌅j 1
j ⌅j+1
p[j] ⇧ p[i]
return j
15
L’algoritmo appena
illustrato non è molto efficiente,
perché esamina gli n punti in modo
12
12
8 esame sistematico dei
troppo caotico. Vediamo10come un
10 punti,8 basato su un ordinamento ed
9
un backtrack, permetta
di
progettare
un
algoritmo
più
efficiente,
proposto da Graham (1972).
9
13
13
5
6
14
14 si osservi
Innanzitutto,
che in
minima è un vertice
11quello con ordinata
6 un insieme di punti,
11
5
4
7
4
dell’inviluppo convesso. Inoltre, tracciando una retta orizzontale in tale punto e facendola ruo7
3
3 antiorario si incontrano in sequenza
tare intorno al 15
punto stesso in senso
tutti i rimanenti punti
15
2
2 nella Fig. 16.3, i punti
(uno per volta, se non ci sono due punti “allineati”). Come illustrato
1
si possono rinumerare in base
all’angolo che la retta 1passante per ciascun punto pi e quello
i =5
i=8
di ordinata minima (il punto p1 ) forma con l’asse orizzontale che passa per p1 . Se ci sono più
punti con ordinata minima, allora quelli con ascissa minima e massima, sono vertici dell’in-
La dimostrazione di correttezza dell’algoritmo segue facilmente per induzione su i in acCAPITOLO 16. BACKTRACK
289
cordo alle considerazioni fatte precedentemente. La fase di backtrack avviene all’interno del
ciclo for, dove si eliminano i punti che sono vertici dell’inviluppo convesso di p1 , . . . , pi 1 ,
è invece
n), perché
fase di ordinamento dei punti.
ma cheO(n
non log
lo sono
più perdominata
p1 , . . . , pidalla
.
© Alberto Montresor
Realizzando l’insieme di punti con un vettore p di n elementi di tipo POINT, l’algoritmo di
Esempio
(Algoritmo
Graham).
dell’algoritmo
Grahamp[1
è .illustrata
Graham
può16.3
essere
realizzatodicon
una pila L’esecuzione
simulata direttamente
con ladi
porzione
. . j] del
Algoritmo
di Graham
nella Fig.
16.4,
dove
evidenziati
gli spigoli
vettore,
scambiando
trasono
loro punti
del vettore
stesso.dell’inviluppo “corrente” (in nero), le rette
(tratteggiate), ed i punti che vengono eliminati ad ogni iterazione (con la croce), fino ad i = 8.
Si
noti che
l’aggiunta
del
p8 provoca l’eliminazione di due punti (p7 e p6 ), ma che ogni
integer
graham
(POINT
[ ] punto
p)
punto
è inserito
integer
min ⌅solo
1 una volta ed è eliminato al più una volta.
43
La testa della pila è individuata dall’indice j. La cancellazione di p[j] è effettuata semplicemente decrementando l’indice j all’interno del while. L’indice j è incrementato all’uscita
effettuare
il successivo inserimento di p[i], che avviene scambiando gli elementi p[j] e p[i],
©per
Alberto
Montresor
44
Algoritmo di Graham
✦
Complessità
✦
O(n log n), perché dominato dalla fase di ordinamento degli angoli
© Alberto Montresor
45
Fly UP