Я работаю над оптимизированным решателем судоку. Основная идея возврата осталась, хотя я добавил упреждающую проверку. Домен (какие числа можно задать в ячейке) сохраняется для каждой ячейки. Прежде чем попытаться ввести число в ячейку и вернуться назад, если это не сработает, я проверяю все связанные ячейки, чтобы увидеть, является ли это число единственным, которое можно там установить. Если возникает конфликт, я пробую следующий номер и так далее. В оптимальном случае это уменьшает количество необходимых возвратов.
Однако мой код, к сожалению, не работает должным образом. Я был бы очень признателен за вашу помощь.
С наилучшими пожеланиями
Эрик
Код:
#include <iostream>
#include <bitset>
#include <array>
using namespace std;
#define n 9
void print_board(int board[n][n]);
bool solve(array<bitset<n>, n * n> &domains, int board[n][n], int i = 0, int j = 0);
void initialize_domains(array<bitset<n>, n * n> &domains, int board[n][n]);
bool update_domains(array<bitset<n>, n * n> &domains, int board[n][n], int i, int j, int k);
std::bitset<n> reverse_bits(const std::bitset<n> &bitset)
{
std::bitset<n> reversed;
for (int i = 0; i < n; ++i)
{
reversed[i] = bitset[n - 1 - i]; // Sets the i-th bit in reversed to the (N-1-i)-th bit in bitset
}
return reversed;
}
void printDomains(const std::array<std::bitset<n>, n * n> &domains)
{
for (int i = 0; i < n; ++i)
{
for (int j = 0; j < n; ++j)
{
std::cout << "Domain for cell (" << i << ", " << j << "): " << reverse_bits(domains[i * n + j]) << std::endl;
}
}
}
int main() // main function handles the flow of the program
{
int board[n][n] = {// definition of the initial board
{5, 3, 0, 0, 7, 0, 0, 0, 0},
{6, 0, 0, 1, 9, 5, 0, 0, 0},
{0, 9, 8, 0, 0, 0, 0, 6, 0},
{8, 0, 0, 0, 6, 0, 0, 0, 3},
{4, 0, 0, 8, 0, 3, 0, 0, 1},
{7, 0, 0, 0, 2, 0, 0, 0, 6},
{0, 6, 0, 0, 0, 0, 2, 8, 0},
{0, 0, 0, 4, 1, 9, 0, 0, 5},
{0, 0, 0, 0, 8, 0, 0, 7, 9}};
std::array<std::bitset<n>, n * n> domains;
initialize_domains(domains, board);
print_board(board);
solve(domains, board);
cout << "\n";
print_board(board);
}
void print_board(int board[n][n]) // displays the Sudoku board
{
for (int i = 0; i < n; ++i)
{
for (int j = 0; j < n; ++j)
{
cout << board[i][j] << " ";
}
cout << "\n";
}
}
// starting from the cell of a cell, update the domains of the influenced cells
bool update_domains(std::array<std::bitset<n>, n * n> &domains, int board[n][n], int i, int j, int k)
{
for (int a = 0; a < n; ++a)
{
domains[i * n + a].set(k - 1); // row
domains[j + n * a].set(k - 1); // column
// If it's no longer possible to place a number in the cell
if (domains[i * n + a].all() && board[i][a] == 0)
return false;
if (domains[j + n * a].all() && board[a][j] == 0)
return false;
}
for (int row = (i / 3) * 3; row < ((i / 3) * 3) + 3; ++row)
{
for (int col = (j / 3) * 3; col < ((j / 3) * 3) + 3; ++col)
{
domains[row * n + col].set(k - 1); // subgrid
if (domains[row * n + col].all() && board[row][col] == 0)
return false;
}
}
return true;
}
// 0 = possible to place the number
// 1 = not possible to place the number
void initialize_domains(array<bitset<n>, n * n> &domains, int board[n][n])
{
for (int i = 0; i < n; ++i)
{
for (int j = 0; j < n; ++j)
{
if (board[i][j] != 0)
update_domains(domains, board, i, j, board[i][j]);
}
}
}
// i specifies the row
// j specifies the column
bool solve(array<bitset<n>, n * n> &domains, int board[n][n], int i, int j) // responsible for solving the Sudoku using brute-force backtracking
{
if (i == n) // if all rows and thus all columns are successfully filled, the Sudoku is solved
return true;
if (j == n) // if the column index exceeds the dimension, the first cell of the next row must be considered
return solve(domains, board, i + 1, 0);
if (board[i][j] != 0) // if there is no empty cell at the current position, move to the next position
return solve(domains, board, i, j + 1);
for (int k = 1; k <= n; ++k) // all valid numbers from 1-9 are tried linearly
{
if (domains[i * n + j].test(k - 1)) // if the number is not in the domain of the current cell, it can be skipped
continue;
array<bitset<n>, n * n> pre_updated_domain = domains;
if (update_domains(domains, board, i, j, k)) // no conflict
{
board[i][j] = k; // the supposedly valid number is tried
if (solve(domains, board, i, j + 1)) // if no further conflicts arise, this is the correct solution
return true;
board[i][j] = 0;
}
// if the number leads to a conflict (in an influenced cell, no number can be placed anymore), it will not be used
domains = pre_updated_domain;
}
return false; // if none of the n numbers is valid, a conflict has occurred
}
Мой текущий результат:
5 3 0 0 7 0 0 0 0
6 0 0 1 9 5 0 0 0
0 9 8 0 0 0 0 6 0
8 0 0 0 6 0 0 0 3
4 0 0 8 0 3 0 0 1
7 0 0 0 2 0 0 0 6
0 6 0 0 0 0 2 8 0
0 0 0 4 1 9 0 0 5
0 0 0 0 8 0 0 7 9
5 3 0 0 7 0 0 0 0
6 0 0 1 9 5 0 0 0
0 9 8 0 0 0 0 6 0
8 0 0 0 6 0 0 0 3
4 0 0 8 0 3 0 0 1
7 0 0 0 2 0 0 0 6
0 6 0 0 0 0 2 8 0
0 0 0 4 1 9 0 0 5
0 0 0 0 8 0 0 7 9
Я ожидал:
5 3 4 6 7 8 9 1 2
6 7 2 1 9 5 3 4 8
1 9 8 3 4 2 5 6 7
8 5 9 7 6 1 4 2 3
4 2 6 8 5 3 7 9 1
7 1 3 9 2 4 8 5 6
9 6 1 5 3 7 2 8 4
2 8 7 4 1 9 6 3 5
3 4 5 2 8 6 1 7 9
комментарии показывают, что у вас есть четкое представление о том, что должен делать ваш код. Теперь вам нужно писать тесты и просматривать код с помощью отладчика, чтобы увидеть, где он делает что-то еще.
кстати, я не вижу смысла использовать здесь std::bitset
. Кажется, вы используете его так же, как массив логических значений, и для этого более удобен std::array<bool,9>
(например, у него есть итераторы, вы можете отменить его с помощью std::reverse
и т. д.)
Было бы полезно, если бы вы перевели все комментарии и строковые литералы на английский язык.
Ему не нравится 1
в board[0][7]
, который является правильным номером: godbolt.org/z/v45Go4Gv1 Но он признает, что это единственно возможное значение (не пытается 2
)
У вашей функции update_domain
возникла проблема при проверке последнего числа в строке, столбце или поле 3x3.
Основная проблема заключается в том, как вы реализовали возврат. Для возврата вы правильно отменяете ход с помощью board[i][j] = 0;
, но не отменяете эффект, который этот ход оказал на domains
. Это означает, что вы только уменьшаете возможности, которые допускает domains
, но никогда не «открываете» возможности после того, как отбили ход. В результате вы, скорее всего, откажетесь от действий, которые на самом деле приводят к решению.
Как и в случае с обновлениями, которые вы выполняете для domains
, вы теряете имевшуюся ранее информацию (вполне возможно, что бит установки уже был установлен до вызова set(k - 1)
— или нет, вы не знаете), нет простого способа напишите функцию undo
, которая восстановит эти биты до того состояния, в котором они были раньше.
Чтобы решить эту проблему, вы можете, например, отслеживать числа, которые больше не доступны для определенной ячейки, и вести подсчет каждого, чтобы знать, сколько (до 3) предыдущих ходов сделали это число невозможным. Затем при возврате вы можете уменьшить этот счетчик. Только когда оно становится равным 0, это число снова становится возможным для этой ячейки.
Я не согласен (или плохо понимаю) @trincot: для меня "domains = pre_updated_domain;" «отменяет влияние, которое переезд оказал на домены».
Точкой блокировки я считаю то, что функция update_domains(...) блокирует изучение возможностей, потому что
Из-за этого ваш код в настоящее время никогда не может закончить заполнение даже первой строки: предполагается, что доска обновлена, но это не тот случай, когда вы вызываете ее вsolve().
Так, переместить доску[i][j] = k; перед if (update_domains... и переместите доску[i][j] = 0; соответственно
5 3 4 6 7 8 9 1 2
6 7 2 1 9 5 3 4 8
1 9 8 3 4 2 5 6 7
8 5 9 7 6 1 4 2 3
4 2 6 8 5 3 7 9 1
7 1 3 9 2 4 8 5 6
9 6 1 5 3 7 2 8 4
2 8 7 4 1 9 6 3 5
3 4 5 2 8 6 1 7 9
большое спасибо!
Лучший способ поблагодарить ответ — проголосовать за него и подтвердить его;)
@Эрик stackoverflow.com/help/someone-answers
к сожалению, пока не могу проголосовать за. Но ваш ответ решил все :)
«К сожалению, мой код не работает должным образом». - это недостаточно подробно. См. также: Что такое отладчик и как он может помочь мне диагностировать проблемы?.