java-forum.org - Java programmieren aus Leidenschaft
Java 6 Einstieg und professioneller Einsatz
Alter Preis: 34,90 EUR
Jetzt: 0,00 EUR

zzgl. Versandkosten

Zurück   java-forum.org - Java programmieren aus Leidenschaft > Blogs > nrg

Bewerten

Probieren geht über Studieren: Backtracking

"Probieren geht über Studieren: Backtracking" bei Mister Wong speichern "Probieren geht über Studieren: Backtracking" bei YiGG.de speichern "Probieren geht über Studieren: Backtracking" bei Google speichern "Probieren geht über Studieren: Backtracking" bei del.icio.us speichern
Veröffentlicht: 13.06.2011 um 21:33 von nrg
Aktualisiert: 14.06.2011 um 13:38 von nrg (Sourcen als ZIP hinzugefügt)

Nachdem Backtracking häufiger Thema hier im Forum ist, dachte ich mir, ich schreibe dazu mal einen kurzen Blog.

Backtracking geht nach dem Versuch-und-Irrtums-Prinzip vor, was nichts anderes bedeutet als "Probiere solange sämtliche Möglichkeiten aus, bis das Problem gelöst ist“. Anwendungsgebiete sind zum Beispiel das Damenproblem, Sudoku, Rucksackproblem oder Springerproblem.

Am besten lässt sich der Algorithmus rekursiv implementieren und folgt auch zumeist einem bestimmten Muster, das ich mal eben in Form von Java/Pseudocode veranschaulichen möchte:

Java Code: Quelltext in neuem Fenster öffnen
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class XYProblem {
 
    gelöst = false;
 
    public löse() {
        löse(0);
    }
 
    private löse(aktuellePosition) {
        if (aktuellePosition > letztePosition) gelöst;
 
        while (Möglichkeit vorhanden) {
            setzen;
            löse(akuellePosition + 1);
        }
        if (ungelöst) zurücksetzen;
    }
}

In der privaten Methode löse wird nur "gesetzt", wenn mindestens eine Möglichkeit an dieser Stelle existiert. Für den Fall, dass es keine Möglichkeit gibt, erfolgt auch kein rekursiver Aufruf und die Methode endet. Dadurch bewegt man sich logischerweise eine Position (zur aufrufenden Methode) zurück und kann dort die nächste Möglichkeit ausprobieren. Dieses Spielchen wird solange wiederholt, bis das Problem gelöst ist oder die Methode ohne Lösung verlassen wird. Für letzteres gilt das Problem als unlösbar.

Um den Algorithmus auch praxisnah zu zeigen und zu analysieren habe ich mich für einen simplen SudokuSolver entschieden. Dieser akzeptiert nur die gängigen 3x3 Sudokus, kann aber auch ohne weiteres universeller geschrieben werden.

SudukoSolver.java
Java Code: Quelltext in neuem Fenster öffnen
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
 
/**
 * 3x3 sudokusolver
 * @author nrg
 */
public class SudokuSolver {
 
    private int[][] sudoku;
    private boolean solved;
 
    /**
     * Constructs a sudokusolver. Note that there is no intialization since yet.
     * You have to read the sudoku from file or use the set-method before
     * invoking the solve-method.
     */
    public SudokuSolver() {
        this.solved = false;
    }
 
    /**
     * Constructs a sudokusolver
     * @param sudoku 2d array represent the soduko, 0 expected for empty fields
     */
    public SudokuSolver(int[][] sudoku) {
        setSudoku(sudoku);
    }
 
    /**
     * Sets sudoku
     * @param sudoku 2d array represent the soduko, 0 expected for empty fields
     */
    public void setSudoku(int[][] sudoku) {
        this.solved = false;
        this.sudoku = sudoku;
    }
 
    /**
     * Returns the current sate of the sudoku
     * @return 2d array represent the soduko
     */
    public int[][] getSudoku() {
        return this.sudoku;
    }
 
    /**
     * Reads a suduko from a file, space as delimiter and point for empty fields are expected
     * 
     * for example
     * 
     * 2 . 7 . 8 6 . . .
     * . 5 . 7 1 . . 8
     * . 9 8 . 4 . 1 7 6
     * . . 4 . . 9 . . 7
     * . 8 . 7 . 5 . 3 .
     * . . . 4 . . 9 2 5
     * 8 . 3 . . . . 5 2
     * 7 . . . 2 . 3 . .
     * . . . 6 5 . . . 1
     * 
     * represents a 3x3 sudoku
     * 
     * @param file the filepath 
     * @throws IOException
     */
    public void read(String file) throws IOException {
        read(new File(file));
    }
 
