转载自:https://mp.weixin.qq.com/s/I9rQ--HaVIhZ9-7STQ9AdA
多路树
多路树由根元素和一组(可能为空)后继者组成,后继者本身也是多路树。多路树永远不会为空。后继树的集合有时称为森林。
在Prolog中,我们用项t(X,F)表示多路树,其中X表示根节点,F表示后继树的森林(Prolog列表)。因此,相应的示例树由以下Prolog项表示:
T = t(a,[t(f,[t(g,[])]),t(c,[]),t(b,[t(d,[]),t(e,[])])])
1、检查给定项是否表示多路树
编写谓词istree / 1,当且仅当其参数是代表多路树的Prolog术语时,该谓词才会成功。
istree(T) :- T是代表多路树的项(i),(o)
1 istree(t(_,F)) :-
2 isforest(F).
3
4 isforest([]).
5 isforest([T|Ts]) :-
6 istree(T),
7 isforest(Ts).
行注释:
1.
2. F是森林
3. 空行
4. 为空终止条件
5. 森林是树的列表
6. T是树
7. Ts是森林。递归遍历尾
测试:
?- consult(p5_01).
true.
?- istree(t(a,[t(f,[t(g,[])]),t(c,[]),t(b,[t(d,[]),t(e,[])])])).
true.
?- istree(T).
T = t(_30720, []) ;
T = t(_30, [t(_42, [])]) ;
T = t(_30, [t(_42, []), t(_564, [])]) ;
T = t(_30, [t(_42, []), t(_564, []), t(_1230, [])]) ;
T = t(_30, [t(_42, []), t(_564, []), t(_1230, []), t(_1896, [])]) .
?-
2、计算多路树的节点,给定节点数生成多路树
nnodes(T,N) :- 多路树T有N个节点 (i,o)
1 nnodes(t(_,F),N) :-
2 nnodes(F,NF),
3 N is NF+1.
4
5 nnodes([],0).
6 nnodes([T|Ts],N) :-
7 nnodes(T,NT),
8 nnodes(Ts,NTs),
9 N is NT+NTs.
10
行注释:
1. 匹配为树则进入后续
2. 递归节点,
3. 增1
4.
5. 匹配列表,终止条件
6. 匹配列表(匹配森林)
7. 取首节点递归
8. 取尾列表递归
9. 节点数的和N加NTs赋给N
10.
请注意,nnodes被称为树和森林。多态的早期形式!
测试:
?- consult(p5_02).
true.
?- nnodes(t(a,[t(f,[t(g,[])]),t(c,[]),t(b,[t(d,[]),t(e,[])])]),N).
N = 7.
对于流模式(o,i)给定节点数生成多路树如下设计
11 nnodes2(t(_,F),N) :-
12 N > 0,
13 NF is N-1,
14 nnodes2F(F,NF).
15
16 nnodes2F([],0).
17 nnodes2F([T|Ts],N) :-
18 N > 0,
19 between(1,N,NT),
20 nnodes2(T,NT),
21 NTs is N-NT,
22 nnodes2F(Ts,NTs).
行注释:
11. %生成多路树,N为节点数
12. 确保N大于0,不是终结点
13. N增1后赋值给NF
14. 进入’森林‘
15. 空行
16. 终止条件
17.
18. 确保N大于0
19. 设置’森林‘中树的数量范围,即多个解
20. 间接递归
21. 逐级深入下层
22. 递归’森林’
测试:
?- nnodes2(T,4).
T = t(_4864, [t(_4876, []), t(_4894, []), t(_4912, [])]) ;
T = t(_4864, [t(_4876, []), t(_5578, [t(_5590, [])])]) ;
T = t(_4864, [t(_6262, [t(_6274, [])]), t(_6298, [])]) ;
T = t(_4864, [t(_6964, [t(_6976, []), t(_6994, [])])]) ;
T = t(_4864, [t(_6964, [t(_7666, [t(_7678, [])])])]) ;
false.
?-
3、从节点字符串构造树
我们假设多路树的节点包含单个字符。在其节点的深度优先顺序中,每当在遍历树的过程中移动到上一级时,就会插入特殊字符^。
通过此规则,对图中的树表示为:afg^^c^bd^e^^^
定义字符串的语法,并在给出String时编写谓词tree(String,Tree)以构造Tree 。使用原子。使谓词在两个方向上都起作用。
从节点字符串构造多路树
我们假设多路树的节点包含单个字符。在其节点的深度优先顺序中,每当在遍历树的过程中移动到上一级时,就会插入特殊字符^。
定义字符串的语法,并在给出字符串时编写谓词tree(String,Tree)以构造Tree。使用原子(而不是字符串)。使谓词在两个方向上都起作用。
Syntax in BNF:
BNF中的语法
<tree> ::= <letter> <forest> '^'
<forest> ::= | <tree> <forest>
首先,使用差异列表是一个不错的解决方案
1 tree(TS,T) :-
2 atom(TS),
3 !,
4 atom_chars(TS,TL),
5 tree_d(TL-[],T).
6
行注释:
1.
2. 确保TS是原子
3. 截断
4. 将原子TS转成列表TL
5. 开始’TL-[]’用差异表方法处理
6.
7 tree(TS,T) :-
8 nonvar(T),
9 tree_d(TL-[],T),
10 atom_chars(TS,TL).
11
行注释:
7.
8. 确保T不是变量
9. 由树T转成列表
10. 将列表TL转成原子TS
11.
12 tree_d([X|F1]-T, t(X,F)) :-
13 forest_d(F1-[‘^’|T],F).
14
行注释:
12. X是双向匹配
13. ’根’的下一层一定是’森林’
14.
15 forest_d(F-F,[]).
16 forest_d(F1-F3,[T|F]) :-
17 tree_d(F1-F2,T),
18 forest_d(F2-F3,F).
19
行注释:
15. 终止条件
16.
17. 取当前节点,继续递归
18. 继续递归匹配后续元素
19.
测试:
?- consult(p5_03).
true.
?- tree('afg^^c^bd^e^^^',T).
T = t(a, [t(f, [t(g, [])]), t(c, []), t(b, [t(d, []), t(e, [])])]) .
?- tree(S,t(a, [t(f, [t(g, [])]), t(c, []), t(b, [t(d, []), t(e, [])])])).
S = 'afg^^c^bd^e^^^' .
?-
另一种解决方案,不像上面的解决方案那么优雅。注意:森林是由树(项)构成的列表
(+,?)
20 tree_2(TS,T) :-
21 atom(TS),
22 !,
23 atom_chars(TS,TL),
24 tree_a(TL,T).
25
行注释:
20.
21. 确保TS是原子
22. 截断
23. 将原子TS转成列表TL
24. 树与列表双向匹配,此为列表转成树
25.
26 tree_2(TS,T) :-
27 nonvar(T),
28 tree_a(TL,T),
29 atom_chars(TS,TL).
30
行注释:
26.
27. 确保T不是变量
28. 树与列表双向匹配,此为树转成列表
29. 列表转成原子,词谓词也是两个方向
30.
31 tree_a(TL,t(X,F)) :-
32 append([X],FL,L1),
33 append(L1,['^'],TL),
34 forest_a(FL,F).
35
行注释:
31.
32. 词谓词可以多个方向匹配
33. 保证列表Tl的最后一个元素为’^’
34. 处理’森林’
35. 空行
36 forest_a([],[]).
37 forest_a(FL,[T|Ts]) :-
38 append(TL,TsL,FL),
39 tree_a(TL,T),
40 forest_a(TsL,Ts).
行注释:
36. 终止条件
37. T是树(项)
38. 多方向匹配
39. T是树’项’
40. 继续处理森林
测试:
?- tree_2('afg^^c^bd^e^^^',T).
T = t(a, [t(f, [t(g, [])]), t(c, []), t(b, [t(d, []), t(e, [])])]) .
?- tree_2(S,t(a, [t(f, [t(g, [])]), t(c, []), t(b, [t(d, []), t(e, [])])])).
S = 'afg^^c^bd^e^^^' .
?-
4、确定树的内部路径长度
我们将多路径树的内部路径长度定义为从树的根到所有节点的路径长度的总和。根据此定义,问题3中的树的内部路径长度为9。
为流模式(+,-)写一个谓词ipl(Tree,IPL)。
1 ipl(T,L) :-
2 ipl(T,0,L).
3
行注释:
1.
2. 第2个参数设为0,从0开始计数
3. 空行
4 ipl(t(_,F),D,L) :-
5 D1 is D+1,
6 ipl(F,D1,LF),
7 L is LF+D.
8
行注释:
4. 第一个参数为树的项时
5. 将D增1后赋值给D1,增加计数
6. 继续递归计数
7. L值等于LF加上D
8. 空行
9 ipl([],_,0).
10 ipl([T1|Ts],D,L) :-
11 ipl(T1,D,L1),
12 ipl(Ts,D,Ls),
13 L is L1+Ls.
行注释:
9. 为森林,终止条件
10. 展开森林
11. 处理单个树(项)
12. 递归’森林的后续元素’
13. 深度L为L1加上Ls
测试:
?- consult(p5_04).
true.
?- T = t(a, [t(f, [t(g, [])]), t(c, []), t(b, [t(d, []), t(e, [])])]),ipl(T,L).
T = t(a, [t(f, [t(g, [])]), t(c, []), t(b, [t(d, []), t(e, [])])]),
L = 9.
5、构造树节点的自底向上顺序
编写谓词bottom_up(Tree,Seq),该谓词构造多路树Tree的节点的自下而上的序列。Seq应该是Prolog列表。
bottom_up_f(t(X,F),Seq) :- %由树起始
bottom_up_f(F,SeqF), %F是’森林’
append(SeqF,[X],Seq).%将根元素放在列表尾
bottom_up_f([],[]).%终止条件
bottom_up_f([T|Ts],Seq):-
bottom_up_f(T,SeqT), %取一棵树处理
bottom_up_f(Ts,SeqTs), %递归剩余森林的树
append(SeqT,SeqTs,Seq).%将从树得到的元素放在列表的前面后续处理的放后面
?- consult(p5_05).
true.
?- bottom_up_f(t(a, [t(g, []), t(f, []), t(c, []), t(d, []), t(e, []),t(b, [])]),Seq).
Seq = [g, f, c, d, e, b, a].
此版本只能单向运行。
在(-,+)流模式中调用谓词bottom_up/2会产生堆栈溢出。这有两个问题。
首先,多态不能正常工作,因为在分解字符串期间,程序无法猜测接下来是否应该构造树或森林。我们可以使用两个单独的谓词bottom_up_tree / 2和bottom_up_forset / 2来解决此问题。
其次,如果我们维持子目标的顺序,则解释器在找到第一个解后陷入无限循环。我们可以通过如下更改目标顺序来解决此问题:
bottom_up_tree(t(X,F),Seq) :- %由树起始
append(SeqF,[X],Seq), %将本树节点元素放入列表Seq的最后或是去列表表尾的元素X
bottom_up_forest(F,SeqF). %遍历森林或剩余列表
bottom_up_forest([],[]).%终止条件
bottom_up_forest([T|Ts],Seq):-
append(SeqT,SeqTs,Seq), %树节点所属所有元素列表SeqT在前,森林所属元素在后,合成总表它们是可以双向匹配的
bottom_up_tree(T,SeqT), %树和列表相互匹配,间接递归
bottom_up_forest(Ts,SeqTs).%森林和列表相互匹配,直接递归
测试:
?- bottom_up_tree(t(a, [t(f, [t(g, [])]), t(c, []), t(b, [t(d, []), t(e, [])])]),Seq).
Seq = [g, f, c, d, e, b, a] .
?- bottom_up_tree(T,[g, f, c, d, e, b, a]).
T = t(a, [t(g, []), t(f, []), t(c, []), t(d, []), t(e, []), t(b, [])]) .
6、类Lisp的树表示
Lisp中的多路树有一种特殊的表示法。Lisp是一种杰出的函数式编程语言,主要用于人工智能问题。因此,它是Prolog的主要竞争对手之一。在Lisp中,几乎所有内容都是列表,就像在Prolog中,所有内容都是项。
下图显示了如何在Lisp中表示多路树结构。
a、tree_ltl(T,L) :- L是多路树T的“ lispy形式的列表” (i,o)
1 tree_ltl(t(X,[]),[X]).
2 tree_ltl(t(X,[T|Ts]),L) :-
3 tree_ltl(T,L1),
4 append([‘(‘,X],L1,L2),
5 rest_ltl(Ts,L3),
6 append(L2,L3,L).
7
行注释:
1. 终止条件
2.
3. 取树X节点下的’森林’继续递归
4. 将本节点元素X加插入元素’(’加上列表L1合并成为列表L2
5. 递归剩余’森林’
6. 合并列表L2,L3到L
7. 空行
8 rest_ltl([],[‘)']).
9 rest_ltl([T|Ts],L) :-
10 tree_ltl(T,L1),
11 rest_ltl(Ts,L2),
12 append(L1,L2,L).
13
行注释:
8. 终止条件
9.
10. 取一个树节点
11. 递归剩余’森林’
12. 合并列表L1,L2到L
13.
辅助谓词在此不做说明了
14 write_ltl([]) :-
15 nl.
16 write_ltl([X|Xs]) :-
17 write(X),
18 write(' '),
19 write_ltl(Xs).
20
21 dotest(T) :-
22 write(T),
23 nl,
24 tree_ltl(T,L),
25 write_ltl(L).
26
27 test(1) :-
28 T = t(a,[t(b,[]),t(c,[])]),
29 dotest(T).
30
31 test(2) :-
32 T = t(a,[t(b,[t(c,[])])]),
33 dotest(T).
34 test(3) :-
35 T = t(a,[t(f,[t(g,[])]),t(c,[]),t(b,[t(d,[]),t(e,[])])]),
36 dotest(T).
测试:
?- consult(p5_06a).
true.
?- test(1).
t(a,[t(b,[]),t(c,[])])
( a b c )
true .
?- test(2).
t(a,[t(b,[t(c,[])])])
( a ( b c ) )
true .
?- test(3).
t(a,[t(f,[t(g,[])]),t(c,[]),t(b,[t(d,[]),t(e,[])])])
( a ( f g ) c ( b d e ) )
true .
b、作为第二个甚至更有趣的练习,尝试以也可以进行逆转换的方式重写tree_ltl / 2:给定列表LTL,构造Prolog树T。使用差异列表。
这是此最优雅的设计方案:流式(i,o)和(o,i)
1 tree_ltl(T,L) :-
2 tree_ltl_d(T,L-[]).
3
4 tree_ltl_d(t(X,[]),[X|L]-L) :-
5 X \= ‘('.
6 tree_ltl_d(t(X,[T|Ts]),['(',X|L]-R) :-
7 forest_ltl_d([T|Ts],L-[‘)'|R]).
8
9 forest_ltl_d([],L-L).
10 forest_ltl_d([T|Ts],L-R) :-
11 tree_ltl_d(T,L-M),
12 forest_ltl_d(Ts,M-R).
13
行注释:
1.
2. 开始调用差异表处理
3.
4. 本节点所指森立为空时终止,X双向匹配
5. 确保X不等于’(’
6. 非终止状况起始的列表与树的相互关系在树的本节点对应的列表中肯定是以’(’开始的再后跟X,双向匹配
7. 继续递归’森林’后续部分
8. 空行
9. 终止条件
10. ’森林’中取’一棵树’
11. 间接递归处理树
12. 直接递归处理剩余’森林’
13.
测试:
?- consult(p5_06b).
true.
?- tree_ltl(t(a, [t(f, [t(g, [])]), t(c, []), t(b, [t(d, []), t(e, [])])]),X).
X = ['(', a, '(', f, g, ')', c, '(', b|...] .
?- tree_ltl(T,['(',a,'(',f,g,')',c,'(',b,d,e,')',')']).
T = t(a, [t(f, [t(g, [])]), t(c, []), t(b, [t(d, []), t(e, [])])]) .
?-