Решатель судоку в Java, используя возврат и рекурсию - PullRequest
24 голосов
/ 23 февраля 2012

Я программирую решатель судоку на Java для сетки 9x9.

У меня есть методы для:

  • печать сетки

  • инициализация платы с заданными значениями

  • проверка на наличие конфликтов (если один и тот же номер находится в той же строке или в подсетке 3x3)

  • метод размещения цифр, одна за другой, который требует наибольшей работы.

Прежде чем углубляться в подробности этого метода, имейте в виду, что для его решения мне необходимо использовать рекурсию, а также возврат к предыдущей версии (см. Пример приложения http://www.heimetli.ch/ffh/simplifiedsudoku.html)

Кроме того, я решаю эту судоку, двигаясь вертикально вниз, начиная с верхнего левого угла, через первый столбец, затем через второй столбец и т. Д.

Пока у меня есть следующее:

public boolean placeNumber(int column){

    if (column == SUDOKU_SIZE){  // we have went through all the columns, game is over

        return true;

    }

    else
    {
        int row=0;  //takes you to the top of the row each time

        while (row < SUDOKU_SIZE)    loops through the column downwards, one by one
        {

            if (puzzle[row][column]==0){  //skips any entries already in there (the given values)

                puzzle[row][column]=1;   //starts with one

                while(conflictsTest(row,column)){   //conflictsTest is the method I wrote, which checks if the given parameters are in conflict with another number

                    puzzle[row][column] += 1;  

                }


           //BACK TRACKING 

                placeNumber(column);      //recursive call

            }
            else{
              row++;                  // row already has a number given, so skip it
            }
        }

        column++;              // move on to second column
        placeNumber(column);

    }
    return false; // no solutions to this puzzle
}

Там, где я обозначил BACKTRACKING, я считаю, что остальная часть моего кода должна идти.

Я придумал что-то вроде:

  • если значение равно 10, установите это значение в ноль, вернитесь назад на строку и увеличьте это значение на 1

Эта стратегия возврата не работает по нескольким причинам:

  1. что если в предыдущей строке было заданное значение (иначе я не должен увеличивать его или касаться, а вместо этого вернуться к последнему значению, которое я там поместил)

  2. что, если предыдущее значение было 9. и если я увеличил это значение на 1, теперь мы находимся на 10, что не сработает.

Может кто-нибудь помочь мне?

Ответы [ 5 ]

7 голосов
/ 23 февраля 2012

Я не знаю, как вы собираетесь решить судоку, но даже если вы используете метод грубой силы (и мне кажется, что вы описываете), вы должны учитывать, что ваша структура данных не подходит.

Под этим я подразумеваю, что каждая ячейка должна быть не просто числом, а набором чисел (которые могут быть там размещены).

Следовательно, данные числа будут представлены в виде одноэлементных наборов, а пустые можно инициализировать с помощью {1,2,3,4,5,6,7,8,9}. И тогда цель состоит в том, чтобы уменьшить число не-одиночных клеток, пока все клетки не станут синглетонами.

(Обратите внимание, что, решая судоку карандашом и бумагой, часто пишут небольшие числа в пустых ячейках, чтобы отследить, какие числа там возможны, насколько это возможно.)

И затем, при «попытке следующего номера» вы берете следующий номер из набора. Данные ячейки не имеют следующего номера, поэтому вы не можете их изменить. Таким образом, трудности, которые вы описываете, исчезают (по крайней мере, немного).

------ РЕДАКТИРОВАТЬ, ПОСЛЕ УЧЕБЫ, ЧТО ТРЕБУЕТСЯ СИЛЬНАЯ СИЛА.

Ваш учитель, очевидно, хочет научить вас чудесам рекурсии. Очень хорошо!

В этом случае нам просто нужно знать, какие клетки даны, а какие нет.

Конкретный простой способ, который можно использовать здесь, состоит в том, чтобы поместить 0 в любую не заданную ячейку, поскольку данные ячейки по определению являются одной из 1,2,3,4,5,6,7,8,9.

Теперь давайте подумаем, как заставить рекурсивную грубую силу работать.

У нас есть цель решить судоку с n пустыми клетками. Если бы у нас была функция, которая бы решала судоку с n-1 пустыми ячейками (или сигнализировала бы, что она не разрешима), то эта задача была бы простой:

let c be some empty cell.
let f be the function that solves a sudoku with one empty cell less.
for i in 1..9
   check if i can be placed in c without conflict
   if not continue with next i
   place i in c
   if f() == SOLVED then return SOLVED
return NOTSOLVABLE

Этот псевдокод выбирает некоторую пустую ячейку, а затем пробует все подходящие числа. Поскольку судоку по определению имеет только одно решение, возможны только следующие случаи:

  • мы выбрали правильный номер. Тогда f () найдет оставшуюся часть решения и вернет решено.
  • мы выбрали неправильное число: f () будет сигнализировать, что судоку не разрешимо с этим неправильным числом в нашей ячейке.
  • мы проверили все числа, но никто не был прав: тогда мы сами получили неразрешимую судоку, и мы сигнализируем об этом нашему вызывающему.

Излишне говорить, что алгоритм основан на предположении, что мы всегда размещаем только числа, которые не являются конфликтует с текущим состоянием. Например, мы не размещаем 9 там, где в той же строке, столбце или поле уже есть 9.

Если мы сейчас подумаем о том, как выглядит наша загадочная, но неизвестная функция f(), то окажется, что она будет почти такой же, как у нас!
Единственный случай, который мы еще не рассмотрели, - это судоку с 0 пустыми клетками. Это означает, что если мы обнаружим, что больше нет пустых ячеек, мы знаем, что мы только что решили судоку и вернули просто решено.

Это обычная хитрость при написании рекурсивной функции, которая должна решить проблему. Мы пишем execute (), и мы знаем, что проблема разрешима вообще. Следовательно, мы уже можем использовать функцию, которую мы только что написали, при условии, что с каждой рекурсией проблема каким-то образом приближается к решению. В конце мы достигаем так называемого базового случая, где мы можем дать решение без дальнейшей рекурсии.

В нашем случае мы знаем, что судоку разрешимо, более того, мы знаем, что у него есть ровно одно решение. Поместив кусок в пустую ячейку, мы приближаемся к решению (или к диагнозу, которого такового нет) и рекурсивно передаем новую, меньшую проблему функции, которую мы только что пишем. Базовый случай - «Судоку с 0 пустыми клетками», который на самом деле является решением .

(Все становится немного сложнее, если есть много возможных решений, но мы оставим это для следующего урока.)

4 голосов
/ 09 апреля 2012

Во-первых, , предложение по оптимизации: проверяя, присутствует ли число, которое вы собираетесь поместить в ячейку, в той же строке, столбце или мини-сетке, вам не нужнозапустить цикл или что-то в этом роде.Вы можете выполнить мгновенную проверку путем индексации массива.

Рассмотрим 3 булевых двумерных массива 9x9:

boolean row[9][9], col[9][9], minigrid[9][9]

Мы будем использовать первый массив для проверки наличия числа вта же строка, второй массив для проверки наличия номера в том же столбце и третий для мини-сетки.

Предположим, вы хотите поместить число n в свою ячейку i , j .Вы проверите, истинно ли row [i] [n-1] .Если да, то строка i th уже содержит n.Точно так же вы проверите, истинны ли col [j] [n-1] и minigrid [gridnum] [n-1] .

Здесь gridnum - это индекс мини-сетки, где находится ячейка, в которую вы хотите вставить число. Чтобы вычислить номер мини-сетки для ячейки i, j , разделите i & j на 3, умножьтенеотъемлемая часть первого с 3, и добавить его к неотъемлемой части последнего.

Вот как это выглядит:

gridnum = (i/3)*3 + j/3

Посмотрев на значения i / 3 и j / 3для всех я и j, вы получите представление о том, как это работает.Также, если вы поместите число в ячейку, обновите и массивы.Например, row [i] [n-1] = true

Если есть часть, которую вы не понимаете, оставьте комментарий, и я отредактирую свой ответ, чтобы объяснить его.

Во-вторых , с помощью рекурсии и обратного отслеживания решить эту проблему довольно просто.

boolean F( i, j) // invoke this function with i = j = 0
{
If i > 8: return true // solved

for n in 1..9
 {
 check if n exists in row, column, or mini grid by the method I described

 if it does: pass ( skip this iteration )

 if it doesn't
  {
   grid[i][j] = n
   update row[][], col[][], minigrid[][]

   if F( if j is 8 then i+1 else i, if j is 8 then 0 else j+1 ) return true // solved
  }
 }
 return false // no number could be entered in cell i,j
}
1 голос
/ 23 февраля 2012

Я предлагаю передать текущую строку и столбец в рекурсивный метод, затем найти все разрешенные числа для ячейки THAT, для каждого разрешенного числа рекурсивно вызвать метод для следующего столбца (или следующей строки, если в последнем столбце) и отменить перемещениеесли это приводит к мертвой дорожке

public boolean fillCell(int r, int c) {
    if last row and last cell {
        //report success
        return true
    }
    for i 1 to 9 {
        if can place i on r, c {
            board[r][c] = i
            if !fillCell(next empty row, next empty column) { //DONT change r or c here or you will not be able to undo the move
                board[r][c] = 0
            }
            /*
            else {
                return true; //returning true here will make it stop after 1 solution is found, doing nothing will keep looking for other solutions also
            }
            */

        }
    }
    return false        
}
0 голосов
/ 23 февраля 2012

некоторые идеи, которые могут быть полезны (относительно рекурсии и обратного отслеживания)

//some attributes you might need for storing e.g. the configuration to track back to.

boolean legal(Configuration configuration) {

}

int partSolution(Configuration configuration) {
  if (legal(configuration))
    return partSolution(nextConfiguration())
  else
    return partSolution(previousConfiguration())    
}

Configuration nextConfiguration() {
 //based on the current configuration and the previous tried ones,
 //return the next possible configuration:
 //next number to enter, next cell to visit
}

Configuration previousConfiguration() {
 //backtrack
}

solve () {
  call partSolution with start configuration while partSolution < 9x9
}

написать класс конфигурации, который содержит сетку с введенными числами и некоторые другие атрибуты, такие как введенный размер и #numbers, и подуматьчто еще нужно

0 голосов
/ 23 февраля 2012

Я бы проверил каждую ячейку и вернулся бы к шагу рекурсии, если решение не найдено.

Более подробно: Перейти к следующей ячейке, если значение x == 0, проверить, будет ли x + 1 допустимым, если true, перейти к следующей ячейке, рекурсивно вызвав метод со следующей возможной ячейкой. Если номер недействителен, проверьте x + 2 и т. Д., Если номер не действителен, верните false и повторите шаг x + 1 в предыдущем вызове. Если вы нажмете на ячейку с номером внутри, не вызывайте рекурсию, а перейдите непосредственно к следующей, поэтому вам не нужно отмечать предварительно введенные ячейки.

Псевдокод:

fillcell cell
 while cell is not 0
  cell = next cell
 while cell value < 10
  increase cell value by one
  if cell is valid 
    if fillcell next cell is true
      return true
return false

Не уверен, что это правильно, но это должно показать идею.

...