【问题标题】:Get all possible binary trees using Prolog?使用 Prolog 获取所有可能的二叉树?
【发布时间】:2017-06-08 03:21:12
【问题描述】:

我知道有很多关于定义二叉树来检查某物是否是二叉树的问题,但我找不到在“相反方向”解决这个问题的线程。

为什么你对二叉树的定义在调用“es_arbol(X)”时没有返回所有可能的二叉树?详细解释并尝试实现一个不同的定义,它确实返回所有可能的二叉树结构。

好的,所以基本上我被困在这部分作业中。在定义了我的二叉树验证函数后,我注意到当不带参数调用它时,它只会返回通过它们的右节点“生长”的树,或者在至少这就是我解释 swi-prolog 的输出的方式。我没有得到的是,假设我的定义是正确的,Prolog 应该能够以两种方式构造它们。如果没有,我希望有人能指出我的正确方向来制定一个更一般的二叉树定义,或者解释为什么我的定义不够。

这是我的定义:

es_arbol(nil).
es_arbol(arbol(_,I,D)) :- es_arbol(I), es_arbol(D).

【问题讨论】:

  • 名称arbol/3 具有误导性,请改用nodo/3

标签: prolog binary-tree


【解决方案1】:

您的谓词沿一个分支生成无限数量的树的原因是因为您有多个递归,并且像任何语言一样,Prolog 将继续进行它找到的第一个递归调用,直到它返回,在这种情况下,永不。所以你总是在树的一条腿上递归。换句话说,每棵树(左子树和右子树)中至少有两个变量,它们具有无限多的可能性。

二叉树在二维上具有无限的递归可能性。您需要一种使用一维度量对树进行排序的方法。一种这样的指标可能是树中的节点数。如果您按节点计数对树进行排序,从节点计数 0 开始,那么对于每个节点计数,都有有限数量的树要枚举。这是它的一般工作方式:

  1. Nodes 是有效的节点数
  2. nil 是具有 0 个节点的有效二叉树
  3. arbol(_, TL, TR) 是具有 N 节点的有效二叉树,如果 NLNR2 加起来为 N-1TLNL 节点的有效二叉树,并且 TR 是有效二叉树NR 节点的树。由于 Prolog 会在回溯之前从给定点找到所有解决方案,因此它将首先搜索具有给定节点数的所有树,然后再回溯到新的有效节点数。

在 Prolog 中,它看起来像这样。

:- use_module(library(clpfd)).

es_arbol(Tree) :-
    length(_, Nodes),
    es_arbol(Tree, Nodes).

我使用length/2 来“生成”Nodes 0、1、2 等值。es_arbol(Tree) 将成功生成连续节点计数从 0 开始的二叉树。对于给定的节点计数,@ 987654336@,它将在最终失败之前找到es_arbol(Tree, Nodes)的所有解决方案并再次回溯到length(_, Nodes),这将在Node的下一个值上成功。

es_arbol(nil, 0).
es_arbol(arbol(_,TreeL,TreeR), N) :-
    N #> 0,
    NL + NR #= N - 1,
    NL #>= 0, NR #>= 0,
    es_arbol(TreeL, NL),
    es_arbol(TreeR, NR).

基本情况是微不足道的。 nil 是具有 0 个节点的树。递归情况表明arbol(_,L,R) 是具有N 节点的二叉树,如果N > 0NLNR 是加起来为NTLTR 的非负整数是长度分别为NLNR 的二叉树。

以上代码运行结果为:

?- es_arbol(Tree).
Tree = nil ;
Tree = arbol(_G258, nil, nil) ;
Tree = arbol(_G17, nil, arbol(_G157, nil, nil)) ;
Tree = arbol(_G17, arbol(_G200, nil, nil), nil) ;
Tree = arbol(_G14, nil, arbol(_G154, nil, arbol(_G593, nil, nil))) ;
Tree = arbol(_G14, nil, arbol(_G154, arbol(_G603, nil, nil), nil)) ;
Tree = arbol(_G14, arbol(_G130, nil, nil), arbol(_G191, nil, nil)) ;
Tree = arbol(_G14, arbol(_G53, nil, arbol(_G193, nil, nil)), nil) ;
Tree = arbol(_G14, arbol(_G53, arbol(_G236, nil, nil), nil), nil) ;
Tree = arbol(_G14, nil, arbol(_G100, nil, arbol(_G214, nil, arbol(_G354, nil, nil)))) ;
Tree = arbol(_G14, nil, arbol(_G100, nil, arbol(_G214, arbol(_G397, nil, nil), nil))) ;
Tree = arbol(_G14, nil, arbol(_G100, arbol(_G216, nil, nil), arbol(_G277, nil, nil))) ;
Tree = arbol(_G14, nil, arbol(_G100, arbol(_G139, nil, arbol(_G279, nil, nil)), nil)) ;
Tree = arbol(_G14, nil, arbol(_G100, arbol(_G139, arbol(_G322, nil, nil), nil), nil)) ;
Tree = arbol(_G14, arbol(_G130, nil, nil), arbol(_G191, nil, arbol(_G664, nil, nil))) ;
Tree = arbol(_G14, arbol(_G130, nil, nil), arbol(_G191, arbol(_G674, nil, nil), nil)) ;
Tree = arbol(_G14, arbol(_G132, nil, arbol(_G272, nil, nil)), arbol(_G676, nil, nil)) .
...


