考虑以下难题:
一个单元格被标记或未标记。谜题右侧和底部的数字表示特定行或列的总和。单元格对其行和列的总和有贡献(如果标记):位置 (i,j) 的单元格对列总和贡献 i,对行总和贡献 j。比如上图的第一行,标记了第 1、2、5 个单元格。这些对行总和贡献 1 + 2 + 5(因此总计 8),对它们的列总和贡献 1。
我在 ECLiPSe CLP 中有一个求解器来解决这个难题,我很想为它编写一个自定义启发式算法。
我认为最容易开始的单元格是那些列和行提示尽可能低的单元格。一般来说,值越低,写为 1 和 之间的自然数之和的N
可能性就越小。在这个谜题的上下文中,这意味着具有最低的单元格出错的可能性最低,因此回溯更少。N
N
column hint + row hint
在实现中,我有一个NxN
代表棋盘的数组,以及两个代表提示的大小为 N 的列表。(侧面和底部的数字。)
我看到两个选项:
为search/6编写一个自定义选择谓词。但是,如果我理解正确的话,我只能给它2个参数。没有办法计算给定变量的行+列总和,因为我需要能够将它传递给谓词。我需要4个参数。
忽略 search/6 并编写自己的标记方法。这就是我现在的方式,请参见下面的代码。
它采用棋盘(NxN
包含所有决策变量的数组)、提示列表并返回包含所有变量的列表,现在根据它们的行 + 列总和进行排序。但是,如您所见,这可能不会变得更麻烦。为了能够排序,我需要将总和附加到每个变量,但为了做到这一点,我首先需要将其转换为还包含所述变量坐标的术语,以便我转换回变量为排序完成后...
lowest_hints_first(Board,RowArr,ColArr,Out) :-
dim(Board,[N,N]),
dim(OutBoard,[N,N]),
( multifor([I,J],[1,1],[N,N]), foreach(Term,Terms), param(RowArr,ColArr) do
RowHint is ColArr[I],
ColHint is RowArr[J],
TotalSum is RowHint + ColHint,
Term = field(I,J,TotalSum)
),
sort(3,<,Terms,SortedTerms), % Sort based on TotalSum
terms_to_vars(SortedTerms,Board,Out), % Convert fields back to vars...
( foreach(Var,Out) do
indomain(Var,max)
).
terms_to_vars([],_,[]).
terms_to_vars([field(I,J,TotalSum)|RestTerms],Vars,[Out|RestOut]) :-
terms_to_vars(RestTerms,Vars,RestOut),
Out is Vars[I,J].
最后,这种启发式方法几乎不比 input_order 快。我怀疑这是因为它的实施方式很糟糕。关于如何做得更好的任何想法?或者我认为这种启发式应该是一个巨大的改进是不正确的?