Prolog二叉搜索树测试 - 不需要的父母的父节点比较

Posted

技术标签:

【中文标题】Prolog二叉搜索树测试 - 不需要的父母的父节点比较【英文标题】:Prolog binary search tree test - unwanted parents' parent node comparison 【发布时间】:2016-01-07 17:55:21 【问题描述】:

我是 Prolog 新手,请记住这一点。 我尝试编写一个谓词来确定某个给定术语是否是二叉搜索树。我想出了这段代码:

is_btree(nil).
is_btree(node(N,L,R)) :-
   number(N),
   is_btree(L), 
   is_btree(R), 
   small(N, R),
   big(N, L).

small(N, nil).
small(N, node(M,L,R)) :-
   N < M,
   small(N, L),
   small(N, R).

big(N, nil).
big(N, node(M,L,R)) :-
   N > M,
   big(N, L),
   big(N, R).

它工作得很好,直到我测试一个在右侧有一个节点的图,它通过条件“高于父节点”,但它高于或等于父节点的父节点。在这种情况下,Prolog 报告失败。

这是一个意外失败的示例查询:

?- is_btree(node(9,node( 3,node( 2,nil,nil),
                           node(10,nil,nil)),
                   node(12,node( 8,nil,nil),
                           node(15,nil,nil)))).
false.

当左侧的某个节点高于父节点的父节点时,会出现非常类似的问题——这种情况如下图所示:

如何仅使用其直接父节点的值而不使用父节点的父节点的值来检查节点值?

【问题讨论】:

但它应该会失败。该树是无效的 BST,您的谓词测试 valid BST。 我们应该使用显式区间算术吗? (Ab-)使用 FD 域似乎也是一种选择,但 that 对我来说太老套了。使用 2 FD 变量(Alpha、Omega)作为区间似乎更有希望... @repeat 我将在回答中坚持简单的内容。 :) @WillNess。凌晨 2 点:我该开始传福音了:) 【参考方案1】:

