Подтвердить что ты не робот

Решение Sudoku на Java, используя обратную трассировку и рекурсию

Я программирую решение Sudoku в 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

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

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

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

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

4b9b3361

Ответ 1

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

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

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

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

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

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

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

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

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

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

У нас есть цель решить sudoku с 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() найдет остальную часть решения и вернет SOLVED.
  • мы выбрали неверный номер: f() будет сигнализировать, что судоку не разрешимо с этим неправильным номером в нашей ячейке.
  • мы проверили все числа, но никто не был прав: тогда у нас есть неразрешимая судоку, и мы сигнализируем об этом нашему вызывающему.

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

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

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

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

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

Ответ 2

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

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

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

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

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

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

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

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

Изучая значения i/3 и j/3 для всех я и j, вы получите представление о том, как это работает. Кроме того, если вы поместите число в ячейку, обновите также массивы. Например. строка [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
}

Ответ 3

Я предлагаю передать и текущую строку и столбец рекурсивному методу затем найдите все допустимые числа для ячейки 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        
}

Ответ 4

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

Более подробно: Перейдите к следующей ячейке, если значение 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

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

Ответ 5

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

//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
}

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