我正在研究算法,最近发现了有趣的挑战。
它会给我们一些行/列,我们的任务是用整数1~N填充表,它只显示一次,它们的行和列总和等于给定的行/列。
挑战简单示例:
[ ] [ ] [ ] 13
[ ] [ ] [ ] 8
[ ] [ ] [ ] 24
14 14 17
answer:
[2] [6] [5] 13
[3] [1] [4] 8
[9] [7] [8] 24
14 14 17
由于
答案 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