如何确定矩阵的所有给定坐标都已连接?

时间:2016-09-13 04:47:06

标签: prolog clpfd

给定网格,我如何确定网格的元素是否都在一个区域中。在下面的情况是正确的,因为矩阵中的每个元素都有一个邻居。

例1:

gridneighbours([[1,1],[1,2],[1,3],[2,1],[2,2],[2,3],[3,1],[4,1],[4,2]]).
true.

然而在我的第二个例子中,例2:

gridneighbours([[1,1],[1,2],[1,3],[1,4],[1,5],[1,6],[3,1],[4,1],[4,2]]).
false.

这是错误的,因为[3,1],[4,1],[4,2]与前面的元素不相交。 最初我尝试使用Prolog中的子集通过简单地在X或Y中添加或减去来检查旁边的现有元素,但是这不起作用,因为分割区域的元素将彼此相邻。对角线也不算是彼此相邻。

更新后,添加了代码: 这是我得到的:

%Check right
neighbourcheck([X,Y|_],S) :- X1 is X + 1, subset([[X1,Y]],S).
%Check left
neighbourcheck([X,Y|_],S) :- X1 is X - 1, subset([[X1,Y]],S).
%Check Up
neighbourcheck([X,Y|_],S) :- Y1 is Y + 1, subset([[X,Y1]],S).
%Check Down
neighbourcheck([X,Y|_],S) :- Y1 is Y - 1, subset([[X,Y1]],S).
% Iterate through all sublists and check for neighbours
gridneighbour(S) :- forall(member(X,S), neighbourcheck(X,S)).

这不起作用的原因是因为子集不关心我们是否与另一个脱节的元素匹配。即[3,1]与[4,1]匹配。 运行此代码并使用上面的示例给出:

  • 示例1:True
  • 例2:真(显然这应该是假的,因为[3,1],[4,1]和[4,2]是分开的。)

2 个答案:

答案 0 :(得分:3)

一种天真的方法可以概括如下:

  • 从两组积分(列表?)开始:您知道的积分属于某个地区Region,其余为Rest。一开始,您可以选择任何一个属于Region的点,剩下的就是Rest
  • Rest中查找与Region中任意点相邻的点。
    • 如果找到邻居,请将其从Rest移至Region并重复
    • 如果你找不到邻居,请停止
  • 如果最后Rest中有分数,则 区域。

以下是定义neighbors/2的简单方法:

neighbors([X1,Y1], [X2,Y2]) :-
    abs(X1-X2) + abs(Y1-Y2) =:= 1.

您可以通过简单地尝试每种可能的组合,在一个列表中查找另一个列表中某个点的邻居的点:

% add_to_region(+Region0, +Rest0, -Region, -Rest)
%% Look in Rest0 for a neighbor to Region0;
%% Region is Region0 with the neighbor,
%% Rest is Rest0 without it
add_to_region(Region, Rest0, [B|Region], Rest) :-
    member(A, Region),
    select(B, Rest0, Rest),
    neighbors(A, B).

对member / 2的调用通过回溯来选择Region中的每个点。 select / 3的调用选择Rest0到B中的每个点,其余的点在Rest中。如果这两个点是邻居,则B被添加到Region的前面。

如果Rest中没有更多邻居到当前区域,则会失败,如果有,则至少成功一次。您可能希望使用once(add_to_region(Region0, Rest0, Region, Rest))来调用它,这样您就不会有不必要的选择点。使用您的示例:

?- once(
      add_to_region(
         [[1,1],[1,2],[1,3],[2,1]],
         [[2,2],[2,3],[3,1],[4,1],[4,2]],
         Region, Rest)).
Region = [[2, 2], [1, 1], [1, 2], [1, 3], [2, 1]],
Rest = [[2, 3], [3, 1], [4, 1], [4, 2]].

查看从[2,2]中挑选Rest并添加到Region的方式。

?- add_to_region(
   [[1,1],[1,2],[1,3],[1,4],[1,5],[1,6]],
   [[3,1],[4,1],[4,2]],
   Region, Rest).
