Scaricare la presentazione
La presentazione è in caricamento. Aspetta per favore
PubblicatoElmo Carnevale Modificato 9 anni fa
1
4/5/2015E. Giovannetti -- OI09.1 Olimpiadi di Informatica 2010 Giornate preparatorie Dipartimento di Informatica Università di Torino marzo 2010 6 – Algoritmi di backtracking: il problema delle regine. (versione 05/04/2015)
2
Il problema delle otto (o n) regine. Disporre 8 regine sulla scacchiera 8 8 in modo che nessuna regina ne attacchi un’altra secondo le regole del gioco degli scacchi; cioè in modo che nessuna si trovi nella stessa riga, o nella stessa colonna, o nelle stesse “diagonali” di un’altra. Il problema è generalizzabile in modo banale a n regine su una scacchiera n n : per n 4 il problema non è risolubile, come si vede con ispezione diretta; per ciascun n 4 il problema ammette più soluzioni. 05/04/2015 13.44E. Giovannetti - AlgELab-09-10 - Lez.462
3
Come rappresentare le soluzioni. Poiché non possono esserci due regine nella stessa riga, qualunque disposizione che risolva il problema deve avere una e una sola regina per riga. Allora possiamo disporre la prima regina nella prima riga, la seconda regina nella seconda riga, … la i-esima regina nella i-esima riga, ecc. Una soluzione è quindi semplicemente una permutazione di degli n numeri naturali da 1 a n o da 0 a n-1, rappresentabile per mezzo di un array. Esempio: per la scacchiera 5 5 l’array {0, 2, 4, 1, 3} è la disposizione delle regine nelle caselle: (0,0); (1,2); (2,4); (3,1); (4,3). Naturamente i ruoli delle righe e delle colonne sono perfettamente intercambiabili. 05/04/2015 13.44E. Giovannetti - AlgELab-09-10 - Lez.463
4
L’algoritmo ricorsivo: descrizione molto informale. Per posizionare le regine dall’i-esima riga in avanti: se i è la riga successiva all’ultima, termina con successo; altrimenti cerca una colonna j tale che siano prive di regine la colonna j stessa, la diagonale ascendente passante per la casella (i,j), la diagonale discendente passante per la casella (i,j); posiziona la i-esima regina nella casella (i,j); posiziona le regine dall’ i+1-esima riga in avanti; se tale posizionamento ha successo, termina con successo; altrimenti backtrack: rimuovi la i-esima regina e poi cerca un’altra colonna j che soddisfi alle condizioni di cui sopra, e così via finché non ci sono più colonne da esaminare: in tal caso termina con fallimento. 05/04/2015 13.44E. Giovannetti - AlgELab-09-10 - Lez.464
5
L’algoritmo ricorsivo: descrizione più precisa n = dimensione della tastiera; array indiciati da 0 a n-1 boolean posizionaDallaRiga(int i) { if(i == n) return true; else { for(int j = 0; j < n; j++) { if(colonna j è libera da regine && diagonale ascendente per (i,j) è libera da regine && diagonale discendente per (i,j) è libera da regine) { posiziona l’i-esima regina nella colonna j; if(posizionaDallaRiga(i+1)) return true; else rimuovi l’i-esima regina; continua il ciclo } } fine ciclo for return false; se si sono provate senza successo tutte } le colonne j, si fallisce } 05/04/2015 13.44E. Giovannetti - AlgELab-09-10 - Lez.465
6
La chiamata iniziale Naturalmente la funzione ricorsiva deve essere inizialmente invocata con il valore del primo indice (1 in Pascal, 0 in C): boolean problemaRegine() { return posizionaDallaRiga(0); } La soluzione è rappresentata da una struttura-dati globale; se si definisce una procedura che visualizza la soluzione, il main avrà la forma … problemaRegine(); visualizzaSoluzione(); … 05/04/2015 13.44E. Giovannetti - AlgELab-09-10 - Lez.466
7
Strutture dati convenienti. Per poter effettuare in tempo costante i test sulla presenza di regine nella colonna e nelle due diagonali, conviene tenere tre array di booleani, indiciati rispettivamente dal numero di colonna e dal “numero di diagonale”, dove il k-esimo elemento di ognuno degli array ha il significato: la k-esima colonna, o diagonale in su, o in giù, è senza regine. In una scacchiera n n vi sono: n colonne; 2n diagonali ascendenti; 2n diagonali discendenti. Osserva: le caselle (i, j) di una data diagonale ascendente sono quelle per cui i+j = costante dipendente dalla diagonale; le caselle di una data diagonale discendente sono quelle per cui i-j = costante dipendente dalla diagonale; 05/04/2015 13.44E. Giovannetti - AlgELab-09-10 - Lez.467
8
Strutture dati convenienti: Pascal. Indiciamo, come naturale, righe e colonne da 1 a 8: const n = …; var q: array[1..n] of integer; // posizioni delle regine freeCol: array[1..n] of boolean; Allora: la somma i+j varia da 1+1 a n+n, cioè da 2 a 2n; la differenza i-j varia da 1-n a n-1 (ad es., per n = 8, varia da -7 a +7). Quindi, poiché in Pascal gli array si possono indiciare con un range qualunque: freeDiagRise: array[2..2*n] of boolean; freeDiagFall: array[-n+1..n-1] of boolean; 05/04/2015 13.44E. Giovannetti - AlgELab-09-10 - Lez.468
9
Strutture-dati convenienti: C++ Gli array sono indiciabili solo a partire da 0, quindi: int q[n]; bool freeCol[n]; Poiché gl’indici di riga e di colonna vanno da 0 a n-1: la somma i+j varia da 0 a 2n-2, cioè ha 2n-1 possibili valori, la somma i-j varia da –n+1 a n-1, cioè ha 2n-1 possibili valori. Quindi: bool freeDiagRise[2*n - 1]; bool freeDiagFall[2*n - 1]; Nel codice, per l’accesso all’array delle diagonali discendenti occorrerà “traslare” il range –n+1..n-1 nel range 0..2n-1, sommando n-1 al valore di i-j. 05/04/2015 13.44E. Giovannetti - AlgELab-09-10 - Lez.469
10
Strutture-dati convenienti: nomi corti. Per ragioni di spazio nelle slides successive (o eventualmente per velocità di codifica) usiamo nomi più corti per gli array. In C++: int q[n]; bool c[n]; // colonne libere bool a[2*n - 1]; // diagonali ascendenti libere bool b[2*n - 1]; // diagonali discendenti libere In Pascal: var q: array[1..n] of integer; c: array[1..n] of boolean; a: array[2..2*n] of boolean; b: array[-n+1..n-1] of boolean; 05/04/2015 13.44E. Giovannetti - AlgELab-09-10 - Lez.4610
11
La funzione ricorsiva (C++) bool placeQueens(int i) { if(i == n) return true; for(int j = 0; j < n; j++) { if(c[j] && a[i+j] && b[i-j+n-1]) { q[i] = j; c[j] = a[i+j] = b[i-j+n-1] = false; if(placeQueens(i+1)) return true; else c[j] = a[i+j] = b[i-j+n-1] = true; } return false; } 05/04/2015 13.44E. Giovannetti - AlgELab-09-10 - Lez.4611
12
La funzione top-level void queens() { for(int i = 0; i < n; i++) c[i] = true; for(int i = 0; i < 2*n-1; i++) a[i] = true; for(int i = 0; i < 2*n-1; i++) b[i] = true; placeQueens(0); } 05/04/2015 13.44E. Giovannetti - AlgELab-09-10 - Lez.4612
13
Il ciclo for in Pascal In Pascal l’indiciamento degli array è più immediato:... for j:= 1 to n do begin if (c[j] and a[i+j] and b[i-j]) then begin q[i]:= j; c[j]:= false; a[i+j]:= false; b[i-j]:=false; if placeQueens(i+1) then begin result:= true; exit end else begin c[j]:= true; a[i+j]:= true; b[i-j]:= true end end;... 05/04/2015 13.44E. Giovannetti - AlgELab-09-10 - Lez.4613
14
Il problema delle n regine: tutte le soluzioni. Con una lieve modifica dell’algoritmo si possono generare tutte le soluzioni, cioè tutte le disposizioni di n regine sulla scacchiera n n che soddisfano alle condizioni del problema. Per ottenere ciò, basta non fermarsi quando si trova una soluzione, bensì continuare il backtracking. Non è quindi più necessario che la procedura restituisca un booleano: ogni volta che si trova una soluzione si può sempli- cemente stamparla, oppure aggiungerla ad una struttura-dati (ad es. una matrice) che rappresenta l’insieme delle soluzioni. Se si è interessati anche o solo al numero delle soluzioni, si può introdurre un contatore che conti il numero di soluzioni, da incrementarsi ogni volta che se trova una. 05/04/2015 13.44E. Giovannetti - AlgELab-09-10 - Lez.4614
15
Il problema delle n regine: tutte le soluzioni. void placeQueens(int i) { for(int j = 0; j < n; j++) { if(c[j] && a[i+j] && b[i-j+n-1]) { q[i] = j; c[j] = a[i+j] = b[i-j+n-1] = false; if(i == n-1) { writeSolution(); numSolutions++; } else placeQueens(i+1); c[j] = a[i+j] = b[i-j+n-1] = true; } 05/04/2015 13.44E. Giovannetti - AlgELab-09-10 - Lez.4615
Presentazioni simili
© 2024 SlidePlayer.it Inc.
All rights reserved.