正如@false 在 cmets 中指出的那样,在这种应用枚举约束的情况下,使用 CLP(FD) 并不是最有效的方式。另一种更有效的方法是使用between/3
es_arbol(nil, 0).
es_arbol(arbol(_,TreeL,TreeR), N) :-
    N > 0,
    N1 is N - 1,
    between(0, N1, NL),
    NR is N1 - NL,
    es_arbol(TreeL, NL),
    es_arbol(TreeR, NR).

【讨论】:

  • @Kaz,这不是关于验证给定的有限二叉树结构。这是关于以可枚举的方式生成所有可能的二叉树。 Prolog 完全能够验证任何给定的有限二叉树,而无需使用计数器。
  • @Fgbruna 可以,但最后两行的tree 应该是es_arbol
  • @Fgbruna 抱歉,丹尼尔是正确的,我在帖子中输入错误。我最初以tree 编写和测试,然后更改为es_arbol,但没有捕捉到所有出现的情况。
  • @false,理解并同意。我更新了我的答案来解决这个问题。
  • @lurker:这并不过分。这是恶意的。这是有道理的,前提是您将> 替换为#> 并删除label/1。然后至少你使用了 clpfd 的功能,这可能是多余的,但一点也不多。
【解决方案2】:

Lurker 已经给出了一个非常好的使用 CLP(FD) 约束的通用解决方案。

我想用另一种方法来增加现有答案,以限制搜索的深度。我使用 list 以符号方式“计数”,而不是 整数

为了推理 Prolog 中的列表,DCG 表示法 () 通常非常方便,在这种情况下也是如此:

es_arbol(nil) --> []。 es_arbol(arbol(_,I,D)) --> [_],es_arbol(I),es_arbol(D)。

您可以将这些规则视为“消耗信用”来应用。

如果我天真地查询,那么我会得到一个 不公平 枚举:

?- 短语(es_arbol(A), Ls)。 A = 无, LS = [] ; A = arbol(_9016, nil, nil), LS = [_9024] ; A = arbol(_9016, nil, arbol(_9030, nil, nil)), Ls = [_9024, _9038] ; A = arbol(_9016, nil, arbol(_9030, nil, arbol(_9044, nil, nil))), Ls = [_9024, _9038, _9052] ; A = arbol(_9016, nil, arbol(_9030, nil, arbol(_9044, nil, arbol(_9058, nil, nil)))), Ls = [_9024, _9038, _9052, _9066]。

关键是我们可以通过限制列表的长度轻松地将其变成公平枚举。例如,要获得恰好具有 两个 内部节点的所有树,我们可以使用:

?- 短语(es_arbol(A), [_,_])。 A = arbol(_10426, nil, arbol(_10434, nil, nil)) ; A = arbol(_10426, arbol(_10434, nil, nil), nil) ; 错误的。

在此基础上,我们可以使用迭代深化来公平地枚举所有树形:

?- length(Ls, _), 短语(es_arbol(A), Ls)。 LS = [], A = 无; Ls = [_7130], A = arbol(_7142, nil, nil) ; Ls = [_7130, _7136], A = arbol(_7148, nil, arbol(_7156, nil, nil)) ; Ls = [_7130, _7136], A = arbol(_7148, arbol(_7156, nil, nil), nil) ; Ls = [_7130, _7136, _7142], A = arbol(_7154, nil, arbol(_7162, nil, arbol(_7170, nil, nil))) ; Ls = [_7130, _7136, _7142], A = arbol(_7154, nil, arbol(_7162, arbol(_7170, nil, nil), nil)) ; Ls = [_7130, _7136, _7142], A = arbol(_7154, arbol(_7162, nil, nil), arbol(_7170, nil, nil)) 。

因此,“象征性地”计数有时是使用实际整数的便捷替代方法。

【讨论】:

  • 不错的解决方案!我喜欢通过列表本身而不是计数器 (@Kaz) 进行约束的优雅。
  • 我想知道:什么可以提高 s(X) 式计数的流行度? (is)/2 的支持,也许?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-04-29
  • 2018-07-10
相关资源
最近更新 更多