false.

此操作失败,因为Rest中的所有点都不是Region中任何一点的邻居。

修改

如上所述肯定是可行的,但稍作修改,我们可以在Prolog中使用更容易实现的算法。它是这样的:

set_region_rest(+Set, -Region, -Rest)

  • 将点集排序为标准的术语顺序;现在你有一个ordset
  • 将此集拆分为Region和不属于它的Rest

要进行拆分,我们将保留一个额外的列表。我们将其称为Open节点列表:我们还没有探索过的节点。在开始时,输入列表的第一个元素是唯一的开放节点。 其余元素按原样传递。 最后两个参数,Region和Rest,是输出参数。

open_set_closed_rest(Open, Set, Closed, Rest)

  • 如果Open集合为空,那么Closed集合的其余部分(现在是Region)也是如此;剩下的Set就是Rest。
  • 否则:
    • 从打开列表中取出第一对坐标;立即把它放在封闭坐标的前面。
    • 尝试在坐标集中找到第一对的任何邻居;如果你发现任何,将它们附加到Open集的前面;删除邻居后Set的其余部分是新的Set。
    • 再次尝试使用新的Open集,其余的Closed集,剩余的Set和Rest。

要在Prolog中执行此操作,我将首先清理坐标表示。 他们有两个列表是有点烦人的:如果我们使用例如一对代替它,那么它的写作要少得多:[X,Y] ---> X-Y。为此,我添加了这个谓词:

xy(XY) :-
        coordinates(C),
        maplist([[X,Y], X-Y]>>true, C, XY).
xy([1-1,1-3,1-2]).
xy([1-2,2-1,2-2]).
xy([1-1, 1-2, 1-3, 1-4, 1-5,
    2-1,                2-5,
    3-1,                3-5,
    4-1,                4-5,
    5-1, 5-2, 5-3, 5-4, 5-5]).
xy([1-1, 1-2, 1-3, 1-4, 1-5,
    2-1,                2-5,
    3-1,      3-3,      3-5,
    4-1,                4-5,
    5-1, 5-2, 5-3, 5-4, 5-5]).

coordinates([[1,1],[1,2],[1,3],[2,1],[2,2],[2,3],[3,1],[4,1],[4,2]]).
coordinates([[1,1],[1,2],[1,3],[1,4],[1,5],[1,6],[3,1],[4,1],[4,2]]).

(我还增加了4个测试集!)

因此,我得到:

?- xy(XY).
XY = [1-1, 1-2, 1-3, 2-1, 2-2, 2-3, 3-1, 4-1, ... - ...] [write] % press 'w' here
XY = [1-1, 1-2, 1-3, 2-1, 2-2, 2-3, 3-1, 4-1, 4-2] ;
XY = [1-1, 1-2, 1-3, 1-4, 1-5, 1-6, 3-1, 4-1, 4-2] ;
XY = [1-1, 1-3, 1-2] ;
XY = [1-2, 2-1, 2-2] ;
XY = [1-1, 1-2, 1-3, 1-4, 1-5, 2-1, 2-5, 3-1, 3-5, 4-1, 4-5, 5-1, 5-2, 5-3, 5-4, 5-5] ;
XY = [1-1, 1-2, 1-3, 1-4, 1-5, 2-1, 2-5, 3-1, 3-3, 3-5, 4-1, 4-5, 5-1, 5-2, 5-3, 5-4, 5-5].

以下是人们可以尝试在代码中编写上述算法的方法:

set_region_rest([A|As], Region, Rest) :-
        sort([A|As], [B|Bs]),
        open_set_closed_rest([B], Bs, Region, Rest).

这只是对输入Set进行了排序,并从中分离出第一个元素。 第一个元素是Open集合中的第一个坐标对,其余是Set,然后是输出参数。

现在,要将Set拆分为Region和Rest,只要我们在Open集中有坐标对,我们就需要继续增长Region。如果Open set为空,则表示我们的Region已完成,剩余的Set为Rest:

:- use_module(library(clpfd)).

