main content

通过整数规划求解数独谜题:基于问题 -凯发k8网页登录

此示例说明如何使用二元整数规划来求解数独谜题。有关基于求解器的方法,请参阅。

您可能看到过数独谜题。谜题是用 1 到 9 的整数填充 9×9 网格,要求这九个数字中的每个数字在每一行、每一列和每个九宫格(3×3 粗线正方形)中都只出现一次,不能重复。网格已根据提示进行了部分填充,您的任务是填充网格的其余部分。

初始谜题

这是一个带有提示的数据矩阵 b。第一行 b(1,2,2) 表示第 1 行第 2 列的整数提示为 2。第二行 b(1,5,3) 表示第 1 行第 5 列的整数提示为 3。以下是整个矩阵 b

b = [1,2,2;
    1,5,3;
    1,8,4;
    2,1,6;
    2,9,3;
    3,3,4;
    3,7,5;
    4,4,8;
    4,6,6;
    5,1,8;
    5,5,1;
    5,9,6;
    6,4,7;
    6,6,5;
    7,3,7;
    7,7,6;
    8,1,4;
    8,9,8;
    9,2,3;
    9,5,4;
    9,8,2];
drawsudoku(b) % for the listing of this program, see the end of this example.

2009 年发布的 中介绍了此谜题以及一种备选的 matlab® 求解方法。

求解数独谜题有许多手动方法,也有许多编程方法。此示例说明一种使用二元整数规划的简单方法。

这种方法特别简单,因为您无需给出求解算法。只需将数独的规则以及每条提示表示为对解的约束,然后 matlab 就会生成解。

二元整数规划方法

关键思路是将谜题从 9×9 正方形网格转换为一个由二元值(0 或 1)组成的 9×9×9 立方数组。将这个立方数组想象成由 9 个正方形网络上下堆叠在一起,其中每一层对应于 1 到 9 中的一个整数。顶部网格是数组的第一个正方形层,只要解或提示包含整数 1,则该层对应位置就为 1。只要解或提示包含整数 2,第二层对应位置就为 1。只要解或提示包含整数 9,第九层对应位置就为 1。

这种表示非常适合二元整数规划。

此处不需要目标函数,目标函数也可能是常数项 0。问题实际上只是求可行解,也就是满足所有约束的解。但是,为在整数规划求解器内部打破平衡,同时保证求解速度,这里使用一个非常数目标函数。

将数独规则表示为约束

假设解 x 用一个 9×9×9 的二元数组表示。那 x 应具备哪些特性呢?首先,由于二维网格 (i,j) 的每个方格中都有且仅有一个值,因此三维数组项 x(i,j,1),...,x(i,j,9) 中有且仅有一个非零元素。换句话说,对于每个 ij,都满足

k=19x(i,j,k)=1.

类似地,在二维网格的每一行 i 中,1 到 9 中的每个数字都只能出现一次。换句话说,对于每个 ik,都满足

j=19x(i,j,k)=1.

二维网格中的每一列 jj 也具有同样的特性:对于每个 jj 和 k,都满足

i=19x(i,j,k)=1.

3×3 粗线网格具有类似的约束。对于 1i31j3 的网格元素,对于每层 1k9 都满足:

i=13j=13x(i,j,k)=1.

要表示全部九个粗线网格,只需将每个 ij 索引加上 3 或 6:

i=13j=13x(i u,j v,k)=1, 其中 u,vϵ{0,3,6}.

将提示表示为约束

每个初始值(提示)都可以表示为一个约束。假设在 1m9 时,(i,j) 格点处的提示为 m。则 x(i,j,m)=1。约束 k=19x(i,j,k)=1 能确保在 km 时,所有其他 x(i,j,k)=0

以优化问题的形式求解数独

创建一个优化变量 x,它是一个大小为 9×9×9 的二元变量。

x = optimvar('x',9,9,9,'type','integer','lowerbound',0,'upperbound',1);

创建一个具有任意目标函数的优化问题。目标函数可以破坏问题的内在对称性,从而帮助求解器解题。

sudpuzzle = optimproblem;
mul = ones(1,1,9);
mul = cumsum(mul,3);
sudpuzzle.objective = sum(sum(sum(x,1),2).*mul);

将约束表示为:在每个坐标方向上 x 的总和为 1。

sudpuzzle.constraints.consx = sum(x,1) == 1;
sudpuzzle.constraints.consy = sum(x,2) == 1;
sudpuzzle.constraints.consz = sum(x,3) == 1;

还要创建粗线网格总和等于 1 的约束。

majorg = optimconstr(3,3,9);
for u = 1:3
    for v = 1:3
        arr = x(3*(u-1) 1:3*(u-1) 3,3*(v-1) 1:3*(v-1) 3,:);
        majorg(u,v,:) = sum(sum(arr,1),2) == ones(1,1,9);
    end
end
sudpuzzle.constraints.majorg = majorg;

在包含提示的格点处设置下界 1 来包括初始提示值。此设置将对应项的值固定为 1,从而将每个提示数处的解设置为提示数本身。

for u = 1:size(b,1)
    x.lowerbound(b(u,1),b(u,2),b(u,3)) = 1;
end

求解数独谜题。

sudsoln = solve(sudpuzzle)
solving problem using intlinprog.
lp:                optimal objective value is 405.000000.                                           
optimal solution found.
intlinprog stopped at the root node because the objective value is within a gap
tolerance of the optimal value, options.absolutegaptolerance = 0 (the default
value). the intcon variables are integer within tolerance,
options.integertolerance = 1e-05 (the default value).
sudsoln = struct with fields:
    x: [9x9x9 double]

对解进行舍入以确保所有项均为整数,并显示解。

sudsoln.x = round(sudsoln.x);
y = ones(size(sudsoln.x));
for k = 2:9
    y(:,:,k) = k; % multiplier for each depth k
end
s = sudsoln.x.*y; % multiply each entry by its depth
s = sum(s,3); % s is 9-by-9 and holds the solved puzzle
drawsudoku(s)

您可以轻松检查解是否正确。

用于绘制数独谜题的函数

type drawsudoku
function drawsudoku(b)
% function for drawing the sudoku board
%   凯发官网入口首页 copyright 2014 the mathworks, inc. 
figure;hold on;axis off;axis equal % prepare to draw
rectangle('position',[0 0 9 9],'linewidth',3,'clipping','off') % outside border
rectangle('position',[3,0,3,9],'linewidth',2) % heavy vertical lines
rectangle('position',[0,3,9,3],'linewidth',2) % heavy horizontal lines
rectangle('position',[0,1,9,1],'linewidth',1) % minor horizontal lines
rectangle('position',[0,4,9,1],'linewidth',1)
rectangle('position',[0,7,9,1],'linewidth',1)
rectangle('position',[1,0,1,9],'linewidth',1) % minor vertical lines
rectangle('position',[4,0,1,9],'linewidth',1)
rectangle('position',[7,0,1,9],'linewidth',1)
% fill in the clues
%
% the rows of b are of the form (i,j,k) where i is the row counting from
% the top, j is the column, and k is the clue. to place the entries in the
% boxes, j is the horizontal distance, 10-i is the vertical distance, and
% we subtract 0.5 to center the clue in the box.
%
% if b is a 9-by-9 matrix, convert it to 3 columns first
if size(b,2) == 9 % 9 columns
    [sm,sn] = meshgrid(1:9); % make i,j entries
    b = [sn(:),sm(:),b(:)]; % i,j,k rows
end
for ii = 1:size(b,1)
    text(b(ii,2)-0.5,9.5-b(ii,1),num2str(b(ii,3)))
end
hold off
end

相关主题

    网站地图