我编写了一个MATLAB程序,使用递归回溯解决方案解决了9 x 9 Sudoku难题,但是递归似乎并未终止。当我暂停调试器并查看开发板时,我发现我的开发板已经包含正确的解决方案。在我的方法中,我逐列地研究了板元素,从(1,1)中的元素1开始,到(9,9)的元素81结束。 checkSudoku
通过查看行,col和3x3子网格来检查数字是否为有效的展示位置。 h
是递归发生的地方。谁能提供有关我的代码出了问题的建议?
function result = h(board, num)
if num >= 82
result = board;
else
if isnan(board(num))
flag = false;
c = ceil(num / 9);
r = num - ((c - 1) * 9);
n = 1;
while (n <= 9) & (~flag)
if checkSudoku(board, r, c, n)
board(num) = n;
product = h(board, num + 1);
if ~isnan(product)
flag = true;
board(num) = n;
else
board(num) = NaN;
n = n + 1;
end
else
n = n + 1;
end
end
if ~flag
result = NaN;
else
result = h(board, num + 1);
end
else
result = h(board, num + 1);
end
end
end
function safe = checkSudoku(board, row, col, num)
r = row;
c = col;
subrow = board(r, :);
subcol = board(:, col);
subBoard = zeros(3, 3);
if any([1 2 3] == r)
if any([1 2 3] == c)
subBoard = board(1:3, 1:3);
elseif any([4 5 6] == c)
subBoard = board(1:3, 4:6);
else
subBoard = board(1:3, 7:9);
end
elseif any([4 5 6] == r)
if any([1 2 3] == c)
subBoard = board(4:6, 1:3);
elseif any([4 5 6] == c)
subBoard = board(4:6, 4:6);
else
subBoard = board(4:6, 7:9);
end
else
if any([1 2 3] == c)
subBoard = board(7:9, 1:3);
elseif any([4 5 6] == c)
subBoard = board(7:9, 4:6);
else
subBoard = board(7:9, 7:9);
end
end
if any(subrow == num)
safe = false;
elseif any(subcol == num)
safe = false;
elseif any(any(subBoard == num))
safe = false;
else
safe = true;
end
end
function solvedBoard = solveSudoku(board)
solvedBoard = h(board, 1);
end
我从麻省理工学院(MITOpenCourseWare)那里取了这个问题和MATLAB文件,这是作业3的可选问题3。可以在here中找到文件和照片。
答案 0 :(得分:1)
即使在简单的情况下,递归函数也可能很难抽象。您的案例除了必须基于先前的迭代来计算事物之外,还具有额外的复杂性,该算法还应该能够回溯一定数量的迭代,然后再继续前进。 >
我举了一个可行的例子,但这不是获得结果的唯一方法。我建议的方法是利用两个 flags 来帮助递归函数知道它的前进方向。您可以没有标志,但是要在评估职能过程中进行更多检查,以评估板的状态。由于具有使用标记的功能,因此我利用它进行了简化。
我强烈建议您阅读return
上的文档,因为它对于这些类型的功能是有用的工具。
现在回答:
起始板:
首先,为了大家的利益,我介绍了未解决的初始董事会。这是一个9x9矩阵,其中包含初始数字和其他地方的NaN
。
unsolvedBoard = [
5 3 NaN NaN 7 NaN NaN NaN NaN
6 NaN NaN 1 9 5 NaN NaN NaN
NaN 9 8 NaN NaN NaN NaN 6 NaN
8 NaN NaN NaN 6 NaN NaN NaN 3
4 NaN NaN 8 NaN 3 NaN NaN 1
7 NaN NaN NaN 2 NaN NaN NaN 6
NaN 6 NaN NaN NaN NaN 2 8 NaN
NaN NaN NaN 4 1 9 NaN NaN 5
NaN NaN NaN NaN 8 NaN NaN 7 9 ] ;
开始条件:
您的算法盲目地遍历了网格的所有99个可能的盒子。问题说明建议您在网格中标识 empty 索引(将其放置在emptyInd
变量中,并使用变量ind
仅遍历这些空索引。
为了进行整合,我修改了主求解器的开始:
function solvedBoard = solveSudoku(board)
emptyInd = find(isnan(board)) ; % find the empty indices in the grid
% this will solve the board recursively
solvedBoard = solverec( board, emptyInd, 1 );
end
现在emptyInd
仅包含51个索引。我们将仅在这些而不是在网格的99个框上进行迭代。
给定框的可能数字:
您的函数checkSudoku(board, row, col, num)
工作正常,但可以简化。您已经在h
函数中将行索引和列索引转换为线性索引,可以在此函数中重用相同类型的计算来了解subrow/subcol/subBoard
的索引。
另请注意,您可以将if
条件与逻辑or
合并以一次检查所有条件。
该功能可以变为:
function safe = checkSudoku(board, row, col, num)
subrow = board(row, :);
subcol = board(:, col);
subSquareRow = (1:3) + 3*(ceil(row/3)-1) ;
subSquareCol = (1:3) + 3*(ceil(col/3)-1) ;
subBoard = board( subSquareRow , subSquareCol );
subBoard = subBoard(:) ; % Reshape into column vector (easier comparison)
% This whole block can be replaced with the line described below
if any(subrow == num) || any(subcol == num) || any(any(subBoard == num))
safe = false;
else
safe = true;
end
% Note that since we are dealing with boolean, the "IF" check above could
% be avoided and simply written as :
% safe = ~( any(subrow == num) || any(subcol == num) || any(any(subBoard == num)) ) ;
end
现在,此函数稍后在递归循环中使用,以检查从1
到9
的数字在给定位置是否有效。您使用了while循环从1
到9
运行。当我们从一开始就知道给定盒子的几个可能的候选者时,我发现检查九个数字很浪费。因此,我编写了一个函数,该函数返回一个框的唯一可能有效数字的列表。如果它仅返回3个可能的数字,则只需要遍历这3个数字,而不必盲目地对它们进行9个以上的处理。
function candidates = getCandidates(board, row, col)
subrow = board(row, :);
subcol = board(:, col);
subSquareRow = (1:3) + 3*(ceil(row/3)-1) ;
subSquareCol = (1:3) + 3*(ceil(col/3)-1) ;
subBoard = board( subSquareRow , subSquareCol );
subBoard = subBoard(:) ; % Reshape into column vector (easier comparison)
% Get the difference of each array compared to a reference line
refval = 1:9 ;
cdrow = setdiff(refval,subrow) ;
cdcol = setdiff(refval,subcol) ;
cdsqr = setdiff(refval,subBoard) ;
% intersection of the three arrays
candidates = intersect( intersect(cdrow,cdcol) , cdsqr ) ;
end
您可以在setdiff
和intersect
上阅读以了解其工作原理。
现在是递归求解器:
此函数正在执行您的h()
函数。您在实施过程中遇到两个主要问题:
if
分支,实际上从未使用过某些路径。即使有效
这很令人困惑,但经常造成混淆也带来了错误。令人沮丧的是,当您的电路板完全解决时,该算法无法检测到该问题,并且无法通过迭代函数调用(最终结果在手)进行工作。您的算法正在寻找解决方案,但是由于这种情况下没有退出门(已完全解决),因此默认情况下将其分配给其他分支,并最终一致地还原最后几次迭代,即使它们是正确的。
对于我们的测试用例和其他几个用例,以下实现似乎可以正常工作。如果需要,可以在其他情况下尝试使用它,只是要知道网格必须是可解的。如果网格不可解,我没有做任何检查或指示,所以我不知道如果在这样的网格上运行会发生什么。
solverec.m
的代码:
function [res, solved, noSolutionFound] = solverec(board,emptyInd,ind,solved)
%% initialise the return flag for first function call
if nargin < 4 ; solved = false ; end
noSolutionFound = false ; % initialise second flag
% check if we are done with all the EmptyInd
if ind>numel(emptyInd) ;
solved = true ;
end
%% Return quickly if the board is already solved
if solved
res = board ;
return ;
end
%% If we are here, we still have to find new emptyInd
% prepare useful indices (row, column & linear index)
num = emptyInd(ind) ;
col = ceil(num / 9);
row = num - ((col - 1) * 9);
% get possible candidates for this box
cd = getCandidates(board, row, col) ;
ncd = numel(cd) ; % number of candidates
if ncd == 0
% no candidate for this box => back track
noSolutionFound = true ;
else
% Try the possible candidates one by one
for k=1:ncd ;
board(num) = cd(k) ; % try one candidate
% move on to next emptyInd
[res, solved, noSolutionFound] = solverec(board,emptyInd,ind+1,solved) ;
% bail out if solved
if solved ; return ; end
% otherwise, reset this emptyInd before trying next candidate
if noSolutionFound
board(num) = NaN ;
end
end
end
if noSolutionFound
% We have exhausted all possible candidates for this emptyInd
% We have to back track further
board(num) = NaN ;
res = board ;
return % this one is actually optional, the function will "return"
% anyway at the end of the "if" block.
end
end
测试:
>> solvedBoard = solveSudoku(unsolvedBoard)
solvedBoard =
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
我将让您编写一个可选的displaySudoku(board)
函数作为练习;)