这里对您要解决的问题略有不同。

    dcg用于收集元素:有序tree-traversal

    in_order(nil) --> []。 in_order(节点(X,L,R))-> in_order(L),[X],in_order(R)。

    clpfd 用于关联相邻的list 元素(它们都是有限域变量)

    链(Zs,#

让我们把它们放在一起,像这样定义is_bintreeFD/1

:- use_module(library(clpfd))。 is_bintreeFD(T) :- phrase(in_order(T), Zs), chain(Zs, #

示例查询:

?- is_bintreeFD(node(9,node( 3,node(2,nil,nil),node(10,nil,nil)), 节点(12,节点(8,无,无),节点(15,无,无))))。 错误。 ?- is_bintreeFD(node(9,node(3,node(2,nil,nil),node(8,nil,nil)), 节点(12,节点(10,nil,nil),节点(15,nil,nil))))。 真的。

【讨论】:

不错!是否可以切换两个目标,因此在失败时尽快停止遍历?在phrase 之前有chain【参考方案2】:

此答案直接跟进this previous answer,特别是@WillNess 的评论建议“[...] 切换两个目标,因此在失败时尽快停止遍历 [...] phrase 之前有chain [...]"。

lazy_chain/2 类似于chain/2,但利用prolog-coroutining 等待足够的实例化:

:- use_module(library(clpfd))。 懒惰链(Zs,R_2):- (var(R_2) -> instantiation_error(R_2) ; clpfd:chain_relation(R_2) -> freeze(Zs,lazy_chain_aux(Zs,R_2)) ;否则 -> domain_error(chain_relation, R_2) )。 懒惰链辅助([],_)。 懒惰链辅助([Z0|Zs],R_2):- 冻结(Zs,lazy_chain_aux_(Zs,R_2,Z0))。 惰性链_辅助_([],_,_)。 lazy_chain_aux_([Z1|Zs], R_2, Z0) :- 呼叫(R_2,Z0,Z1), 冻结(Zs,lazy_chain_aux_(Zs,R_2,Z1))。

基于lazy_chain/2,我们这样定义is_bintreeL/2

is_bintreeL(T) :- 懒惰链(Zs,#phrase(in_order(T), Zs)。

那么……“早期失败”呢?

?- T = node(2, nil, node(1, nil, node(3, nil, node(4, nil, node(5, nil, node( 6,无,节点(7,无,节点(8,无,节点(9,无,节点(10,无,节点(11,无,节点(12,无,节点(13,无,节点(14,无,节点(15,无,节点(16,无,节点(17,无,节点(18,无,节点(19,无,节点(20,无,节点(21,无,节点(22,无,节点(23,无,节点(24,无,节点(25,无,节点(26,无,节点(27,无,节点(28,无,节点(29,无,节点(30,无,节点( 31,无,节点(32,无,节点(33,无,节点(34,无,节点(35,无,节点(36,无,节点(37,无,节点(38,无,节点(39,无,节点(40,无,节点(41,无,节点(42,无,节点(43,无,节点(44,无,节点(45,无,节点(46,无,节点(47,无,节点(48,无,节点(49,无,节点(50,无,节点(51,无,节点(52,无,节点(53,无,节点(54,无,节点(55,无,节点( 56,无,节点(57,无,节点(58,无,节点(59,无,节点(60,无,节点(61,无,节点(62,无,节点(63,无,节点(64,无,节点(65,无,节点(66,无,节点(67,无,节点(68,无,节点(69,无,节点(70,无,节点(71,无,节点(72,无,节点(73,无,节点(74,无,节点(75,无,节点(76,无,节点(77,无,节点(78,无,节点(79,无,节点(80,无,节点(81,无,节点(82,无,节点(83,无,节点(84,无,节点(85,无,节点(86,无,节点(87,无,节点( 88,无,节点(89,无,节点(90,无,节点(91,无,节点(92,无,节点(93,无,节点(94,无,节点(95,无,节点(96,无,节点(97,无,节点(98,无,节点(99,无,节点(100,无,无))))))))))))))))))))))) )))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) ))))))))))))))))))))))))))))))))), 时间((短语(in_order(T),Zs),渴望_chain(Zs,#210 次推断,0.000 CPU 在 0.000 秒内(98% CPU,4100201 唇) 错误的。 ?- T = node(2, nil, node(1, nil, node(3, nil, node(4, nil, node(5, nil, node( 6,无,节点(7,无,节点(8,无,节点(9,无,节点(10,无,节点(11,无,节点(12,无,节点(13,无,节点(14,无,节点(15,无,节点(16,无,节点(17,无,节点(18,无,节点(19,无,节点(20,无,节点(21,无,节点(22,无,节点(23,无,节点(24,无,节点(25,无,节点(26,无,节点(27,无,节点(28,无,节点(29,无,节点(30,无,节点( 31,无,节点(32,无,节点(33,无,节点(34,无,节点(35,无,节点(36,无,节点(37,无,节点(38,无,节点(39,无,节点(40,无,节点(41,无,节点(42,无,节点(43,无,节点(44,无,节点(45,无,节点(46,无,节点(47,无,节点(48,无,节点(49,无,节点(50,无,节点(51,无,节点(52,无,节点(53,无,节点(54,无,节点(55,无,节点( 56,无,节点(57,无,节点(58,无,节点(59,无,节点(60,无,节点(61,无,节点(62,无,节点(63,无,节点(64,无,节点(65,无,节点(66,无,节点(67,无,节点(68,无,节点(69,无,节点(70,无,节点(71,无,节点(72,无,节点(73,无,节点(74,无,节点(75,无,节点(76,无,节点(77,无,节点(78,无,节点(79,无,节点(80,无,节点(81,无,节点(82,无,节点(83,无,节点(84,无,节点(85,无,节点(86,无,节点(87,无,节点( 88,无,节点(89,无,节点(90,无,节点(91,无,节点(92,无,节点(93,无,节点(94,无,节点(95,无,节点(96,无,节点(97,无,节点(98,无,节点(99,无,节点(100,无,无))))))))))))))))))))))) )))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) ))))))))))))))))))))))))))))))))), time((lazy_chain(Zs,#52 次推断,0.000 CPU 在 0.000 秒内(98% CPU,1225664 唇) 错误的。

懒惰获胜——至少在上述情况下:)

但是请注意,将lazy_chain/2 与dcg 一起使用可能会导致难以找到的错误!

如需更强大的解决方案,请参阅this alternative answer...


为了完整起见,这里是eager_chain/2的源代码:

急切链(Zs,R_2):- ( var(R_2) -> 实例化_error(R_2) ; clpfd:chain_relation(R_2) -> eager_chain_aux(Zs, R_2) ;否则-> domain_error(chain_relation, R_2) )。 急切链辅助([],_)。 eager_chain_aux([Z0|Zs], R_2) :- eager_chain_aux_(Zs, R_2, Z0)。 eager_chain_aux_([], _, _)。 eager_chain_aux_([Z1|Zs], R_2, Z0) :- 呼叫(R_2,Z0,Z1), eager_chain_aux_(Zs, R_2, Z1)。

【讨论】:

【参考方案3】:

在这个答案中,我们使用clpfd 用于声明性整数算术

:- use_module(library(clpfd))。 :- asserta(clpfd:full_answer)。

我们这样定义谓词is_bintree/1bintree_in/2

is_bintree(T) :- bintree_in(T,_)。 bintree_in(nil, LB-UB) :- % LB-UB 表示open interval (LB,UB) LB #< UB。 % 是所有整数 I 使得 LB #> LB, 中 #< UB, bintree_in(L, LB-Mid), bintree_in(R,中 UB)。

首先,我们测试1,2 OP 给出的树:

| ?- bintree_in(node(9,node( 3,node(2,nil,nil),node(10,nil,nil)), 节点(12,节点(8,无,无),节点(15,无,无))),_)。 不

让我们修复树并再次检查!

| ?- bintree_in(node(9,node(3,node(2,nil,nil),node(8,nil,nil)), 节点(12,节点(10,无,无),节点(15,无,无))),_)。 _A in inf..1, _B in 16..sup ? ; %(有点草率) 不

好的!接下来是一些极端案例:

| ?- bintree_in(T, 0-0)。 % 无解(如预期) 不 | ?- bintree_in(T, 0-1)。 % 空树 T = 无? ; 不 | ?- bintree_in(T, 0-2)。 % 单例树 T = 无? ; T = 节点(1,无,无)? ; 不

请注意,虽然is_btree/1 只能“测试”,但bintree_in/2 可以3“测试”和“生成”!

所以让我们在一个小域中生成(所有可能的)一定大小的二叉树:

| ?- bintree_in(T, 0-3)。 % T 的元素少于 3 个 T = 无? ; T = node(_A,nil,nil), _A in 1..2 ? ; T = 节点(1,nil,节点(2,nil,nil))? ; T = 节点(2,节点(1,nil,nil),nil)? ; 不 | ?- bintree_in(T, 0-4)。 % T 的元素少于 4 个 T = 无? ; T = node(_A,nil,nil), _A in 1..3 ? ; T = node(_A,nil,node(_B,nil,nil)), _A#=<_b _b>=_A+1, _B in 2..3, _A in 1..2 ? ; T = 节点(1,nil,节点(2,nil,节点(3,nil,nil)))? ; T = 节点(1,nil,节点(3,节点(2,nil,nil),nil))? ; T = node(_A,node(_B,nil,nil),nil), _A#>=_B+1, _A in 2..3, _B in 1..2 ? ; T = 节点(2,节点(1,nil,nil),节点(3,nil,nil))? ; T = 节点(3,节点(1,nil,节点(2,nil,nil)),nil)? ; T = 节点(3,节点(2,节点(1,nil,nil),nil),nil)? ; 不

最后,我们使用bintree_in/2 生成候选解决方案并使用is_btree/1 进行测试!

is_btree/1 需要足够的实例化; labeling/2 为我们提供了基本术语。

| ?- call_time(( UB in 2..12, indomain(UB), bintree_in(T, 0-UB), term_variables(T, Zs), labeling([], Zs), \+ is_btree(T) ;真的 ), T_ms)。 T_ms = 6270 ? ; 不

脚注 1: 此答案中的代码运行(sicstus-prolog 和 swi-prolog. 脚注 2: 呈现的所有prolog-toplevel 输出都是 SICStus Prolog 4.3.2(64 位)。 脚注 3: 不仅两者都做,而且 (几乎)任意混合生成和测试,因为它可以处理部分实例化的术语。

【讨论】:

哇,感谢您的解决方案,但我真的只是认为这种类型的树也可以是有效的...... :D 对不起,伙计们【参考方案4】:

在对this previous answer 的评论中,@WillNess 建议添加“早期故障”作为一项功能。

in_order_inf_sup//3 有效地结合了in_order//1chain/2:

:- use_module(library(clpfd)).

in_order_inf_sup(nil, P, P) --> [].
in_order_inf_sup(node(X,L,R), P0, P) -->
   in_order_inf_sup(L, P0, P1),
   [X],
    P1 #< X ,
   in_order_inf_sup(R, X, P).

示例查询(与上一个答案相同):

?- 短语(in_order_inf_sup(node(9,node(3,node(2,nil,nil),node(10,nil,nil))), 节点(12,节点(8,nil,nil),节点(15,nil,nil))),_,_), Z)。 错误。 ?- 短语(in_order_inf_sup(节点(9,节点(3,节点(2,nil,nil),节点(8,nil,nil)), 节点(12,节点(10,nil,nil),节点(15,nil,nil))),_,_), Z)。 Zs = [2,3,8,9,10,12,15]。

【讨论】:

【参考方案5】:

但它应该失败。该树是无效的 BST,您的谓词测试有效的 BST。

不过这里有一些事情要做。现在,您在树上执行两次传递 - 第一次在 is_btree 中,第二次在 small/big 中。

两者可以合二为一,但一个显而易见的解决方案将完全按照您的意愿行事,从而在此类无效的 BST 上取得成功:

is_bst(nil).

is_bst(node(N,L,R)):- 
   (  L = nil 
   ;  L = node(M,LL,LR), M < N, is_bst(L), ....
   ),
   (  R = nil
   ;  ...... 
   ).

要修复它,我们必须从树遍历返回一个结果——即树最右边的元素——并在比较中使用 it 进行验证。

edit:漏掉了最左边的元素也需要返回)

【讨论】:

以上是关于Prolog二叉搜索树测试 - 不需要的父母的父节点比较的主要内容,如果未能解决你的问题,请参考以下文章

赫夫曼树

在二叉搜索树中找到一个节点的父节点

树(二叉树的插入删除查找遍历)

求教二叉树遍历,父节点出错

leetcode 669. 修剪二叉搜索树

数据结构学习笔记04树(二叉树二叉搜索树平衡二叉树)