    /**
     * Reads a suduko from a file, space as delimiter and point for empty fields is expected
     * for example and further details see {@link SudokuSolver#read(String)}
     * @param file the fileobjekt
     * @throws IOException
     */
    public void read(File file) throws IOException {
        BufferedReader bisr = new BufferedReader(new FileReader(file));
        List<String> lines = new ArrayList<String>();
        String line = null;
        while ((line = bisr.readLine()) != null)
            lines.add(line);
        solved = false;
        sudoku = new int[lines.size()][];
        for (int i = 0; i < lines.size(); i++)
            sudoku[i] = parseLine(lines.get(i).split("\\s"));
    }
 
    /**
     * solve the current given sudoku
     * @return the solve-time in nanoseconds
     * @throws IllegalStateException if sudoku == null
     * @throws UnsolveableSudokuException if the sudoko can not be solved
     * @throws InvalidSudokuException if the sudoko is not 3x3
     */
    public long solve() {
        if (sudoku == null) throw new IllegalStateException("initialize sodoku first");
        
        if (sudoku.length != 9)
            throw new InvalidSudokuException("number of rows must be 9");
        for (int i = 0; i < sudoku.length; i++) {
            if (sudoku[i].length != 9)
                throw new InvalidSudokuException("number of columns in row " + (i+1) + " must be 9");
        }
        
        long start = System.nanoTime();
        if (!solved) solve(0, 0);
        if (!solved) throw new UnsolveableSudokuException("sudoku cannot be solved");
        return System.nanoTime() - start;
    }
 
    /**
     * backtracking recursion
     * @param row current row
     * @param col current col
     */
    private void solve(int row, int col) {
        if (solved = row >= sudoku.length) return;
 
        if (sudoku[row][col] == 0) {
            for (int n : getPossibilities(row, col)) {
                if (solved) break;
                sudoku[row][col] = n;
                solve(nextRow(row, col), nextCol(row, col));
            }
            if (!solved) sudoku[row][col] = 0;
        } else {
            solve(nextRow(row, col), nextCol(row, col));
        }
    }
 
    /**
     * returns the next row
     * @param row current row
     * @param col current col
     * @return next row
     */
    private int nextRow(int row, int col) {
        return col + 1 < sudoku.length ? row : row + 1;
    }
 
    /**
     * returns the next col
     * @param row current row
     * @param col current col
     * @return next col
     */
    private int nextCol(int row, int col) {
        return col + 1 < sudoku.length ? col + 1 : 0;
    }
 
    /**
     * returns all possibilities for the current field
     * @param row current row
     * @param col current col
     * @return all possibilities in a int array
     */
    private int[] getPossibilities(int row, int col) {
        CoatSet cs = new CoatSet(10);
        for (int i = 0; i < sudoku.length; i++) {
            cs.coat(sudoku[row][i]);
            cs.coat(sudoku[i][col]);
        }
        int rowStart = row - row % 3, colStart = col - col % 3;
        for (int r = rowStart; r < rowStart + 3; r++) {
            for (int c = colStart; c < colStart + 3; c++)
                cs.coat(sudoku[r][c]);
        }
        return cs.getUncoated();
    }
 
    /**
     * returns a String representing the sudoku
     * @return String representation of the sudoku
     */
    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        final String lineSep = System.getProperty("line.separator");
        final char space = ' ';
        for (int[] a : sudoku) {
            for (int i = 0; i < a.length; i++) {
                if (i > 0) sb.append(space);
                sb.append(a[i]);
            }
            sb.append(lineSep);
        }
        return sb.toString();
    }
 
    /**
     * private class to coat numbers
     * @author nrg
     */
    private class CoatSet {
 
        private boolean[] set;
        private int uncoated;
 
        /**
         * Constructs a coatset of the given size 
         * @param size size of the coatset
         */
        private CoatSet(int size) {
            this.uncoated = size;
            this.set = new boolean[size];
            for (int i = 0; i < set.length; i++)
                set[i] = true;
        }
 
        /**
         * coats a number
         * @param n the number
         * @throws ArrayIndexOutOfBoundsException if n < 0 or n >= size
         */
        private void coat(int n) {
            if (set[n]) uncoated--;
            set[n] = false;
        }
 
        /**
         * returns all uncoated numbers
         * @return a int-array contains all uncoated numbers
         */
        private int[] getUncoated() {
            int[] array = new int[uncoated];
            for (int i = 1, j = 0; i < set.length; i++)
                if (set[i]) array[j++] = i;
            return array;
        }
    }
 
    /**
     * parses a string-array to a int-array
     * @param a the string array
     * @return the int array
     * @throws InvalidSudokuException if any field != point or field != [1-9]
     */
    private static int[] parseLine(String[] a) {
        int[] array = new int[a.length];
        for (int i = 0; i < a.length; i++) {        
            if (!a[i].matches("[1-9]|\\."))
                throw new InvalidSudokuException("at field " + a[i]);
            array[i] = a[i].equals(".") ? 0 : Integer.parseInt(a[i]);
        }
        return array;
    }
}

