You are not logged in.

  • Login

1

Monday, May 22nd 2006, 3:46pm

Sudoku Algorithmus Backtracking

Zur Benutzung dieses Sudokus Algorithmus'
java -jar <option> <puzzle> <puzzle> <puzzle> <...>

<option> = Option
Option 1) = Zeige Sudoku
Option 2) = Löse Sudoku mit Backtracking
Option 3) = Löse Sudoku mit optimiertem Backtracking
<puzzle> = Dateiname
Die Puzzles werden nacheinander abgearbeitet

Beispielsudokus sind diesem Thread angehängt

Das ADSTool kümmert sich um Zeitenmessung und um das Einlesen der Dateien. Ihr findet es außerdem als Dateianhang

Java Quellcode

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
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
package ads;
/**
 * Autoren: Marco May & Torben Brodt
 */
import fhw.ADSTool;
 
public class SudokuPlay {
	public static void main(String[] args) {
		if(args.length < 2)
			System.out.println("Zur Backtracking Loesung 2 als Paramter uebergeben\nZum intelligenten Loesen eine 3");
 
		ADSTool.resetTime();
		if(args.length > 1)
			for(int i=1; i<args.length; i++) {
				Sudoku feld = new Sudoku(9, args[0], args[i]);
				System.out.println(feld);
				ADSTool.showRTime();
				System.out.println();
			}
	}	
}
 
class Sudoku {
	// Konstante fuer die Anzahl an Blöcken(9er/12er Sudoku) und die Breite einer Box (3 oder 4) 
	public final int n, box_n;
 
	// Zaehlt die Anzahl der erprobten Platzierungen
	public int tries=0;
 
	// Sudoku Feld (noch nicht vorbelegt, da 9^12 moeglich sind)
	public byte[][] feld;
 
	/**
	 * Konstruktor
	 * args[0] = dateiname
	 * args[1] = smart
	 */
	public Sudoku(int n, String what, String filename) {
 
		this.n = n;
		this.box_n = (int)Math.sqrt(n);
		this.feld = new byte[n][n];
 
		//Datei einlesen
		read(filename);
 
		//und loesen
		if(what.equals("2")) {
			solveBacktrack(0,0);
 
		} else if(what.equals("3")) {
			smartSolve();
 
		} else if(what.equals("1")) {
//			belassen fuer einfache Ausgabe
		}
 
	}
 
	/**
	 * Liest ein Raetsel ein
	 */
	public void read(String filename) {
		int i=0, j=0;
 
		try {
			int[] file = ADSTool.readIntArray(filename);
 
			for(int digit : file) {
				if(j == n) {
					j = 0;
					i++;
				}
				feld[i][j] = (byte)digit; 
				j++;
			}
		} catch(Exception e) {
			System.out.println("Bitte mit dem korrekten Dateinamen als Konsolenparameter starten");
			System.exit(0);
		}
	}
 
	/**
	 * Loest das Raetsel durch Backtracking
	 */
	public boolean solveBacktrack(int i, int j) {		
		if(j == n) { 	/* Zeilenende erreicht, 
						 * fange wieder links an,
						 * gehe eine Zeile nach unten
						 * oder breche ab, wenn du ganz fertig bist
						 */
			i++;
			if(i == n)
				return true;
		    j = 0;
		}		
 
		if(feld[i][j] > 0)  // Das Feld ist erfolgreich gesetzt
							// Funktionsaufruf mit naechster Spalte
		    return solveBacktrack(i, j+1);
 
		for(byte a=1; a<n+1; a++) {
		    if(check(i, j, a) == false) {  
				feld[i][j] = a;       
				if(solveBacktrack(i, j+1))
				    return true;
		    }
		}
 
		feld[i][j] = 0; // Der Versuch war wohl nichts
		return false;
	}
 
 
	/**
	 * Prueft ob dieses Feld gesetzt werden kann (ruft dabei 3 Subfunktionen auf)
	 * @return-> Gibt true zurueck, wenn der Wert bereits existiert
	 */
	public boolean check(int i, int j, int value) {
		tries++;
 
//		Fuer die Ausgabe mit SudokuSmart
//		System.out.println(""+i+"/"+j);
 
		if(checkHorizontal(j, value))
			return true;
 
		if(checkVertical(i, value))
			return true;		
 
		if(checkBox(i, j, value))
			return true;
 
		return false;
	}
 
	/**
	 * Prueft ob der Wert bereits in der (horizontalen) SPALTE existiert
	 * @return-> Gibt true zurueck, wenn der Wert bereits existiert
	 */
	public boolean checkHorizontal(int j, int value) {
		for(int a=0; a<n; a++)
		    if(feld[a][j] == value)
		    	return true;
 
		return false;
	}
 
