Matlab 数独解算器递归回溯未终止
我编写了一个MATLAB程序,使用递归回溯解决方案解决了一个9 x 9的数独难题,但递归似乎没有终止。当我暂停调试器并查看电路板时,我发现我的电路板已经包含了正确的解决方案。在我的方法中,我一列一列地处理board元素,从(1,1)中的元素1开始,到(9,9)中的元素81结束Matlab 数独解算器递归回溯未终止,matlab,sudoku,Matlab,Sudoku,我编写了一个MATLAB程序,使用递归回溯解决方案解决了一个9 x 9的数独难题,但递归似乎没有终止。当我暂停调试器并查看电路板时,我发现我的电路板已经包含了正确的解决方案。在我的方法中,我一列一列地处理board元素,从(1,1)中的元素1开始,到(9,9)中的元素81结束checkSudoku通过查看行、列和3x3子网格检查数字是否为有效位置h是递归发生的地方。有人能就我的代码哪里出错提供建议吗 function result = h(board, num) if num >= 82
checkSudoku
通过查看行、列和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
我从MITOpenCourseWare中取出问题和MATLAB文件,作业3可选问题3。可以找到文件和照片。即使在简单的情况下,递归函数也很难抽象。您的案例具有额外的复杂性,因为除了必须根据以前的迭代计算外,算法还应该能够在继续前进之前回溯一定数量的迭代 我举了一个有效的例子,但这不是实现结果的唯一途径。我建议的方法是使用两个标志来帮助递归函数知道它的方向。您可以不使用标志,但需要在函数期间进行更多检查,以评估电路板的状态。因为有使用标志的功能,所以我利用它来简化 我强烈建议您阅读上的文档,因为它是这些类型函数的有用工具 现在回答:
起跑板: 首先,为了大家的利益,我提出了启动未解决的董事会。它是一个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个可能的框上盲目迭代。问题陈述建议您识别网格中的空索引(放置在
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
函数中将行和列索引转换为线性索引,您可以在此函数中重复使用相同类型的计算,以了解subcol/subBoard
的索引。
还请注意,您可以将if
条件与逻辑条件合并,以一次检查所有条件。
该功能可以变成:
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
你可以不断地阅读,了解它是如何工作的
现在是递归解算器: 此函数正在执行
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
我将让您编写可选的
显示数独(board)
函数作为练习;)board变量是什么样子的?您好,board是一个9 x 9矩阵。我从MITOpenCourseWare中取出问题和MATLAB文件,作业3可选问题3。此链接中的文件和照片无法附加图像,因为没有足够的信誉点:p这是因为即使在电路板完成时,您也没有检查该文件和照片,或者没有标志指示它,因此递归从未停止。应始终编写递归例程以实现平滑返回。如果你需要返回一个告诉调用例程返回的FoundIt
标志,那么就这样做。对Matlab来说是新的,所以我不太确定return关键字是如何使用的,但这是否意味着我应该在最顶部添加一个If语句来检查整个电路板是否已填充且有效(终止条件),如果是的话,写“return”关键字?我要终止的基本情况实际上是(如果num>=82),但似乎是这样
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
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