SudokuSolverTest.java
Java Code: Quelltext in neuem Fenster öffnen
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import java.io.IOException;
 
public class SudokuSolverTest {
 
    public static void main(String[] args) {    
        SudokuSolver ss = new SudokuSolver(new int[][] {
                {2, 0, 7, 0, 8, 6, 0, 0, 0},
                {0, 0, 5, 0, 7, 1, 0, 0, 8},
                {0, 9, 8, 0, 4, 0, 1, 7, 6},
                {0, 0, 4, 0, 0, 9, 0, 0, 7},
                {0, 8, 0, 7, 0, 5, 0, 3, 0},
                {0, 0, 0, 4, 0, 0, 9, 2, 5},
                {8, 0, 3, 0, 0, 0, 0, 5, 2},
                {7, 0, 0, 0, 2, 0, 3, 0, 0},
                {0, 0, 0, 6, 5, 0, 0, 0, 1},
        });
        
        System.out.println("solved in " + ss.solve()/1000000. + " milliseconds");
        System.out.println(ss);
    }
}

InvalidSudokuException.java
Java Code: Quelltext in neuem Fenster öffnen
1
2
3
4
5
6
7
8
public class InvalidSudokuException extends RuntimeException {
 
    private static final long serialVersionUID = 1L;
 
    public InvalidSudokuException(String msg) {
        super(msg);
    }
}

UnsolveableSudokuException.java
Java Code: Quelltext in neuem Fenster öffnen
1
2
3
4
5
6
7
8
public class UnsolveableSudokuException extends RuntimeException {
 
    private static final long serialVersionUID = 1L;
 
    public UnsolveableSudokuException(String msg) {
        super(msg);
    }
}

Ausgabe
Code:
solved in 2.372598 milliseconds
2 1 7 9 8 6 5 4 3
6 4 5 3 7 1 2 9 8
3 9 8 5 4 2 1 7 6
5 3 4 2 6 9 8 1 7
9 8 2 7 1 5 6 3 4
1 7 6 4 3 8 9 2 5
8 6 3 1 9 7 4 5 2
7 5 1 8 2 4 3 6 9
4 2 9 6 5 3 7 8 1
Zur Analyse des Programmablaufs habe ich mal ein "kleines" GIF erstellt, das einen Mitschnitt vom Debugger in Eclipse zeigt:



Abschließend lässt sich noch sagen, dass diese Vorgehensweiße eher für Computer geeignet ist. Der Mensch selbst könnte beim Lösen von einem Sudoku nur bedingt so vorgehen, weil es einfach zu viele Kombinationen sind, die geprüft werden müssen. Eine logische Vorgehensweiße wäre an der Stelle deutlich schneller aber würde natürlich nie auf die Lösungszeit kommen, die ein Prozessor dafür benötigt .
Aber auch ein Computer kann hiermit an seine Grenzen kommen, weil die Zeit, die benötigt wird um das Problem zu lösen, logischerweise exponentiell zu der "Größe" des Problems anwächst. Das sieht man sehr schnell, wenn man aus dem 8-Damen-Problem ein 100-Damen-Problem macht .
Zu einem java.lang.StackOverflowError sollte es aber in keinem Fall kommen, weil Java einen Stack von über 7300 Methoden zulässt .
Angehängte Dateien
Dateityp: zip SudokuSolver.zip (3,1 KB, 28x aufgerufen)
Kategorie: Kategorielos
Hits 1680 Kommentare 0
« Zurück     Startseite des Blogs     Nächste »
Kommentare 0

Kommentare

 

Alle Zeitangaben in WEZ +1. Es ist jetzt 23:07 Uhr.


Powered by vBulletin® Version 3.8.6 (Deutsch)
Copyright ©2000 - 2013, Jelsoft Enterprises Ltd.
Search Engine Friendly URLs by vBSEO 3.3.2
Thanks for Smilies by smilies.4-user.de