open_set_closed_rest([], Rest, [], Rest).

为了找出一个坐标的哪个邻居在Set中,我们使用ord_intersection/4,它给出了Set中的邻居和Set的其余部分。

NB :列出4个邻居坐标!

open_set_closed_rest([X-Y|As], Set, [X-Y|Closed0], Rest) :-
        X0 #= X-1, X1 #= X + 1,
        Y0 #= Y-1, Y1 #= Y + 1,
        ord_intersection([X0-Y,X-Y0,X-Y1,X1-Y], Set, New, Set0),
        append(New, As, Open),
        open_set_closed_rest(Open, Set0, Closed0, Rest).

就是这样。有了这个,我得到以下6个解决方案:

?- xy(XY), set_region_rest(XY, Region, Rest).
XY = [1-1, 1-2, 1-3, 2-1, 2-2, 2-3, 3-1, 4-1, 4-2],
Region = [1-1, 1-2, 1-3, 2-3, 2-2, 2-1, 3-1, 4-1, 4-2],
Rest = [] ;
XY = [1-1, 1-2, 1-3, 1-4, 1-5, 1-6, 3-1, 4-1, 4-2],
Region = [1-1, 1-2, 1-3, 1-4, 1-5, 1-6],
Rest = [3-1, 4-1, 4-2] ;
XY = [1-1, 1-3, 1-2],
Region = [1-1, 1-2, 1-3],
Rest = [] ;
XY = [1-2, 2-1, 2-2],
Region = [1-2, 2-2, 2-1],
Rest = [] ;
XY = [1-1, 1-2, 1-3, 1-4, 1-5, 2-1, 2-5, 3-1, 3-5, 4-1, 4-5, 5-1, 5-2, 5-3, 5-4, 5-5],
Region = [1-1, 1-2, 1-3, 1-4, 1-5, 2-5, 3-5, 4-5, 5-5, 5-4, 5-3, 5-2, 5-1, 4-1, 3-1, 2-1],
Rest = [] ;
XY = [1-1, 1-2, 1-3, 1-4, 1-5, 2-1, 2-5, 3-1, 3-3, 3-5, 4-1, 4-5, 5-1, 5-2, 5-3, 5-4, 5-5],
Region = [1-1, 1-2, 1-3, 1-4, 1-5, 2-5, 3-5, 4-5, 5-5, 5-4, 5-3, 5-2, 5-1, 4-1, 3-1, 2-1],
Rest = [3-3].

顺便说一下,使用set_region_rest/3作为构建块,我们可以轻松地将一组坐标分割成区域:

set_regions([], []).
set_regions([X|Xs], [R|Rs]) :-
        set_region_rest([X|Xs], R, Rest),
        set_regions(Rest, Rs).

现在:

?- set_regions([1-1, 1-2, 1-3, 1-4, 1-5,      1-7,
                2-1,                2-5,      2-7,
                3-1,      3-3,      3-5,      3-7,
                4-1,                4-5,
                5-1, 5-2, 5-3, 5-4, 5-5,      5-7], R).
R = [[1-1, 1-2, 1-3, 1-4, 1-5, 2-5, 3-5, 4-5,
      5-5, 5-4, 5-3, 5-2, 5-1, 4-1, 3-1, 2-1],
     [1-7, 2-7, 3-7],
     [3-3],
     [5-7]].

答案 1 :(得分:0)

可以将此问题视为union find algorithms的实例。这种算法通常利用基本上起作用的特殊数据结构 以下两个目的:

1) For a point p, find a representant p* 
   in the data structure
2) For two representants p1* and p2* extend 
   the data structure by connecting both

Here是Prolog实现,它使用线程本地事实链接/ 2 作为联盟找到数据结构。这是第二个例子 网格问题:

?- regions(L).
L = [(1, 6),  (4, 2)].

技术说明:Prolog统一还有一个内置的联合查找组件, 如果你提到变量X,它将被解除引用,这是步骤1)。如果你统一X = Y并且X和Y已被解除引用,一个变量将链接到另一个变量,这是步骤2)。