	/**
	 * Prueft ob der Wert bereits in der (vertikalen) REIHE existiert
	 * @return-> Gibt true zurueck, wenn der Wert bereits existiert
	 */
	public boolean checkVertical(int i, int value) {
		for(int a=0; a<n; a++)
		    if(feld[i][a] == value)
		    	return true;
 
		return false;
	}
 
	/**
	 * Prueft ob der Wert bereits in der BOX existiert
	 * @return-> Gibt true zurueck, wenn der Wert bereits existiert
	 */
	public boolean checkBox(int i, int j, int value) {
		// oberes, linkes Eck der Box herausfinden (2|8 zu 0|6)
		int i_start = (int)(i/box_n) * box_n;
		int j_start = (int)(j/box_n) * box_n;
 
		for(int a=i_start; a<i_start+box_n; a++)
		    for(int b=j_start; b<j_start+box_n; b++)
		    	if(feld[a][b] == value)
		    		return true;
 
		return false;
	}
 
	/**
	 * SMART: Loest das Raetsel intelligenter
	 */
	public boolean smartSolve() {
		int[] best = smartChoice();
		int i=best[0], j=best[1];
 
		if(i == -1 && j == -1) //Das Sudoku funktioniert - es gibt keine leeren Felder mehr
			return true;
 
		for(byte a=1; a<n+1; a++) {
//			System.out.print("pruefe wert "+a+" in ");
			if(check(i, j, a) == false) {
				feld[i][j] = a;
				if(smartSolve())
					return true;
			}
		}
 
		feld[i][j] = 0; // Der Versuch war wohl nichts
		return false;
	}
 
 
	/**
	 * Sucht die Position der besten Möglichkeit 
	 *  gibt es eine Anzahl von Moeglichkeiten oefter, dann 
	 * 	dann wird die letzte Zahl als beste Moeglichkeit gewaehlt, 
	 * 	um in einem späteren Schleifendurchläufen Performance zu sparen
	 * @return: Gibt die Koordinaten zurueck
	 */
	public int[] smartChoice()
	{
		int minCount=n, minI=-1, minJ=-1;
		for(int i=0; i<n; i++) {
			for(int j=0; j<n; j++) {
				if(feld[i][j] == 0) {
						int c = smartCount(i,j);
						if(c <= minCount) {
							minCount = c;
							minI = i;
							minJ= j;
						}
				}
			}
		}
		int[] results = {minI, minJ};
		return results;
	}
 
 
	/**
	 * SMART: Zaehlt die leeren Felder (die Anzahl an moeglichen Loesungen) 
	 */
	public int smartCount(int i, int j) {
		boolean[] list = new boolean[10];
 
		for(int a=0; a<n; a++) {
			if(feld[i][a] > 0) 
				list[feld[i][a]] = true; // Durch "true" schliessen wir die Felder mit Wert aus
			if(feld[a][j] > 0)
				list[feld[a][j]] = true;
		}
 
		//Box
		int i_start = (int)(i/box_n) * box_n;
		int j_start = (int)(j/box_n) * box_n;
		for(int a=i_start; a<i_start+box_n; a++)
			if(a != i)
				for(int b=j_start; b<j_start+box_n; b++)
			    	if(feld[a][b] > 0 && b != j) 
			    		// >0 schliesst mehr Felder aus, dadurch an Pos1 der Abfrage
			    		list[feld[a][b]] = true;
 
		// Jetzt zaehlen wir die Felder, die wir gefunden haben
		byte max=0, possible=0;
		for(byte a=1; a<=n; a++) {
			if(list[a] == false)
				max++;
			else
				possible = a;
		}
 
		if(max == 1) {
			feld[i][j] = possible;
		}
 
		return max;
	}
 
 
	/**
	 * Ueberschreibt die Ausgabefunktion
	 * Wir verwenden hier nur einen Zaehler
	 * n*n Stringerweiterungungen sind langsamer als StringBuffer 
	 */
	public String toString() {
		StringBuffer returns = new StringBuffer();
 
		for(int i=0; i<n*n; i++) {
 
			if(i%n == 0 && i > 0) {
				returns.append("\n");
				if(i%(box_n*n) == 0)
					returns.append("-----------------------\n");
			}
 
			if (i%box_n == 0 && i%n != 0)
				returns.append(" |");
 
			returns.append(feld[i/n][i%n] == 0 ? "  " : " "+feld[i/n][i%n]);
		}
		returns.append("\nEs wurden "+tries+" Platzierungen ausprobiert in ");
		return returns.toString();
	}
}
Torben Brodt has attached the following files:
  • sudokupuzzles.zip (1.82 kB - 1,932 times downloaded - latest: May 19th 2012, 11:56pm)
  • fhwads.jar (29.85 kB - 1,874 times downloaded - latest: May 19th 2012, 5:13pm)

