如何解决这个ILP / CP矩阵拼图

时间:2016-01-12 09:17:45

标签: matrix puzzle constraint-programming integer-programming

我正在研究算法,最近发现了有趣的挑战。

它会给我们一些行/列,我们的任务是用整数1~N填充表,它只显示一次,它们的行和列总和等于给定的行/列。

挑战简单示例:

    [ ]  [ ]  [ ]   13
    [ ]  [ ]  [ ]    8
    [ ]  [ ]  [ ]   24
     14   14   17

answer:

    [2]  [6]  [5]   13
    [3]  [1]  [4]    8
    [9]  [7]  [8]   24
     14   14   17

由于

4 个答案:

答案 0 :(得分:4)

据我所知,没有比使用回溯方法更有效地解决这个特定问题的直接算法。

然而,您可以比仅仅枚举所有可能的解决方案更智能地执行此操作。执行此操作的有效方法是约束编程(CP)(或约束逻辑编程(CLP)等派生范例)。基本上它归结为关于你试图减少变量域的问题的约束的推理。

在缩小域名后,您可以进行选择,以后可以回溯。做出这样的选择之后,您再次减少域名,并可能需要做出其他选择。

您可以使用 ECLiPSe (不是IDE,但是使用约束逻辑编程工具):

:- lib(ic).
:- import alldifferent/1 from ic_global.
:- import sumlist/2 from ic_global.

solve(Problem) :-
    problem(Problem,N,LA,LB),
    puzzle(N,LA,LB,Grid),
    print_Grid(Grid).

puzzle(N,LA,LB,Grid) :-
    N2 is N*N,
    dim(Grid,[N,N]),
    Grid[1..N,1..N] :: 1..N2,
    (for(I,1,N), param(N,Grid,LA,LB) do
        Sc is nth1(I,LA),
        Lc is Grid[1..N,I],
        sumlist(Lc,Sc),
        Sr is nth1(I,LB),
        Lr is Grid[I,1..N],
        sumlist(Lr,Sr)
    ),
    term_variables(Grid,Vars),
    alldifferent(Vars),
    labeling(Vars).

print_Grid(Grid) :-
    dim(Grid,[N,N]),
    ( for(I,1,N), param(Grid,N) do
        ( for(J,1,N), param(Grid,I) do
            X is Grid[I,J],
        ( var(X) -> write("  _") ; printf(" %2d", [X]) )
        ), nl
    ), nl.

nth1(1,[H|_],H) :- !.
nth1(I,[_|T],H) :-
    I1 is I-1,
    nth1(I1,T,H).

problem(1,3,[14,14,17],[13,8,24]).

该计划含糊地基于my implementation for multi-sudoku。现在您可以使用ECLiPSe解决问题:

ECLiPSe Constraint Logic Programming System [kernel]
Kernel and basic libraries copyright Cisco Systems, Inc.
and subject to the Cisco-style Mozilla Public Licence 1.1
(see legal/cmpl.txt or http://eclipseclp.org/licence)
Source available at www.sourceforge.org/projects/eclipse-clp
GMP library copyright Free Software Foundation, see legal/lgpl.txt
For other libraries see their individual copyright notices
Version 6.1 #199 (x86_64_linux), Sun Mar 22 09:34 2015
[eclipse 1]: solve(1).
lists.eco  loaded in 0.00 seconds
WARNING: module 'ic_global' does not exist, loading library...
queues.eco loaded in 0.00 seconds
ordset.eco loaded in 0.00 seconds
heap_array.eco loaded in 0.00 seconds
graph_algorithms.eco loaded in 0.03 seconds
max_flow.eco loaded in 0.00 seconds
flow_constraints_support.eco loaded in 0.00 seconds
ic_sequence.eco loaded in 0.01 seconds
ic_global.eco loaded in 0.05 seconds
  2  5  6
  3  1  4
  9  8  7


Yes (0.05s cpu, solution 1, maybe more) ? ;
  5  2  6
  1  3  4
  8  9  7


Yes (0.05s cpu, solution 2, maybe more) ? ;
  2  6  5
  3  1  4
  9  7  8


Yes (0.05s cpu, solution 3, maybe more) ? ;
  3  6  4
  2  1  5
  9  7  8


Yes (0.05s cpu, solution 4, maybe more) ? ;
  6  2  5
  1  3  4
  7  9  8


Yes (0.05s cpu, solution 5, maybe more) ? ;
  6  3  4
  1  2  5
  7  9  8


Yes (0.05s cpu, solution 6, maybe more) ? ;
  2  6  5
  4  1  3
  8  7  9


Yes (0.05s cpu, solution 7, maybe more) ? ;
  4  6  3
  2  1  5
  8  7  9


Yes (0.05s cpu, solution 8, maybe more) ? 
  6  2  5
  1  4  3
  7  8  9


Yes (0.05s cpu, solution 9, maybe more) ? ;
  6  4  3
  1  2  5
  7  8  9


Yes (0.05s cpu, solution 10, maybe more) ? ;

No (0.06s cpu)

只需查询solve(1),约束逻辑编程工具就可以完成其余工作。因此总共有10个解决方案。

请注意,该程序适用于任意N,但是 - 最坏的情况是该程序执行回溯 - 显然程序只能解决合理N的问题。

答案 1 :(得分:3)

哦,当这些小优化问题出现时我真的很喜欢它。他们总是让我想起有一次,在我第一年,我建造了一个可以解决数独游戏的东西,并且玩得很开心!你可能会猜到我从那以后解决了多少数独的数据:)。

