Sudoku Algorithmus Backtracking

Diese Seite verwendet Cookies. Durch die Nutzung unserer Seite erklären Sie sich damit einverstanden, dass wir Cookies setzen. Weitere Informationen

  • 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

    Quellcode

    1. package ads;
    2. /**
    3. * Autoren: Marco May & Torben Brodt
    4. */
    5. import fhw.ADSTool;
    6. public class SudokuPlay {
    7. public static void main(String[] args) {
    8. if(args.length < 2)
    9. System.out.println("Zur Backtracking Loesung 2 als Paramter uebergeben\nZum intelligenten Loesen eine 3");
    10. ADSTool.resetTime();
    11. if(args.length > 1)
    12. for(int i=1; i<args.length; i++) {
    13. Sudoku feld = new Sudoku(9, args[0], args[i]);
    14. System.out.println(feld);
    15. ADSTool.showRTime();
    16. System.out.println();
    17. }
    18. }
    19. }
    20. class Sudoku {
    21. // Konstante fuer die Anzahl an Blöcken(9er/12er Sudoku) und die Breite einer Box (3 oder 4)
    22. public final int n, box_n;
    23. // Zaehlt die Anzahl der erprobten Platzierungen
    24. public int tries=0;
    25. // Sudoku Feld (noch nicht vorbelegt, da 9^12 moeglich sind)
    26. public byte[][] feld;
    27. /**
    28. * Konstruktor
    29. * args[0] = dateiname
    30. * args[1] = smart
    31. */
    32. public Sudoku(int n, String what, String filename) {
    33. this.n = n;
    34. this.box_n = (int)Math.sqrt(n);
    35. this.feld = new byte[n][n];
    36. //Datei einlesen
    37. read(filename);
    38. //und loesen
    39. if(what.equals("2")) {
    40. solveBacktrack(0,0);
    41. } else if(what.equals("3")) {
    42. smartSolve();
    43. } else if(what.equals("1")) {
    44. // belassen fuer einfache Ausgabe
    45. }
    46. }
    47. /**
    48. * Liest ein Raetsel ein
    49. */
    50. public void read(String filename) {
    51. int i=0, j=0;
    52. try {
    53. int[] file = ADSTool.readIntArray(filename);
    54. for(int digit : file) {
    55. if(j == n) {
    56. j = 0;
    57. i++;
    58. }
    59. feld[i][j] = (byte)digit;
    60. j++;
    61. }
    62. } catch(Exception e) {
    63. System.out.println("Bitte mit dem korrekten Dateinamen als Konsolenparameter starten");
    64. System.exit(0);
    65. }
    66. }
    67. /**
    68. * Loest das Raetsel durch Backtracking
    69. */
    70. public boolean solveBacktrack(int i, int j) {
    71. if(j == n) { /* Zeilenende erreicht,
    72. * fange wieder links an,
    73. * gehe eine Zeile nach unten
    74. * oder breche ab, wenn du ganz fertig bist
    75. */
    76. i++;
    77. if(i == n)
    78. return true;
    79. j = 0;
    80. }
    81. if(feld[i][j] > 0) // Das Feld ist erfolgreich gesetzt
    82. // Funktionsaufruf mit naechster Spalte
    83. return solveBacktrack(i, j+1);
    84. for(byte a=1; a<n+1; a++) {
    85. if(check(i, j, a) == false) {
    86. feld[i][j] = a;
    87. if(solveBacktrack(i, j+1))
    88. return true;
    89. }
    90. }
    91. feld[i][j] = 0; // Der Versuch war wohl nichts
    92. return false;
    93. }
    94. /**
    95. * Prueft ob dieses Feld gesetzt werden kann (ruft dabei 3 Subfunktionen auf)
    96. * @return-> Gibt true zurueck, wenn der Wert bereits existiert
    97. */
    98. public boolean check(int i, int j, int value) {
    99. tries++;
    100. // Fuer die Ausgabe mit SudokuSmart
    101. // System.out.println(""+i+"/"+j);
    102. if(checkHorizontal(j, value))
    103. return true;
    104. if(checkVertical(i, value))
    105. return true;
    106. if(checkBox(i, j, value))
    107. return true;
    108. return false;
    109. }
    110. /**
    111. * Prueft ob der Wert bereits in der (horizontalen) SPALTE existiert
    112. * @return-> Gibt true zurueck, wenn der Wert bereits existiert
    113. */
    114. public boolean checkHorizontal(int j, int value) {
    115. for(int a=0; a<n; a++)
    116. if(feld[a][j] == value)
    117. return true;
    118. return false;
    119. }
    120. /**
    121. * Prueft ob der Wert bereits in der (vertikalen) REIHE existiert
    122. * @return-> Gibt true zurueck, wenn der Wert bereits existiert
    123. */
    124. public boolean checkVertical(int i, int value) {
    125. for(int a=0; a<n; a++)
    126. if(feld[i][a] == value)
    127. return true;
    128. return false;
    129. }
    130. /**
    131. * Prueft ob der Wert bereits in der BOX existiert
    132. * @return-> Gibt true zurueck, wenn der Wert bereits existiert
    133. */
    134. public boolean checkBox(int i, int j, int value) {
    135. // oberes, linkes Eck der Box herausfinden (2|8 zu 0|6)
    136. int i_start = (int)(i/box_n) * box_n;
    137. int j_start = (int)(j/box_n) * box_n;
    138. for(int a=i_start; a<i_start+box_n; a++)
    139. for(int b=j_start; b<j_start+box_n; b++)
    140. if(feld[a][b] == value)
    141. return true;
    142. return false;
    143. }
    144. /**
    145. * SMART: Loest das Raetsel intelligenter
    146. */
    147. public boolean smartSolve() {
    148. int[] best = smartChoice();
    149. int i=best[0], j=best[1];
    150. if(i == -1 && j == -1) //Das Sudoku funktioniert - es gibt keine leeren Felder mehr
    151. return true;
    152. for(byte a=1; a<n+1; a++) {
    153. // System.out.print("pruefe wert "+a+" in ");
    154. if(check(i, j, a) == false) {
    155. feld[i][j] = a;
    156. if(smartSolve())
    157. return true;
    158. }
    159. }
    160. feld[i][j] = 0; // Der Versuch war wohl nichts
    161. return false;
    162. }
    163. /**
    164. * Sucht die Position der besten Möglichkeit
    165. * gibt es eine Anzahl von Moeglichkeiten oefter, dann
    166. * dann wird die letzte Zahl als beste Moeglichkeit gewaehlt,
    167. * um in einem späteren Schleifendurchläufen Performance zu sparen
    168. * @return: Gibt die Koordinaten zurueck
    169. */
    170. public int[] smartChoice()
    171. {
    172. int minCount=n, minI=-1, minJ=-1;
    173. for(int i=0; i<n; i++) {
    174. for(int j=0; j<n; j++) {
    175. if(feld[i][j] == 0) {
    176. int c = smartCount(i,j);
    177. if(c <= minCount) {
    178. minCount = c;
    179. minI = i;
    180. minJ= j;
    181. }
    182. }
    183. }
    184. }
    185. int[] results = {minI, minJ};
    186. return results;
    187. }
    188. /**
    189. * SMART: Zaehlt die leeren Felder (die Anzahl an moeglichen Loesungen)
    190. */
    191. public int smartCount(int i, int j) {
    192. boolean[] list = new boolean[10];
    193. for(int a=0; a<n; a++) {
    194. if(feld[i][a] > 0)
    195. list[feld[i][a]] = true; // Durch "true" schliessen wir die Felder mit Wert aus
    196. if(feld[a][j] > 0)
    197. list[feld[a][j]] = true;
    198. }
    199. //Box
    200. int i_start = (int)(i/box_n) * box_n;
    201. int j_start = (int)(j/box_n) * box_n;
    202. for(int a=i_start; a<i_start+box_n; a++)
    203. if(a != i)
    204. for(int b=j_start; b<j_start+box_n; b++)
    205. if(feld[a][b] > 0 && b != j)
    206. // >0 schliesst mehr Felder aus, dadurch an Pos1 der Abfrage
    207. list[feld[a][b]] = true;
    208. // Jetzt zaehlen wir die Felder, die wir gefunden haben
    209. byte max=0, possible=0;
    210. for(byte a=1; a<=n; a++) {
    211. if(list[a] == false)
    212. max++;
    213. else
    214. possible = a;
    215. }
    216. if(max == 1) {
    217. feld[i][j] = possible;
    218. }
    219. return max;
    220. }
    221. /**
    222. * Ueberschreibt die Ausgabefunktion
    223. * Wir verwenden hier nur einen Zaehler
    224. * n*n Stringerweiterungungen sind langsamer als StringBuffer
    225. */
    226. public String toString() {
    227. StringBuffer returns = new StringBuffer();
    228. for(int i=0; i<n*n; i++) {
    229. if(i%n == 0 && i > 0) {
    230. returns.append("\n");
    231. if(i%(box_n*n) == 0)
    232. returns.append("-----------------------\n");
    233. }
    234. if (i%box_n == 0 && i%n != 0)
    235. returns.append(" |");
    236. returns.append(feld[i/n][i%n] == 0 ? " " : " "+feld[i/n][i%n]);
    237. }
    238. returns.append("\nEs wurden "+tries+" Platzierungen ausprobiert in ");
    239. return returns.toString();
    240. }
    241. }
    Alles anzeigen
    Dateien
    • fhwads.jar

      (29,85 kB, 2.399 mal heruntergeladen, zuletzt: )
    • sudokupuzzles.zip

      (1,82 kB, 2.435 mal heruntergeladen, zuletzt: )
  • 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?
  • 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

    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)

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

    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.
  • 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:
  • 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 ;)
    Ubuntu Edgy * Kernel 2.6.17 * Gnome 2.16 * Beryl
    2 x Athlon MP 1900 * MSI K7D Master-L * 1024 MB ECC DDR333
    Hercules 9800XT 256 MB Ram * 1x 250 GB IDE
    Wasserkühlung
  • @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.
  • 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.
    Ubuntu Edgy * Kernel 2.6.17 * Gnome 2.16 * Beryl
    2 x Athlon MP 1900 * MSI K7D Master-L * 1024 MB ECC DDR333
    Hercules 9800XT 256 MB Ram * 1x 250 GB IDE
    Wasserkühlung
  • 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

    Dieser Beitrag wurde bereits 4 mal editiert, zuletzt von hägar ()