2

Thursday, November 23rd 2006, 5:50pm

Ich bin auf dieses Forum durch Google gestoßen, da ich auf der Suche nach einem Script zum Lösen von Sudokus war. Dieses hier gefällt mir im Gegensatz zu den anderen richtig gut und habe es auch gleich nach PHP umgeschrieben 8)
Nun möchte ich aber noch einen Sudoku-Generator programmieren, für den ich die Anzahl der Lösungen für ein Sudoku benötige, um ein eindeutig lösbares Sudoku zu erhalten. Ich sitze jetzt schon zwei Tage daran und habe noch keine funktionierende Lösung gefunden. Könnte ihr mir da weiterhelfen?

3

Thursday, November 23rd 2006, 7:03pm

Cool, poste doch mal die PHP Variante.
Obwohl ich PHP eigentlich besser beherrsche als Java, bin ich beim PHP Sudoku gescheitert.
Die Objektverwaltung macht PHP halt etwas anders.

Einen Sudoku Generator habe ich nicht selbst geschrieben.
Wiki beschreibt grob das Vorgehen
http://de.wikipedia.org/wiki/Sudoku#Erstellung_neuer_Sudokus

Quoted

1. Weg: Ein leeres Puzzlefeld wird Zelle für Zelle durch „Auswürfeln“ (Zufallsgenerator) mit Ziffern befüllt. Sobald es zu einem Regelverstoß kommt, muss per Backtracking-Methode eine andere Belegung probiert werden. Dies ist weniger trivial als beim Lösen des Puzzles: Da eine möglichst „zufällige“ Belegung des Puzzlefeldes benötigt wird, kann man nicht einfach alle Ziffern der Reihe nach durchprobieren. Es hindert aber nicht, alle Ziffern, sobald sie einmal „ausgewürfelt“ wurden, als künftig – für die jeweilige Zelle – gesperrt „abzuhaken“ (in einer Tabelle zu markieren)

Quoted

2. Weg: Neun Einsen ohne Regelverstoß im Puzzelfeld verteilen. Dann neun Zweier, neun Dreier, usw. verteilen. Auch hier muss ein Backtracking-Algorithmus angewandt werden.

Quoted

3. Weg: Man füllt eine Zeile oder eine Spalte in beliebiger Reihenfolge mit den erlaubten Ziffern, verschiebt dann mit jeder weiteren Zeile/Spalte die Ziffernfolge, bis man am Schluss alle möglichen Varianten untereinander/nebeneinander in einer n × n-Matrix vorliegen hat. Dies alleine wäre ein äußerst trivial zu lösendes Rätsel, da sich die Ziffernfolgen wiederholen; deswegen sollte man über erlaubte Transformationen diese Matrix nun schrittweise so verändern, dass die Ursprungsziffernfolge sowie die ausgeführten Transformationen nicht mehr nachvollziehbar sind. Erlaubte Transformationen sind z. B. das Invertieren, das Spiegeln (vertikal, horizontal, schräg), Rotieren, Vertauschen ganzer Zeilen oder Spalten, sofern sie innerhalb eines Mini-Quadrates bleiben, oder das Vertauschen ganzer Zeilen und Spalten von Miniquadraten. Etliche dieser Transformationen hintereinander verwischen (fast) alle Hinweise auf die ursprüngliche Ziffernfolge. Von den hier vorgestellten Erstellungsmethoden ist diese die am wenigsten aufwendige aber rechenintensivste.


Auf hexadoku.de setze ich pysdk ein - das solltest du dir auch mal im Source anzeigen. Ist wirklich "relativ" wenig Code.

4

Thursday, November 23rd 2006, 8:53pm

pysdk sieht sehr gut aus, vielen Dank für den Link :) Dann werde ich das benutzen, enthält ja sowohl Solver als auch Generator.

Woran bist du an der PHP Variante gescheitert? Ich habe den Code eigentlich 1:1 mit den entsprechenden Funktionen in PHP umgewandelt, jedoch auf die Methoden solveBacktrack und check mit seinen Submethoden reduziert.
Den Code kann ich noch posten, muss ihn nur noch mal etwas sortieren und übersichtlicher schreiben :wink:

5

Friday, November 24th 2006, 1:56pm

Die php version hatte ich noch ein paar Wochen vorher probiert.
Noch ohne eine funktionierende Java Vorlage.

Sollte schon funktionieren, wenn ichs nochmal Java->PHP versuche.
Habs nur nicht mehr probiert

6

Friday, December 22nd 2006, 5:43pm

