生成列表[1,1,2,2,...,n,n]的所有排列,其中每对之间的元素数量均为Prolog

时间:2016-03-06 19:04:18

标签: algorithm prolog

我最近开始学习Prolog,我得到了一个编写谓词list(N, L)的任务,该谓词生成列表L,以便:

  • L的长度为2N,
  • 1到N之间的每个数字在L中恰好出现两次,
  • 在每对相同元素之间存在偶数个其他元素,
  • 每个号码的首次出现次数递增。

作者说有N!这样的清单。

例如,对于N = 3,所有解都是:

?- list(3, L).
L = [1, 1, 2, 2, 3, 3] ;
L = [1, 1, 2, 3, 3, 2] ;
L = [1, 2, 2, 1, 3, 3] ;
L = [1, 2, 2, 3, 3, 1] ;
L = [1, 2, 3, 3, 2, 1] ;
L = [1, 2, 3, 1, 2, 3] ;
false.

我目前的解决方案如下:

even_distance(H, [H | _]) :-
   !.
even_distance(V, [_, _ | T]) :-
   even_distance(V, T).

list(N, [], _, Length, _, _) :-
   Length =:= 2*N,
   !.
list(N, [New | L], Max, Length, Used, Duplicates) :-
   select(New, Duplicates, NewDuplicates),
   even_distance(New, Used),
   NewLength is Length + 1,
   list(N, L, Max, NewLength, [New | Used], NewDuplicates).
list(N, [New | L], Max, Length, Used, Duplicates) :-
   Max < N,
   New is Max + 1,
   NewLength is Length + 1,
   list(N, L, New, NewLength, [New | Used], [New | Duplicates]).

list(N, L) :-
   list(N, L, 0, 0, [], []).

它做了两件事:

  • 如果当前最大值小于N,请将该数字添加到列表中,将其放在重复列表中,然后更新最大值;
  • 选择一些副本,检查它与列表中已有的数字之间是否有偶数个元素(即该数字位于奇数位置),然后将其添加到列表中并从重复项中删除。

它有效,但速度慢,看起来不太好。

本练习的作者表明,对于N&lt; 12,他的解决方案生成一个平均约11个推断的单个列表(使用time/1并将结果除以N!)。随着我的解决方案,它增长到~60。

我有两个问题:

  1. 如何改进此算法?
  2. 这个问题可以推广到其他已知问题吗?我知道基于多重集[1, 1, 2, 2, ..., n, n]的类似问题(例如Langford配对),但是找不到这样的东西。
  3. 我问,因为最初的问题是关于在自相交的闭合曲线中枚举交点。您绘制这样的曲线,选择一个点和方向并跟随曲线,在第一次遇到时枚举每个交叉点并在第二次会议上重复该数字:example(答案为[1, 2, 3, 4, 5, 3, 6, 7, 8, 1, 9, 5, 4, 6, 7, 9, 2, 8])。

    作者指出,每条这样的曲线都满足谓词list,但不是每个列表都对应一条曲线。

1 个答案:

答案 0 :(得分:2)

我不得不求助于算术来满足由偶数元素分隔的整数对的要求。很高兴能够在没有算术的情况下解决...


list(N,L) :- numlist(1,N,H), list_(H,L), even_(L).

list_([D|Ds],[D|Rs]) :-
    list_(Ds,Ts),
    select(D,Rs,Ts).
list_([],[]).

even_(L) :-
    forall(nth0(P,L,X), (nth0(Q,L,X), abs(P-Q) mod 2 =:= 1)).

select / 3用于'插入模式'。

编辑以避免算术,我们可以使用这个更详细的架构

even_(L) :-
    maplist(even_(L),L).
even_(L,E) :-
    append(_,[E|R],L),
    even_p(E,R).
even_p(E,[E|_]).
even_p(E,[_,_|R]) :- even_p(E,R).

修改

这是一个基于预先列出的空“插槽”列表中的分配的片段。根据我的测试,它比你的解决方案更快 - 大约2倍。

list(N,L) :-
    N2 is N*2,
    length(L,N2),
    numlist(1,N,Ns),
    pairs(Ns,L).

pairs([N|Ns],L) :- first(N,L,R),even_offset(N,R),pairs(Ns,L).
pairs([],_).

first(N,[N|R],R) :- !.
first(N,[_|R],S) :- first(N,R,S).

even_offset(N,[N|_]).
even_offset(N,[_,_|R]) :- even_offset(N,R).

我的第一次尝试,在每次插入后使用even_ / 1 进行过滤,速度要慢得多。我最初专注于在select / 3之后立即推动过滤器,并且性能确实几乎是最后一个片段,但是唉,它失去了6个解决方案......