Sudoku – to gra logiczna, która polega na wypełnieniu siatki 9×9 cyframi tak, aby każda kolumna, każdy wiersz i każdy z dziewięciu kwadratów 3×3 (które razem tworzą większą siatkę 9×9) zawierały wszystkie cyfry od 1 do 9.
Oto podstawowe reguły:
- Podstawowa siatka: Cała gra odbywa się na siatce 9×9, która jest podzielona na 9 mniejszych kwadratów 3×3.
- Cyfry od 1 do 9: Każda cyfra od 1 do 9 musi pojawić się dokładnie raz w każdym wierszu, każdej kolumnie i każdym kwadracie 3×3.
- Jedna cyfra na komórkę: Każda komórka w siatce może zawierać tylko jedną cyfrę.
- Początkowe wskazówki: Gra zaczyna się z pewną liczbą już wypełnionych cyfr (wskazówek), które gracze muszą użyć jako punkt wyjścia.
- Logika, nie, zgadywanie: Rozwiązanie sudoku opiera się wyłącznie na dedukcji i logice. Nie ma potrzeby zgadywania.
- Brak powtórzeń: Cyfra może pojawić się tylko raz w każdym wierszu, kolumnie i kwadracie 3×3.
- Unikalne rozwiązanie: Każda prawidłowo zaprojektowana łamigłówka sudoku ma tylko jedno możliwe rozwiązanie.
Spis treści
Sudoku – przygotowanie planszy
Rozpoczynamy od zdefiniowania planszy jako dwuwymiarowej tablicy liczb całkowitych, gdzie wartość 0 reprezentuje pustą komórkę:
public static void main(String[] args) { int[][] board = { {8, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 3, 6, 0, 0, 0, 0, 0}, {0, 7, 0, 0, 9, 0, 2, 0, 0}, {0, 5, 0, 0, 0, 7, 0, 0, 0}, {0, 0, 0, 0, 4, 5, 7, 0, 0}, {0, 0, 0, 1, 0, 0, 0, 3, 0}, {0, 0, 1, 0, 0, 0, 0, 6, 8}, {0, 0, 8, 5, 0, 0, 0, 1, 0}, {0, 9, 0, 0, 0, 0, 4, 0, 0} }; printBoard(board); } private static void printBoard(int[][] board) { for (int i = 0; i < SIZE; i++) { for (int j = 0; j < SIZE; j++) { System.out.print(board[i][j] + " "); } System.out.print("\n"); } System.out.println(); }
Sudoku – rozwiązanie
Kluczową częścią algorytmu jest metoda solve(), która rekurencyjnie przeszukuje możliwości wypełnienia planszy:
private boolean solve(int[][] board) { for (int i = 0; i < SIZE; i++) { for (int j = 0; j < SIZE; j++) { if (board[i][j] == NO_VALUE) { for (int k = 1; k <= 9; k++) { board[i][j] = k; if (isValid(board, i, j) && solve(board)) { return true; } board[i][j] = NO_VALUE; } return false; } } } return true; }
➡ ZOBACZ 👉: Rekurencja ➿ rekursja ➿ rekurencja
Dla każdej pustej komórki (NO_VALUE), metoda próbuje wstawić każdą możliwą wartość od 1 do 9, sprawdzając za każdym razem, czy nie narusza to zasad sudoku przy użyciu metody isValid().
Sudoku – walidacja
private boolean isValid(int[][] board, int row, int column) { return (rowConstraint(board, row) && columnConstraint(board, column) && subsectionConstraint(board, row, column)); }
Korzystając z pomocniczych metod, algorytm weryfikuje, czy dane umieszczenie liczby nie powoduje konfliktów.
Sudoku – pomocnicze metody walidacyjne
private boolean rowConstraint(int[][] board, int i) { boolean[] constraint = new boolean[SIZE]; return IntStream.range(1, 9) .allMatch(column -> checkConstraint(board, i, constraint, column)); }
private boolean columnConstraint(int[][] board, int j) { boolean[] constraint = new boolean[SIZE]; return IntStream.range(1, 9) .allMatch(row -> checkConstraint(board, row, constraint, j)); }
private boolean subsectionConstraint(int[][] board, int i, int j) { boolean[] constraint = new boolean[SIZE]; int subsectionRowStart = (i / 3) * 3; int subsectionRowEnd = subsectionRowStart + 3; int subsectionColumnStart = (j / 3) * 3; int subsectionColumnEnd = subsectionColumnStart + 3; for (int r = subsectionRowStart; r < subsectionRowEnd; r++) { for (int c = subsectionColumnStart; c < subsectionColumnEnd; c++) { if (!checkConstraint(board, r, constraint, c)) return false; } } return true; }
Metody takie jak rowConstraint() i columnConstraint() używają strumieni IntStream do sprawdzania, czy każda liczba w danym wierszu lub kolumnie jest unikalna. Z kolei metoda subsectionConstraint() odpowiada za weryfikację małych kwadratów 3×3.
Sudoku – optymalizacja sprawdzania
boolean checkConstraint(int[][] board, int i, boolean[] constraint, int j) { if (board[i][j] != NO_VALUE) { if (!constraint[board[i][j] - 1]) { constraint[board[i][j] - 1] = true; } else { return false; } } return true; }
Metoda checkConstraint() zapewnia, że wstawiona liczba nie powtarza się już w danym kontekście (wiersz, kolumna, kwadrat 3×3).
Sudoku – wizualizacja rozwiązania
private void printBoard(int[][] board) { for (int i = 0; i < SIZE; i++) { for (int j = 0; j < SIZE; j++) { System.out.print(board[i][j]); System.out.print(" "); } System.out.println(); } System.out.println(); }
Choć algorytm zwraca wartość logiczną wskazującą, czy znaleziono rozwiązanie, dla lepszego zrozumienia procesu warto również wyświetlić końcową konfigurację planszy.
Sudoku – podsumowanie
Wyżej przedstawiony algorytm, jest potężnym narzędziem, które przez systematyczne próbowanie i eliminowanie możliwości, pozwala znaleźć prawidłowe rozwiązanie łamigłówki. Tutaj istotna jest dokładna walidacja każdego ruchu zgodnie z zasadami sudoku oraz rekurencyjne szukanie rozwiązania, które spełnia wszystkie ograniczenia. Mimo że algorytm ten jest skuteczny, istnieje wiele możliwości jego optymalizacji, na przykład poprzez wprowadzenie bardziej zaawansowanych technik eliminacji i sprawdzania możliwości, co może znacząco przyspieszyć proces znajdowania rozwiązania.
➡ ZOBACZ 👉: StormIT | Oferta
20+ BONUSOWYCH materiałów z programowania
e-book – „8 rzeczy, które musisz wiedzieć, żeby dostać pracę jako programista”,
e-book – „Java Cheat Sheet”,
checklista – „Pytania rekrutacyjne”
i wiele, wiele wiecej!