Ich hatte gerade mal ein wenig zeit und hab mir mal den Sudoku Code angeschaut.

1. Könnte es sein, dass die "smart"-Variante nicht so smart ist, wie man glauben soll?
Bei mir produziert dieser Algorithmus ein paar 9er zu viel.

2. Das ist jetzt nicht böse gemeint, aber dein Quellcode ist nicht gerade leserlich bzw. leicht verständlich. Ein paar mehr Leerzeichen bzw. Leerzeilen, aber vor allem eine konsequente Klammersetzung wären von vorteil.

3. Ansonsten: repekt ;)

7

Saturday, December 23rd 2006, 6:16pm

@1: unser Proff hat zumindest keine Fehler beanstandet. Kannst du mir ein Beispiel nennen, wo das Ergebnis falsch ist?

@2: Im realen Leben streiten sich die Männer wer den längsten hat, beim Coden gehts nunmal drum, wer den kürzesten Code hat. Und da wir die Aufgabe alle machen mussten, hatte man konnte man direkt vergleichen.
Aber "konsequent" ist es doch. Alles was einen Einzeier zur Folge hat, ist nicht geklammert - alles andere schon.

8

Wednesday, December 27th 2006, 8:09am

Hehe wer den kürzesten Code hat ;)
Das ist gut, solltet wohl mit Assembler anfangen.

Hab das Programm mit "3" und "puzzle1.sudoku" aufgerufen und bekomm das hier als Ergebnis.

9 9 9 | 1 3 8 | 4 7 9
6 9 9 | 9 9 5 | 2 9 8
7 5 2 | 9 9 4 | 1 3 9
-----------------------
8 7 9 | 3 9 2 | 9 5 4
5 4 1 | 9 9 9 | 6 2 3
9 2 3 | 5 4 6 | 7 8 1
-----------------------
9 9 7 | 8 6 1 | 3 4 5
4 8 6 | 7 5 3 | 9 9 2
9 1 9 | 4 2 9 | 9 6 7
Es wurden 134 Platzierungen ausprobiert in

Und zum Thema konsequenter Klammerung. Nein du hast mindestens einmal ne if-Anweisung drinnen, bei der ein "Einzeiler" geklammert wurde (Aus rein leserlichen Gründen würde ich prinzipiell auch bei Einzeilern die Klammern dazumachen.

9

Wednesday, December 27th 2006, 2:20pm

ups :-/ Dann klappt der Code wohl wirklich nicht.

Aber ist auch schon zu lange her um mich da wieder einzuarbeiten... Vielleicht findet ja jemand anderes den Fehler oder ich schaus mir mal in den nächsten Semesterferien an.

10

Wednesday, December 27th 2006, 2:47pm

Jaja das Student-sein ist was herrliches *grr
Habs leider schon hinter mir :(

11

Tuesday, June 10th 2008, 9:15am

Ich habe mir das Ganze mal etwas genauer angesehen.
* Das normale Backtracking funktioniert problemlos.
* Bei der Smart-Variante liegt der Fehler in den Zeilen 261-263; das gehört schlichtweg raus. Beim Vorwärts-Weg wird hier ein Wert (possible) eingetragen, aber beim Rückwärts-Weg gibt es kein entsprechendes Löschen(0). Wie schon gesagt: Die drei Zeilen raus, und alles läuft bestens.

Die Vorgabe aus puzzle1:
9 | 1 3 8 | 7
6 | | 8
2 | | 1
-------+-------+------
8 7 | 3 2 | 5 4
| 9 |
9 2 | 5 6 | 8 1
-------+-------+------
7 | | 3
4 | | 2
1 | 4 2 9 | 6

Die Lösung dazu mit Smart:
5 9 4 | 1 3 8 | 2 7 6
6 3 1 | 2 7 5 | 4 9 8
7 8 2 | 9 6 4 | 1 3 5
-------+-------+------
8 7 6 | 3 1 2 | 9 5 4
1 4 5 | 8 9 7 | 6 2 3
9 2 3 | 5 4 6 | 7 8 1
-------+-------+------
2 5 7 | 6 8 1 | 3 4 9
4 6 9 | 7 5 3 | 8 1 2
3 1 8 | 4 2 9 | 5 6 7
Es wurden 253 Platzierungen ausprobiert in 1.39 s

Ich habe zudem damit noch verschiedene Erweiterungen (z.B. 2x3, 2x4, X-Sudoku) implementiert; das Beispiel war die ideale Basis. Fazit: Ich als Prof hätte eine glatte 1-2 vergeben ;-).

Hägar

This post has been edited 4 times, last edit by "hägar" (Jun 10th 2008, 9:27am)


Similar threads

Social bookmarks