现在,您的问题是ILP (Integer Linear Program)。甚至在您阅读该文章之前,您应该注意 ILP是 。将解决方案空间限制为 N Z 是严重限制的,并且通常不存在这样的解决方案!

对于你的问题,手头的任务基本上归结为解决这个问题,

Minimise 0 (arbitrary objective function)

受制于,

x1 + x2 + x3 = 13
x4 + x5 + x6 = 8
x7 + x8 + x9 = 24

x1 + x4 + x7 = 14
x2 + x5 + x8 = 14
x3 + x6 + x9 = 17

而且,

x_i in N, x_i distinct.

以矩阵形式,这些方程成为,

    |1   1   1   0   0   0   0   0   0|
    |0   0   0   1   1   1   0   0   0|
A = |0   0   0   0   0   0   1   1   1|
    |1   0   0   1   0   0   1   0   0|
    |0   1   0   0   1   0   0   1   0|
    |0   0   1   0   0   1   0   0   1|

    |13|
    | 8|
B = |24|
    |14|
    |14|
    |17|

使约束减少到A*x = B。所以我们想要解决的问题现在可以等同地写成,

Minimise 0

受制于,

A * x = B

x in N^7, x_i distinct.

这对你来说难过吗?如果没有,请考虑一下:真正的线条是巨大的,在那条线上,每隔一段时间,就是一个小点。那是一个整数。我们需要其中一些。我们不知道哪些。从本质上讲,一个完美的类比就是在大海捞针中寻找针。

现在,不要绝望,我们非常擅长找到这些ILP针!我只是想让你理解这个问题源于此领域的重大难题。

我想给你工作代码,但我不知道你首选的语言/工具包。如果这只是一个业余爱好者的方法,即使是Excel的求解器也能很好地工作。如果不是,我认为我没有比Willem Van Onsem已经有的更好地表达它了,我想引导你回答它的实现。

答案 2 :(得分:2)

下面是另一个Constraint Programming模型,使用与Willem Van Onsem的解决方案类似的方法,即使用全局约束" all_different",这是一种有效的方法来确保数字在矩阵只分配一次。 ("全局约束"的概念在CP中非常重要,并且有很多研究发现了针对不同类型的常见约束的快速实现。)

这是MiniZinc模型:http://hakank.org/minizinc/matrix_puzzle.mzn

include "globals.mzn"; 
% parameters
int: rows;
int: cols;
array[1..rows] of int: row_sums;
array[1..cols] of int: col_sums;

% decision variables
array[1..rows,1..cols] of var 1..rows*cols: x;

solve satisfy;

constraint
  all_different(array1d(x)) /\
  forall(i in 1..rows) (
    all_different([x[i,j] | j in 1..cols]) /\
    sum([x[i,j] | j in 1..cols]) = row_sums[i]
  )
  /\
  forall(j in 1..cols) (
    all_different([x[i,j] | i in 1..rows]) /\
    sum([x[i,j] | i in 1..rows]) = col_sums[j]
  );

  output [
    if j = 1 then "\n" else " " endif ++
      show_int(2,x[i,j])
    | i in 1..rows, j in 1..cols
  ];

  % Problem instance
  rows = 3;
  cols = 3;
  row_sums = [13,8,24];
  col_sums = [14,14,17];

以下是前两个(10个)解决方案:

 2  5  6
 3  1  4
 9  8  7
 ----------

 5  2  6
 1  3  4
 8  9  7
 ----------
 ...

另一条评论:CP的一个有趣的事情 - 以及一个重要的概念 - 是可以使用几乎相同的模型生成新的问题实例:http://hakank.org/minizinc/matrix_puzzle2.mzn

唯一的区别是以下几行,即更改" row_sums"和" col_sums"决策变量和评论提示。

  array[1..rows] of var int: row_sums; % add "var"
  array[1..cols] of var int: col_sums; % add "var"

  % ...

  % row_sums = [13,8,24];
  % col_sums = [14,14,17];

以下是三个生成的问题实例(9!= 362880可能):

  row_sums: [21, 15, 9]
  col_sums: [19, 15, 11]

  5  9  7
  8  4  3
  6  2  1
  ----------
  row_sums: [20, 16, 9]
  col_sums: [20, 14, 11]

  5  8  7
  9  4  3
  6  2  1
  ----------
  row_sums: [22, 14, 9]
  col_sums: [18, 15, 12]

  5  9  8
  7  4  3
  6  2  1
  ----------

答案 3 :(得分:-1)

我认为回溯算法在这里会很好用。

尽管回溯仍然是“蛮力”,但在平均情况下它可能非常快。例如,解决带有回溯的SUDOKU通常只需要1000-10000次迭代(考虑到O-复杂度为O(9 ^ n),其中n是空的空间,这非常快,因此平均数独有大约9 ^ 60种可能性,其中平均电脑需要多年才能完成。

这个任务有很多规则(数字的唯一性和行/列的总和),这对于bactracking非常有用。更多规则=在每个步骤之后更多地检查并丢弃无法提供解决方案的分支。

这有助于:https://en.wikipedia.org/wiki/Sudoku_solving_algorithms