Ruby 如何使用回溯算法使数独解算器返回?
这个周末,我开发了一个基于回溯算法的数独解算器()。数独被加载到一个包含81个整数(9x9网格)的数组中,其中0是空点。有一个Ruby 如何使用回溯算法使数独解算器返回?,ruby,algorithm,sudoku,backtracking,Ruby,Algorithm,Sudoku,Backtracking,这个周末,我开发了一个基于回溯算法的数独解算器()。数独被加载到一个包含81个整数(9x9网格)的数组中,其中0是空点。有一个valid?方法可以检查sudoku\u arr是否是有效的数独 如下所示:在下一个空点上尝试值,检查它是否是有效的数独,如果不增加值1(最多9),如果有效,继续在下一个点上尝试第一个值,如果不增加前一个0的值 因此,我们必须跟踪上一个数组,这就是我出错的地方,我不确定它是否可以解决。下面代码中不起作用的部分是SudokuSolver类中的solve\u by\u inc
valid?
方法可以检查sudoku\u arr
是否是有效的数独
如下所示:在下一个空点上尝试值,检查它是否是有效的数独,如果不增加值1(最多9),如果有效,继续在下一个点上尝试第一个值,如果不增加前一个0的值
因此,我们必须跟踪上一个数组,这就是我出错的地方,我不确定它是否可以解决。下面代码中不起作用的部分是SudokuSolver
类中的solve\u by\u increasing\u value\u previous\u index
。代码如下:
require 'pp'
sudoku_str = "
+-------+-------+-------+
| _ 6 _ | 1 _ 4 | _ 5 _ |
| _ _ 8 | 3 _ 5 | 6 _ _ |
| 2 _ _ | _ _ _ | _ _ 1 |
+-------+-------+-------+
| 8 _ _ | 4 _ 7 | _ _ 6 |
| _ _ 6 | _ _ _ | 3 _ _ |
| 7 _ _ | 9 _ 1 | _ _ 4 |
+-------+-------+-------+
| 5 _ _ | _ _ _ | _ _ 2 |
| _ _ 7 | 2 _ 6 | 9 _ _ |
| _ 4 _ | 5 _ 8 | _ 7 _ |
+-------+-------+-------+"
class SudokuException < StandardError
attr_reader :sudoku_arr
def initialize(message, sudoku_arr)
super(message)
@sudoku_arr = sudoku_arr
end
end
class Sudoku
attr_accessor :sudoku_arr,
:index_of_tried_value,
:tried_value
def initialize(sudoku_arr, index_of_tried_value, tried_value)
@sudoku_arr = sudoku_arr
@index_of_tried_value = index_of_tried_value
@tried_value = tried_value
end
def rows_valid?
rows_have_unique_values?(@sudoku_arr)
end
def columns_valid?
rows_have_unique_values?(@sudoku_arr.each_slice(9).to_a.transpose.flatten!)
end
def squares_valid?
tmp_a = @sudoku_arr.each_slice(3).to_a
rows_have_unique_values?(
(tmp_a[0] << tmp_a[3] << tmp_a[6] << tmp_a[1] << tmp_a[4] << tmp_a[7] <<
tmp_a[2] << tmp_a[5] << tmp_a[8] << tmp_a[9] << tmp_a[12] << tmp_a[15] <<
tmp_a[10] << tmp_a[13] << tmp_a[16] << tmp_a[11] << tmp_a[14] << tmp_a[17] <<
tmp_a[18] << tmp_a[21] << tmp_a[24] << tmp_a[19] << tmp_a[22] << tmp_a[25] <<
tmp_a[20] << tmp_a[23] << tmp_a[26]).flatten!)
end
def valid?
rows_valid? && columns_valid? && squares_valid?
end
def rows_have_unique_values?(arr)
(arr[0,9]- [0]).uniq.size == (arr[0,9]- [0]).size &&
(arr[9,9]- [0]).uniq.size == (arr[9,9]- [0]).size &&
(arr[18,9]-[0]).uniq.size == (arr[18,9]-[0]).size &&
(arr[27,9]-[0]).uniq.size == (arr[27,9]-[0]).size &&
(arr[36,9]-[0]).uniq.size == (arr[36,9]-[0]).size &&
(arr[45,9]-[0]).uniq.size == (arr[45,9]-[0]).size &&
(arr[54,9]-[0]).uniq.size == (arr[54,9]-[0]).size &&
(arr[63,9]-[0]).uniq.size == (arr[63,9]-[0]).size &&
(arr[72,9]-[0]).uniq.size == (arr[72,9]-[0]).size
end
end
class SudokuSolver
attr_accessor :sudoku_arr,
:indeces_of_zeroes
def initialize(str)
@sudoku_arr = str.gsub(/[|\+\-\s]/,"").gsub(/_/,'0').split(//).map(&:to_i)
@indeces_of_zeroes = []
@sudoku_arr.each_with_index { |e,index| @indeces_of_zeroes << index if e.zero? }
end
def solve
sudoku_arr = @sudoku_arr
try_index = @indeces_of_zeroes[0]
try_value = 1
sudoku = Sudoku.new(sudoku_arr, try_index, try_value)
solve_by_increasing_value(sudoku)
end
def solve_by_increasing_value(sudoku)
if sudoku.tried_value < 10
sudoku.sudoku_arr[sudoku.index_of_tried_value] = sudoku.tried_value
if sudoku.valid?
pp "increasing_index..."
solve_by_increasing_index(sudoku)
else
pp "increasing_value..."
sudoku.tried_value += 1
solve_by_increasing_value(sudoku)
end
else
pp "Increasing previous index..."
solve_by_increasing_value_previous_index(sudoku)
end
end
def solve_by_increasing_index(sudoku)
if sudoku.sudoku_arr.index(0).nil?
raise SudokuException(sudoku.sudoku_arr.each_slice(9)), "Sudoku is solved."
end
sudoku.index_of_tried_value = sudoku.sudoku_arr.index(0)
sudoku.tried_value = 1
solve_by_increasing_value(sudoku)
end
def solve_by_increasing_value_previous_index(sudoku)
# Find tried index and get the one before it
tried_index = sudoku.index_of_tried_value
previous_index = indeces_of_zeroes[indeces_of_zeroes.index(tried_index)-1]
# Setup previous Sudoku so we can go back further if necessary:
# Set tried index back to zero
sudoku.sudoku_arr[tried_index] = 0
# Set previous index
sudoku.index_of_tried_value = previous_index
# Set previous value at index
sudoku.tried_value = sudoku.sudoku_arr[previous_index]
pp previous_index
pp sudoku.tried_value
# TODO Throw exception if we go too far back (i.e., before first index) since Sudoku is unsolvable
# Continue business as usual by increasing the value of the previous index
solve_by_increasing_value(sudoku)
end
end
sudoku_solver = SudokuSolver.new(sudoku_str)
sudoku_solver.solve
require'pp'
数独
+-------+-------+-------+
| _ 6 _ | 1 _ 4 | _ 5 _ |
| _ _ 8 | 3 _ 5 | 6 _ _ |
| 2 _ _ | _ _ _ | _ _ 1 |
+-------+-------+-------+
| 8 _ _ | 4 _ 7 | _ _ 6 |
| _ _ 6 | _ _ _ | 3 _ _ |
| 7 _ _ | 9 _ 1 | _ _ 4 |
+-------+-------+-------+
| 5 _ _ | _ _ _ | _ _ 2 |
| _ _ 7 | 2 _ 6 | 9 _ _ |
| _ 4 _ | 5 _ 8 | _ 7 _ |
+-------+-------+-------+"
类SudokuException (tmp_a[0]我还没有仔细阅读您的代码,但是回溯算法可以归结为以下几点:
int solve(char board[81]) {
int i, c;
if (!is_valid(board)) return 0;
c = first_empty_cell(board);
if (c<0) return 1; /* board is full */
for (i=1; i<=9; i++) {
board[c] = i;
if (solve(board)) return 1;
}
board[c] = 0;
return 0;
}
int solve(字符板[81]){
int i,c;
如果(!is_valid(board))返回0;
c=第一个空单元(板);
如果(c有一种算法叫做knuth发明的“舞蹈链接”,它可以应用于数独。
索杜科问题可以通过精确覆盖问题来解决
这是我的代码来解决精确的封面问题,它也可以解决数独
回溯部分在dlx()中
const int INF=0x3f3f;
常数int T=9;
常数int N=T*T*T+10;
常数int M=T*T*T*4+T*T*4+10;
int-id;
int L[M]、R[M]、U[M]、D[M];
int ANS[N],和[N],列[M],行[M],H[N];
结构链接
{
跳舞链接(){}
跳舞链接(int n,int m)
{
对于(int i=0;iThanks.是的。我的问题是,在将一个值重置为0后,如何进一步返回?我的意思是,你转到上一个值,然后增加它?或者等待…你继续第一个值,然后将其增加1?!这似乎是正确的*…你如何记得你在分支中的位置?两个变量(索引和值)?/edit,它不会,因为你不能丢弃整个分支…你应该返回并将上一个索引上的值增加1。如果这不起作用,则增加前一个索引上的值。你如何跟踪?@user2609980这一切都在堆栈上完成。行board[c]=0;
将当前单元格的值恢复为“未填充”并在solve()的调用迭代中将值0(=“not solved”)返回给for
循环
,其中考虑了前一个未填充的单元格。如果前一个单元格上的所有测试都失败,那么前一个单元格也将重置为零,并在这之前为该单元格尝试一个新值。诸如此类……您好,您的回答帮了我很大的忙,帮助我确定了问题。我不认为问题是在数独上存储了“尝试”值的索引因为我只有一个实例。但问题是,回溯时我没有用1增加值。通过添加+1,代码似乎可以工作:#在索引sudoku处设置上一个值。尝试过的值=sudoku.sudoku\u arr[上一个索引]+1
,不幸的是,它没有结束数独游戏,因为有一个堆栈级别到深度的错误
,你看到解决这个问题的方法了吗。PS我接受你的答案,因为你基本上回答了它。:-)谢谢。谢谢:-)如果正确回溯,调用堆栈的深度只能达到81级,并且永远不会溢出。听起来您仍然在使用全局变量来存储搜索树中的位置,这是错误的。您必须在搜索例程中使用局部变量(如上面示例代码中的i
)如果你想让它正常工作。(好吧,除非你创建一个包含81个元素的数组来存储每个单元格中的搜索进度。但是为什么要这么做呢?)使用你更简单的算法,它就可以工作了!我看到了SudokuException.:-)
const int INF = 0x3f3f3f3f;
const int T = 9;
const int N = T*T*T+10;
const int M = T*T*T*4+T*T*4+10;
int id;
int L[M],R[M],U[M],D[M];
int ANS[N],SUM[N],COL[M],ROW[M],H[N];
struct Dancing_links
{
Dancing_links() {}
Dancing_links(int n,int m)
{
for(int i=0; i<=m; i++)
{
SUM[i] = 0;
L[i+1] = D[i] = U[i] = i;
R[i]=i+1;
}
L[m+1]=R[m]=0,L[0]=m,id=m+1;
clr(H,-1);
}
void remove(int c)
{
L[R[c]] = L[c];
R[L[c]] = R[c];
for(int i=D[c]; i!=c; i=D[i])
for(int j=R[i]; j!=i; j=R[j])
{
U[D[j]] = U[j];
D[U[j]] = D[j];
SUM[COL[j]]--;
}
}
void resume(int c)
{
for(int i=U[c]; i!=c; i=U[i])
for(int j=L[i]; j!=i; j=L[j])
{
U[D[j]] = D[U[j]] = j;
SUM[COL[j]]++;
}
L[R[c]] = R[L[c]] = c;
}
void add(int r,int c)
{
ROW[id] = r,COL[id] = c;
SUM[c]++;
D[id] = D[c],U[D[c]] = id,U[id] = c,D[c] = id;
if(H[r] < 0)
H[r] = L[id] = R[id] = id;
else
R[id] = R[H[r]],L[R[H[r]]] = id,L[id] = H[r],R[H[r]] = id;
id++;
}
int dlx(int k)
{
if(R[0] == 0)
{
/*output the answer*/return k;
}
int s=INF,c;
for(int i=R[0]; i; i=R[i])
if(SUM[i] < s)
s=SUM[c=i];
remove(c);
for(int r=D[c]; r!=c; r=D[r])
{
ANS[k] = ROW[r];
for(int j=R[r]; j!=r; j=R[j]) remove(COL[j]);
int tmp = dlx(k+1);
if(tmp != -1) return tmp; //delete if multipal answer is needed
for(int j=L[r]; j!=r; j=L[j]) resume(COL[j]);
}
resume(c);
return -1;
}
};