【发布时间】:2016-05-23 11:37:05
【问题描述】:
考虑以下谜题:
一个单元格被标记或未标记。谜题右侧和底部的数字表示特定行或列的总和。单元格对其行和列的总和有贡献(如果标记):位置 (i,j) 的单元格对列总和贡献 i,对行总和贡献 j。比如上图的第一行,标记了第 1、2、5 个单元格。它们对行总和贡献 1 + 2 + 5(因此总计 8),对它们的列总和贡献 1。
我在 ECLiPSe CLP 中有一个求解器来解决这个难题,我很想为它编写一个自定义启发式算法。
我认为最容易开始的单元格是那些列和行提示尽可能低的单元格。一般来说,N 越低,将N 写为 1 和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 快。我怀疑这是因为它的实施方式很糟糕。关于如何做得更好的任何想法?还是我认为这种启发式应该是一个巨大的改进的感觉是不正确的?
【问题讨论】:
-
要评估您的启发式方法,首先要做的是比较回溯的数量。当使用
search/6时,您可以使用backtrack(Count)选项来获取此号码。使用您自己的搜索策略,请参阅 eclipseclp.org/doc/tutorial/tutorial088.html#toc82 了解如何计算等效计数。 -
这是个好主意。我将为我自己的搜索实现它,尽管使用
search/6并将它传递给我自己的搜索谓词会更容易。当您将输入传递给所述谓词时,您有什么想法可以完成这项工作吗?我无法让它仅使用search/6需要的 2 个参数谓词。 -
您可以在代码中调用
search/6来代替foreach(Var,Out)循环。 -
顺便说一句,您的排序应该类似于
sort(3,=<,Terms,SortedTerms),否则您将丢失具有非唯一排序键(TotalSum)的列表元素。 -
非常感谢您的帮助。我实现了你链接的教程,它一直为高达 250x250 的谜题提供 0 个回溯,我认为这非常奇怪。事实证明,其他变量选择启发式方法也使用 0 个回溯。那是search/6,所以它不能是回溯实现。令我惊讶的是,在这种情况下,约束传播是如此强大。
标签: prolog clpfd eclipse-clp