数独解决方案
Write a program to solve a Sudoku puzzle by filling the empty cells.
A sudoku solution must satisfy all of the following rules:
- Each of the digits
1-9
must occur exactly once in each row. - Each of the digits
1-9
must occur exactly once in each column. - Each of the the digits
1-9
must occur exactly once in each of the 93x3
sub-boxes of the grid.
Empty cells are indicated by the character '.'
.
Note:
- The given board contain only digits
1-9
and the character'.'
. - You may assume that the given Sudoku puzzle will have a single unique solution.
- The given board size is always
9x9
.
解决方法:更新反馈与回溯法
This is one of the fastest Sudoku solvers I've ever written. It is compact enough - just 150 lines of C++ code with comments. I thought it'd be interesting to share it, since it combines several techniques like reactive network update propagation and backtracking with very aggressive pruning. 更新传播反馈网络,回溯,剪枝
The algorithm is online - it starts with an empty board and as you add numbers to it, it starts solving the Sudoku.
开始board是空的,一旦向里面填充数字,就开始解决数独
Unlike in other solutions where you have bitmasks of allowed/disallowed values per row/column/square, this solution track bitmask for every(!) cell, forming a set of constraints for the allowed values for each particular cell. Once a value is written into a cell, new constraints are immediately propagated to row, column and 3x3 square of the cell. If during this process a value of other cell can be unambiguously deduced - then the value is set, new constraints are propagated, so on.... You can think about this as an implicit reactive network of cells. 使用为掩码来约束,每个位置可以填充的数字,一旦有新数字填入,约束会更新,(行,列,以及3*3),在更新过程中,如果可以选择的数字降为1,则这个数字会被设置,同时更新约束(行,列,3*3)
If we're lucky (and we'll be lucky for 19 of 20 of Sudokus published in magazines) then Sudoku is solved at the end (or even before!) processing of the input. 如果很幸运,数字都被填充了,接结束了
Otherwise, there will be empty cells which have to be resolved. Algorithm uses backtracking for this purpose. To optimize it, algorithm starts with the cell with the smallest ambiguity. This could be improved even further by using priority queue (but it's not implemented here). Backtracking is more or less standard, however, at each step we guess the number, the reactive update propagation comes back into play and it either quickly proves that the guess is unfeasible or significantly prunes the remaining search space. 否则这里仍有空的地方。算法中使用回溯法来填充这些剩余空的地方,从numPossibilities小的开始回溯。
It's interesting to note, that in this case taking and restoring snapshots of the compact representation of the state is faster than doing backtracking rollback by "undoing the moves". 在回溯前,保存cells的状态,为了上一次回溯对cells的影响。
注意: bitset array pair make_pair
class Solution {
public:
struct cell{
uint8_t value;
uint8_t numPossibilities;
bitset<10> constrains;
cell():value(0),numPossibilities(9),constrains(){};
};
array<array<cell,9>,9> cells;
bool setValue(int i,int j,int val)
{
cell &c = cells[i][j];
if(c.value == val)
{
return true;
}
if(c.constrains[val])
{
return false;
}
c.constrains = bitset<10>(0x3FE);
c.constrains.reset(val);
c.numPossibilities = 1;
c.value = val;
for(int k = 0; k < 9; k++)
{
/* update row */
if(k != j && !updateConstrains(i,k,val))
{
return false;
}
/* update col */
if(k != i && !updateConstrains(k,j,val))
{
return false;
}
/* update 3*3 */
int lx = i/3 * 3 + k/3;
int ly = j/3 * 3 + k%3;
if(lx != i && ly != j && !updateConstrains(lx,ly,val))
{
return false;
}
}
return true;
}
bool updateConstrains(int x, int y, int val)
{
cell &c = cells[x][y];
if(c.constrains[val])
{
return true;
}
if(c.value == val)
{
return false;
}
c.constrains.set(val);
if(--c.numPossibilities > 1)
{
return true;
}
/* fill cells[i][j] using the left 1 */
for(int v = 1; v <= 9; v++)
{
if(!c.constrains[v])
return setValue(x,y,v);
}
return false;
}
vector<pair<int,int> > bt;
bool findValuesforEmptyCells()
{
bt.clear();
for(int i = 0; i < 9; i++)
{
for(int j = 0; j < 9; j++)
{
if(!cells[i][j].value)
{
bt.push_back(make_pair(i,j));
}
}
}
sort(bt.begin(),bt.end(),[this](const pair<int,int> &a, const pair<int,int> &b){
return cells[a.first][a.second].numPossibilities < cells[b.first][b.second].numPossibilities;
});
return backtrace(0);
}
bool backtrace(int k)
{
if(k >= bt.size())
return true;
int x = bt[k].first;
int y = bt[k].second;
if(cells[x][y].value)
{
return backtrace(k + 1);
}
auto cons = cells[x][y].constrains;
array<array<cell,9>,9> snapshot(cells);
for(int i = 1; i <= 9; i++)
{
if(!cons[i]){
if(setValue(x,y,i)){
if(backtrace(k + 1))
return true;
}
cells = snapshot;
}
}
return false;
}
void solveSudoku(vector<vector<char>>& board) {
/* clear cells */
cells = array<array<cell,9>,9>();
/* init */
for(int i = 0; i < 9; i++)
{
for(int j = 0; j < 9; j++)
{
if(board[i][j] != '.' && !setValue(i,j,board[i][j]-'0'))
{
return; /* incorrect or unsolvable */
}
}
}
/* find empty value and backtrace */
if(!findValuesforEmptyCells())
{
return; /* unsolvable */
}
/* copy the solution back to the board */
for(int i = 0; i < 9; i++){
for(int j = 0; j < 9; j++){
if(cells[i][j].value)
{
board[i][j] = cells[i][j].value + '0';
}
}
}
}
};