转载自:https://mp.weixin.qq.com/s/e6Mw5v3So-wgUPDsOixulQ
13、布局二叉树(1)
给定二叉树作为通常的Prolog项t(X,L,R)(或nil)。作为绘制树的准备工作,需要一种布局算法来确定每个节点在矩形网格中的位置。可以考虑几种布局方法,下图显示了其中一种。
在此布局策略中,节点v的位置 通过以下两个规则获得:
x(v)等于节点v在有序序列中的位置
y(v)等于树中节点v的深度
为了存储节点的位置,我们将代表一个节点(及其后继节点)的Prolog项扩展如下:
nil代表空树(通常)
t(W,X,Y,L,R)代表(非空)二叉树,其根W“位于”(X,Y),子树L和R
谓词形如:layout_binary_tree/2:
layout_binary_tree(T,PT) :- PT是从二叉树T获得的“定位”二叉树。
1 :- ensure_loaded(p4_04).
2
3 layout_binary_tree(T,PT) :-
4 layout_binary_tree(T,PT,1,_,1).
5
layout_binary_tree(T,PT,In,Out,D) :- T和PT如 layout_binary_tree/2;
In是有序序列中树T(or PT)开始的位置 ,
Out is the position after the last node of in the
Out是按顺序排列的T (or PT) 最后一个节点之后的位置。
D是T (or PT) 根的深度 .
6 layout_binary_tree(nil,nil,I,I,_).
7 layout_binary_tree(t(W,L,R),t(W,X,Y,PL,PR),Iin,Iout,Y) :-
8 Y1 is Y + 1,
9 layout_binary_tree(L,PL,Iin,X,Y1),
10 X1 is X + 1,
11 layout_binary_tree(R,PR,X1,Iout,Y1).
12
行注释:
1. 确保装入谓词construct/2,用以测试
2. 空行
3. T为起始树,PT是用于’操纵’的当前树
4. 参数3,4为’1’代表从根开始
5. 空行
6. 终结点,即终止条件
7.
8. Y增1赋值给Y1
9. 继续递归左树
10. X增1赋值给X1
11. 继续递归右树
12. 空行
测试:
?- consult('p4_13.pl').
true.
?- construct([n,k,m,c,a,h,g,e,u,p,s,q],T),layout_binary_tree(T,PT).
T = t(n, t(k, t(c, t(a, nil, nil), t(h, t(g, t(e, nil, nil), nil), nil)), t(m, nil, nil)), t(u, t(p, nil, t(s, t(q, nil, nil), nil)), nil)),
PT = t(n, 8, 1, t(k, 6, 2, t(c, 2, 3, t(a, 1, 4, nil, nil), t(h, 5, 4, t(g, 4, 5, t(e, 3, 6, nil, nil), nil), nil)), t(m, 7, 3, nil, nil)), t(u, 12, 2, t(p, 9, 3, nil, t(s, 11, 4, t(q, 10, 5, nil, nil), nil)), nil)) .
14、布局二叉树(2)
上图显示了一种替代的布局方法。找出规则并编写相应的Prolog谓词。提示:在给定级别上,相邻节点之间的水平距离是恒定的。
节点v的位置通过以下规则获得:
(1)y(v)等于树中节点v的深度
(2)如果D表示树的深度(即填充级别的数量),则级别i(从根开始,从1开始)的节点之间的水平距离等于2**(D-i+1)。树的最左侧节点在位置1。
layout_binary_tree2(T,PT) :- PT是从二叉树T获得的“定位”二叉树。
1 :- ensure_loaded(p4_04).
2
3 layout_binary_tree2(nil,nil) :-
4 !.
5
行注释:
1. 确保装入谓词construct/2,用以测试
2. 空行
3. 终止节点
4. 截断
5. 空行
6 layout_binary_tree2(T,PT) :-
7 hor_dist(T,D4),
8 D is D4//4,
9 x_pos(T,X,D),
10 layout_binary_tree2(T,PT,X,1,D).
11
行注释:
6. T为起始树,PT是用于’操纵’的当前树
7. 获取水平距离D4
8. 本节点水平距离
9. 根据D获取位置X
10. 开始调用layout_binary_tree2/5
11.
hor_dist(T,D4) :- D4是T的根节点与其后继节点(如果有)之间水平距离的四倍
12 hor_dist(nil,1).
13 hor_dist(t(_,L,R),D4) :-
14 hor_dist(L,D4L),
15 hor_dist(R,D4R),
16 D4 is 2 * max(D4L,D4R).
17
行注释:
12. 终结点
13. 获取D4
14. 左树到本节点的距离D4L
15. 右树到本节点的距离D4L
16. 取左右两树节点分别到本树节点距离的最大值乘以2赋值给D4作为两节点间的水平距离
17. 空行
x_pos(T,X,D) :- X是T根节点相对于图片坐标系的水平位置。 D是T的根节点与其后继节点(如果有)之间的水平距离。
18 x_pos(t(_,nil,_),1,_) :-
19 !.
20 x_pos(t(_,L,_),X,D) :-
21 D2 is D//2,
22 x_pos(L,XL,D2),
23 X is XL+D.
24
行注释:
18. 终止条件
19. 截断
20. 由距离D获取位置X
21. 下层树两节点的距离为D2
22. 递归去获得XL
23. 的到的位置就是XL加上D2后赋值给X
24.
layout_binary_tree2(T,PT,X,Y,D) :-T和PT与layout_binary_tree/2中的一样;
D是T的根节点与其后继节点(如果有)之间的水平距离。 X,Y是根节点的坐标。
25 layout_binary_tree2(nil,nil,_,_,_).
26 layout_binary_tree2(t(W,L,R),t(W,X,Y,PL,PR),X,Y,D) :-
27 Y1 is Y + 1,
28 Xleft is X - D,
29 D2 is D//2,
30 layout_binary_tree2(L,PL,Xleft,Y1,D2),
31 Xright is X + D,
32 layout_binary_tree2(R,PR,Xright,Y1,D2).
33
行注释:
25. 终止位条件
26. 从根开始
27. Y增1赋值给Y1进到树的下一层
28. 左树的水平位置Xleft
29. 进到下一层水平位置关系
30. 递归左树处理后续
31.进到下一层水平位置关系
32. 递归右树处理后续
33.
测试:
?- consult('p4_14.pl').
true.
?- construct([n,k,m,c,a,e,d,g,u,p,q],T),layout_binary_tree2(T,PT).
T = t(n, t(k, t(c, t(a, nil, nil), t(e, t(d, nil, nil), t(g, nil, nil))), t(m, nil, nil)), t(u, t(p, nil, t(q, nil, nil)), nil)),
PT = t(n, 15, 1, t(k, 7, 2, t(c, 3, 3, t(a, 1, 4, nil, nil), t(e, 5, 4, t(d, 4, 5, nil, nil), t(g, 6, 5, nil, nil))), t(m, 11, 3, nil, nil)), t(u, 23, 2, t(p, 19, 3, nil, t(q, 21, 4, nil, nil)), nil)) .
?-
15、布局二叉树(3)
上图显示了另一种布局策略。该方法产生非常紧凑的布局,同时在每个节点中保持一定的对称性。找出规则并编写相应的Prolog谓词。提示:考虑节点与其后继节点之间的水平距离。您可以将两个子树打包在一起以构建组合的二叉树的紧密程度如何?
使用与问题13和14中相同的约定,并以适当的方式测试谓词。注意:这是一个难题。不要太早放弃!
您最喜欢哪种布局?
节点v的位置通过以下规则获得:
(1)y(v)等于树中节点v的深度
(2)为了确定节点的水平位置,我们为每个子树构造了“轮廓”,并将它们在水平方向上移到尽可能近的位置。但是,我们在每个节点中都保持对称性。即节点与其左子树的根之间的水平距离与它与右子树的根之间的水平距离相同。
树的“轮廓”是项c(Xleft,Xright)的列表,这些项给出了树的最外层节点在每个级别上相对于根位置的水平位置。在问题描述中给出的示例中,根为k的树的“轮廓”为[c(-1,1),c(-2,0),c(-1,1)]。请注意,“轮廓”列表中的第一个元素是从节点c和m的位置派生的。
轮廓树完整样例:
1 CT = t(n, t(k, t(c, t(a, nil, nil, []),
2 t(e, t(d, nil, nil, []), t(g, nil, nil, []),
3 [c(-1, 1)]),
4 [c(-1, 1), c(0, 2)]),
5 t(m, nil, nil, []),
6 [c(-1, 1), c(-2, 0), c(-1, 1)]),
7 t(u, t(p, nil, t(q, nil, nil, []),
8 [c(1, 1)]),
9 nil,
10 [c(-1, -1), c(0, 0)]),
11 [c(-2, 2), c(-3, 1), c(-4, 2), c(-3, -1)])
行注释(此非程序代码)
1.
2.
3. 第四层(e层)
4. 第三层(c层)
5.
6. 第二层(k层)
7.
8. 第三层(p层)
9.
10. 第二层(u层)
11. 根(root)
layout_binary_tree3(T,PT) :- PT是从二叉树T获得的“定位”二叉树。
程序代码:
1 :- ensure_loaded(p4_04).
2
3 layout_binary_tree3(nil,nil) :-
4 !.
5 layout_binary_tree3(T,PT) :-
6 contour_tree(T,CT),
7 CT = t(_,_,_,Contour),
8 mincont(Contour,MC,0),
9 Xroot is 1-MC,
10 layout_binary_tree3(CT,PT,Xroot,1).
11
行注释:
1. 确保装入谓词construct/2,用以测试
2. 空行
3. 终止条件
4. 截断
5. 由树T构建树的’轮廓’CT
6. 获取树轮廓
7. 根节点
8. 找最左面位置轮廓的节点
9. 获取根节点水平位置
10. 调用layout_binary_tree3/4
11.
12 contour_tree(nil,nil).
13 contour_tree(t(X,L,R),t(X,CL,CR,Contour)) :-
14 contour_tree(L,CL),
15 contour_tree(R,CR),
16 combine(CL,CR,Contour).
17
行注释:
12. 终止条件
13.
14. 递归左树
15. 递归右树
16. 左,右合成到’根’
17.
18 combine(nil,nil,[]).
19 combine(t(_,_,_,CL),nil,[c(-1,-1)|Cs]) :-
20 shift(CL,-1,Cs).
21 combine(nil,t(_,_,_,CR),[c(1,1)|Cs]) :-
22 shift(CR,1,Cs).
23 combine(t(_,_,_,CL),t(_,_,_,CR),[c(DL,DR)|Cs]) :-
24 maxdiff(CL,CR,MD,0),
25 DR is (MD+2)//2,
26 DL is -DR,
27 merge(CL,CR,DL,DR,Cs).
28
行注释:
18.
19.
20. 向左右空减1
21.
22. 向右左空增1
23.
24. 由CL,CR得到最大差值MD
25. 再深一层右节点轮廓值
26. 再深一层左节点轮廓值
27. 完成再深一层的合并
28. 空行
29 shift([],_,[]).
30 shift([c(L,R)|Cs],S,[c(LS,RS)|CsS]) :-
31 LS is L+S, RS is R+S,
32 shift(Cs,S,CsS).
33
34 maxdiff([],_,MD,MD) :- !.
35 maxdiff(_,[],MD,MD) :- !.
36 maxdiff([c(_,R1)|Cs1],[c(L2,_)|Cs2],MD,A) :-
37 A1 is max(A,R1-L2),
38 maxdiff(Cs1,Cs2,MD,A1).
39
行注释:
29. 终止条件
30. 层处理
31. 由S得到LS和RS
32. 递归处理深一层
33.
34. 第1参数(左)为空终止并截断
35. 第2参数(右)为空终止并截断
36. 最大差
37. 原最大值与R1,L2变量的差取最大值给A1
38. 递归继续去值A1,到达终点参数3,4合一得到嘴大值
39. 空行
40 merge([],CR,_,DR,Cs) :-
41 !,
42 shift(CR,DR,Cs).
43 merge(CL,[],DL,_,Cs) :-
44 !,
45 shift(CL,DL,Cs).
46 merge([c(L1,_)|Cs1],[c(_,R2)|Cs2],DL,DR,[c(L,R)|Cs]) :-
47 L is L1+DL,
48 R is R2+DR,
49 merge(Cs1,Cs2,DL,DR,Cs).
50
行注释:
40. 右合并,第1参数(左)为空
41. 截断
42. 层处理
43. 左合并,第2参数(右)为空
44. 截断
45. 层处理
46. 合并
47. 计算得到L
48. 计算得到R
49. 得到深一层DL,DR
50. 空行
51 mincont([],MC,MC).
52 mincont([c(L,_)|Cs],MC,A) :-
53 A1 is min(A,L),
54 mincont(Cs,MC,A1).
55
行注释:
51. 终止条件
52. 获取’轮廓’最小值
53. 本层最小值赋给A1
54. 继续递归到下一层
55.
56 layout_binary_tree3(nil,nil,_,_).
57 layout_binary_tree3(t(W,nil,nil,_),t(W,X,Y,nil,nil),X,Y) :-
58 !.
59 layout_binary_tree3(t(W,L,R,[c(DL,DR)|_]),t(W,X,Y,PL,PR),X,Y):-
60 Y1 is Y + 1,
61 Xleft is X + DL,
62 layout_binary_tree3(L,PL,Xleft,Y1),
63 Xright is X + DR,
64 layout_binary_tree3(R,PR,Xright,Y1).
行注释:
56. 终止条件
57.
58. 截断
59.
60. Y增1赋值给Y1
61. 获取左树值
62. 往左“走”
63. 获取右树值
64. 往右“走”
测试:
?- consult('p4_15.pl').
true.
?- construct([n,k,m,c,a,e,d,g,u,p,q],T),layout_binary_tree3(T,PT).
T = t(n, t(k, t(c, t(a, nil, nil), t(e, t(d, nil, nil), t(g, nil, nil))), t(m, nil, nil)), t(u, t(p, nil, t(q, nil, nil)), nil)),
PT = t(n, 5, 1, t(k, 3, 2, t(c, 2, 3, t(a, 1, 4, nil, nil), t(e, 3, 4, t(d, 2, 5, nil, nil), t(g, 4, 5, nil, nil))), t(m, 4, 3, nil, nil)), t(u, 7, 2, t(p, 6, 3, nil, t(q, 7, 4, nil, nil)), nil)) .
?-
16、二叉树的字符串表示形式
将二叉树表示为以下类型的字符串
a(b(d,e),c(,f(g,)))
为简单起见,假设节点中的信息是单个字母,并且字符串中没有空格。
a、编写Prolog谓词,如果给定了树,则生成该字符串表示形式像往常一样(为零或t(X,L,R)项)。然后写一个谓词做相反的事情。即给定字符串表示形式,以通常的形式构造树。最后,将两个谓词组合在一个可以在两个方向上使用的单个谓词tree_string/2中。
二叉树的字符串表示形式,字符串表示形式具有以下语法:
<tree> ::= | <letter><subtrees>
<subtrees> ::= | '(' <tree> ',' <tree> ')'
根据此语法,叶节点(带有字母x)可以用x(,)表示,而不仅可以用单个字符x表示。但是,在生成字符串表示形式时,我们将避免这种情况。
1 tree_string(T,S) :-
2 nonvar(T),
3 !,
4 tree_to_string(T,S).
5 tree_string(T,S) :-
6 nonvar(S),
7 string_to_tree(S,T).
8
行注释:
1.
2. 确定T不是变量
3. 截断
4. 树转为字符串
5.
6. 确定S不是变量
7. 字符串转成树
8. 空行
9 tree_to_string(T,S) :-
10 tree_to_list(T,L),
11 atom_chars(S,L).
12
13 tree_to_list(nil,[]).
14 tree_to_list(t(X,nil,nil),[X]) :-
15 !.
16 tree_to_list(t(X,L,R),[X,'('|List]) :-
17 tree_to_list(L,LsL),
18 tree_to_list(R,LsR),
19 append(LsL,[','],List1),
20 append(List1,LsR,List2),
21 append(List2,[')'],List).
22
行注释:
9. 将树转成字符串
10. 将树T转换为列表L
11. 将列表L转成字符串S
12. 空行
13. 终止条件(nil)
14. 终止条件树为终结点形如(X,nil,nil)
15. 截断
16. 分部展开树
17. 递归左树
18. 递归右树
19. 将列表LsL后根’,’元素,放入列表List1
20. 将列表List1后跟列表LsR中的元素得到结果列表List2
21. 将列表List2后跟元素’)’,得到结果表List
22.
23 string_to_tree(S,T) :-
24 atom_chars(S,L),
25 list_to_tree(L,T).
26
27 list_to_tree([],nil).
28 list_to_tree([X],t(X,nil,nil)) :-
29 char_type(X,alpha).
30 list_to_tree([X,'('|List],t(X,Left,Right)) :-
31 char_type(X,alpha),
32 append(List1,[')'],List),
33 append(LeftList,[','|RightList],List1),
34 list_to_tree(LeftList,Left),
35 list_to_tree(RightList,Right).
36
行注释:
23. 将字符串转成树
24. 将字符串S转换为列表L
25. 将列表L转为树
26. 空行
27. 终止条件
28. 确保X属于alpha集合,也是终止条件
29. 依次取列中表元素
30. 取列表中元素X及’(’
31. 确保X属于alpha
32. 取出列表List的最后一个元素是’)’,剩余元素给List1
33. 将列表List1以元素为’,’分解成列表LeftList和列表RightList
34. 递归左列表继续转成树Left
35. 递归右列表继续转成树Right
36.
测试:
?- consult('p4_16a.pl').
true.
?- tree_string(t(a, t(b, t(d, nil, nil), t(e, nil, nil)), t(c, nil, t(f, t(g, nil, nil), nil))),S).
S = 'a(b(d,e),c(,f(g,)))'.
?- tree_string(T,'a(b(d,e),c(,f(g,)))').
T = t(a, t(b, t(d, nil, nil), t(e, nil, nil)), t(c, nil, t(f, t(g, nil, nil), nil))) .
?-
b、使用差异列表和单个谓词tree_dlist/2编写相同的谓词tree_string/2,该谓词在两个方向上进行树和差异列表之间的转换。
二叉树的字符串表示形式使用差异列表的优雅设计。
1 tree_string(T,S) :-
2 nonvar(T),
3 tree_dlist(T,L-[]),
4 !,
5 atom_chars(S,L).
6 tree_string(T,S) :-
7 nonvar(S),
8 atom_chars(S,L),
9 tree_dlist(T,L-[]).
10
行注释:
1. 将树T转为字符串S
2. 确保T不是变量
3. 将树T转换为列表
4. 截断
5. 将列表L转换为字符串S
6. 将字符串S转为树T
7. 确保S不是变量
8. 将字符串S转换为列表L
9. 将列表L转换为树T
10. 空行
tree_dlist/2 双向都可以操作!
11 tree_dlist(nil,L-L).
12 tree_dlist(t(X,nil,nil),L1-L2) :-
13 letter(X,L1-L2).
14 tree_dlist(t(X,Left,Right),L1-L7) :-
15 letter(X,L1-L2),
16 symbol('(',L2-L3),
17 tree_dlist(Left,L3-L4),
18 symbol(',',L4-L5),
19 tree_dlist(Right,L5-L6),
20 symbol(')',L6-L7).
21
行注释:
11. 终止条件
12. 差异列表对应的是形如t(X,nil,nil)的树则终止
13. 获取差异表的字符X
14.
15. 字符后一定是左圆括号
16. 列表中匹配左圆括号
17. 递归左树
18. 匹配左右树的分隔符
19. 递归右树
20. 有括号匹配到结尾
21. 空行
22 symbol(X,[X|Xs]-Xs).
23
24 letter(X,L1-L2) :-
25 symbol(X,L1-L2),
26 char_type(X,alpha).
行注释:
22. 拆分或匹配列表的首尾
23. 空行
24. 字符与差异表,反之亦然双向
25. 取字符
26. 确保字符是alpha集
测试:
?- consult('p4_16b.pl').
true.
?- tree_string(T,'a(b(d,e),c(,f(g,)))').
T = t(a, t(b, t(d, nil, nil), t(e, nil, nil)), t(c, nil, t(f, t(g, nil, nil), nil))) .
?- tree_string(t(a, t(b, t(d, nil, nil), t(e, nil, nil)), t(c, nil, t(f, t(g, nil, nil), nil))),S).
S = 'a(b(d,e),c(,f(g,)))'.
?-
17、二叉树的预排序和有序序列
a、写谓词preorder/2和inorder/2,分别构造给定二叉树的预排序和有序序列。结果应该是原子,例如问题16中示例的预序列的’abdecfg’。
我们考虑二进制树,其节点由单个小写字母标识。
1 preorder(T,S) :-
2 preorder_tl(T,L),
3 atom_chars(S,L).
4
5 preorder_tl(nil,[]).
6 preorder_tl(t(X,Left,Right),[X|List]) :-
7 preorder_tl(Left,ListLeft),
8 preorder_tl(Right,ListRight),
9 append(ListLeft,ListRight,List).
10
行注释:
1. 树T与字符串S互转
2. 树T与列表L互转
3. 字符串S与列表L互转
4. 空行
5. 终止条件
6. 取元素X(树与列表是双向的)(看第程序第16行)
7. 递归用左边的元素
8. 递归用右边的元素
9. 拆分与合并双向(对照程序19行)
10. 空行
11 inorder(T,S) :-
12 inorder_tl(T,L),
13 atom_chars(S,L).
14
15 inorder_tl(nil,[]).
16 inorder_tl(t(X,Left,Right),List) :-
17 inorder_tl(Left,ListLeft),
18 inorder_tl(Right,ListRight),
19 append(ListLeft,[X|ListRight],List).
行注释:
11. 树T与字符串S互转
12. 树T与列表L互转
13. 字符串S与列表L互转
14.
15. 终止条件
16. 取元素X(树与列表是双向的)
17. 递归用左边的元素
18. 递归用右边的元素
19. 拆分与合并双向
测试:
?- preorder(T,abcdefg).
T = t(a, nil, t(b, nil, t(c, nil, t(d, nil, t(e, nil, t(f, nil, t(g, nil, nil))))))) .
?- preorder(t(a, nil, t(b, nil, t(c, nil, t(d, nil, t(e, nil, t(f, nil, t(g, nil, nil))))))),S).
S = abcdefg.
?- inorder(T,abcdefg).
T = t(a, nil, t(b, nil, t(c, nil, t(d, nil, t(e, nil, t(f, nil, t(g, nil, nil))))))) .
?- inorder(t(a, nil, t(b, nil, t(c, nil, t(d, nil, t(e, nil, t(f, nil, t(g, nil, nil))))))),S).
S = abcdefg.
?-
b、您可以反向使用问题a中的preorder/2吗?即给定一个预排序列,构造一个相应的树?如果没有,请进行必要的修改。
与7.a.的设计类似。但是,对于流模式(-,+),我们必须在preorder_l/2和inorder_l/2的第二个子句中修改子目标的顺序
preorder(T,S) :- S是二叉树T的节点的预遍历序列。(tree,atom) (+,?) or (?,+)
1 preorder(T,S) :-
2 nonvar(T),
3 !,
4 preorder_tl(T,L),
5 atom_chars(S,L).
行注释:
1. 将树T转为原子S
2. 确保不是变量
3. 截断
4. 树T转为列表L
5. 将原子S转为列表L
6 preorder(T,S) :-
7 atom(S),
8 atom_chars(S,L),
9 preorder_lt(T,L).
10
行注释:
6. 将原子S转转为树T
7. 确保S是原子
8. 将原子S转为列表L
9. 列表L转为树T
10.
11 preorder_tl(nil,[]).
12 preorder_tl(t(X,Left,Right),[X|List]) :-
13 preorder_tl(Left,ListLeft),
14 preorder_tl(Right,ListRight),
15 append(ListLeft,ListRight,List).
16
行注释:
11. 树转列表
12. 取树中元素X归入第2个参数的列表
13. 递归左树
14. 递归右树
15. 左右树列表归入列表List
16. 空行
17 preorder_lt(nil,[]).
18 preorder_lt(t(X,Left,Right),[X|List]) :-
19 append(ListLeft,ListRight,List),
20 preorder_lt(Left,ListLeft),
21 preorder_lt(Right,ListRight).
22
行注释:
17. 列表转树
18. 取列表中元素X放入树的本层节点
19. 将总列表List分成左右两个列表
20. 递归左
21. 递归右
22. 空行
inorder(T,S) :- S是二叉树T的节点的有序树遍历序列(tree,atom) (+,?) or (?,+)
23 inorder(T,S) :-
24 nonvar(T),
25 !,
26 inorder_tl(T,L),
27 atom_chars(S,L).
28 inorder(T,S) :-
29 atom(S),
30 atom_chars(S,L),
31 inorder_lt(T,L).
32
行注释:
23. 树T转成原子S
24. 确保T不是变量
25. 截断
26. 将树转成列表
27. 将原子S转为列表L
28. 将原子S转为树T
29. 确保S是原子
30. 将原子S转为列表L
31. 列表L转树T
32.
33 inorder_tl(nil,[]).
34 inorder_tl(t(X,Left,Right),List) :-
35 inorder_tl(Left,ListLeft),
36 inorder_tl(Right,ListRight),
37 append(ListLeft,[X|ListRight],List).
38
行注释:
33. 树转列表
34. 取树中本节点元素X
35. 递归左树
36. 递归右树
37. 将X放入右列表中然后都归入List
38.
39 inorder_lt(nil,[]).
40 inorder_lt(t(X,Left,Right),List) :-
41 append(ListLeft,[X|ListRight],List),
42 inorder_lt(Left,ListLeft),
43 inorder_lt(Right,ListRight).
行注释:
39. 列表转树
40. 将列表List中的一个元素放入本层树的节点
41. 将列表List拆分成左右两个列表,以X为界且X归属在右列表中
42. 递归左树
43. 递归右树
测试:
?- consult('p4_17b.pl').
true.
?- preorder(T,abcdefg).
T = t(a, nil, t(b, nil, t(c, nil, t(d, nil, t(e, nil, t(f, nil, t(g, nil, nil))))))) .
?- preorder(t(a, nil, t(b, nil, t(c, nil, t(d, nil, t(e, nil, t(f, nil, t(g, nil, nil))))))),S).
S = abcdefg.
?- inorder(T,abcdefg).
T = t(a, nil, t(b, nil, t(c, nil, t(d, nil, t(e, nil, t(f, nil, t(g, nil, nil))))))) .
?- inorder(t(a, nil, t(b, nil, t(c, nil, t(d, nil, t(e, nil, t(f, nil, t(g, nil, nil))))))),S).
S = abcdefg.
?-
C、如果既给出了二叉树节点的预排序列又给出了有序序列,那么就明确地确定了该树。编写完成该工作的谓词pre_in_tree/3。
二叉树的预排序和有序序列
如果既给出了二叉树节点的预排序序列又给出了有序序列,那么就明确地确定了该树。
pre_in_tree(P,I,T) :- T是具有预序列P和有序序列I的二叉树(atom,atom,tree) (+,+,?)
:- ensure_loaded(p4_17b).%装载p2_17b(前一例)
pre_in_tree(P,I,T) :-
preorder(T,P),
inorder(T,I).
测试:
?- consult('p4_17c.pl').
true.
?- pre_in_tree(abcdefg,I,T).
I = abcdefg,
T = t(a, nil, t(b, nil, t(c, nil, t(d, nil, t(e, nil, t(f, nil, t(g, nil, nil))))))) .
?- pre_in_tree(P,I,t(a, nil, t(b, nil, t(c, nil, t(d, nil, t(e, nil, t(f, nil, t(g, nil, nil)))))))).
P = I, I = abcdefg.
?-
这是generate-and-test方法的不错的应用
可以将测试器推入发生器内部以获得更好的性能。
pre_in_tree_push(P,I,T) :-
atom_chars(P,PL),
atom_chars(I,IL),
pre_in_tree_pu(PL,IL,T).
pre_in_tree_pu([],[],nil).
pre_in_tree_pu([X|PL],IL,t(X,Left,Right)) :-
append(ILeft,[X|IRight],IL),
append(PLeft,PRight,PL),
pre_in_tree_pu(PLeft,ILeft,Left),
pre_in_tree_pu(PRight,IRight,Right).
真好但是还有一个更好的解决方案。见问题d
测试:
上述代码自己尝试做一下声明
?- consult('p4_17c.pl').
true.
?- pre_in_tree_push(abcdefg,badcegf,T).
T = t(a, t(b, nil, nil), t(c, t(d, nil, nil), t(e, nil, t(f, t(g, nil, nil), nil)))) .
?-
d、使用差异列表解决问题a至c。酷!使用预定义的谓词time/1来比较解决方案。
如果同一字符出现在多个节点中,会发生什么情况。尝试例如pre_in_tree(aba,baa,T)。
使用差异列表来完成
pre_in_tree_d(P,I,T) :- T是具有预序列P和有序序列I的二叉树。(atom,atom,tree) (+,+,?)
pre_in_tree_d(P,I,T) :-
atom_chars(P,PL),
atom_chars(I,IL),
pre_in_tree_dl(PL-[],IL-[],T).
pre_in_tree_dl(P-P,I-I,nil).
pre_in_tree_dl(P1-P4,I1-I4,t(X,Left,Right)) :-
symbol(X,P1-P2), symbol(X,I2-I3),
pre_in_tree_dl(P2-P3,I1-I2,Left),
pre_in_tree_dl(P3-P4,I3-I4,Right).
symbol(X,[X|Xs]-Xs).
上述代码自己尝试做一下声明式注释
测试:
?- time(pre_in_tree(abdecfg,dbeacgf,_)).
% 8,616 inferences, 0.002 CPU in 0.002 seconds (88% CPU, 4182524 Lips)
true .
?- time(pre_in_tree_push(abdecfg,dbeacgf,_)).
% 65 inferences, 0.000 CPU in 0.000 seconds (82% CPU, 1756757 Lips)
true .
?- consult('p4_17d.pl').
true.
?- time(pre_in_tree_d(abdecfg,dbeacgf,_)).
% 30 inferences, 0.000 CPU in 0.000 seconds (70% CPU, 1250000 Lips)
true .
18、二叉树的点串表示
我们再次考虑二进制树,其节点由单个小写字母标识,如问题16的示例所示。这样的树可以由其节点的预排序序列表示,其中在树遍历过程中遇到空子树(nil)的地方插入点(.)。例如,问题16中显示的树表示为’abd..e..c.fg…’。首先,尝试建立语法(BNF或语法图),然后编写谓词tree_dotstring/2,该谓词在两个方向上进行转换。使用差异列表。
点串表示的语法如下:
<tree> ::= . | <letter> <tree> <tree>
1 tree_dotstring(T,S) :-
2 nonvar(T),
3 !,
4 tree_dots_dl(T,L-[]),
5 atom_chars(S,L).
6 tree_dotstring(T,S) :-
7 atom(S),
8 atom_chars(S,L),
9 tree_dots_dl(T,L-[]).
10
行注释:
1.
2. 确保T不是变量
3. 截断
4. 完整树T的差异表是L-[]
5. 将列表L转换成字符串S
6.
7. 确保S是原子
8. 将原子S转换成列表L
9. 差异表是L-[]的完整树是T
10.
11 tree_dots_dl(nil,L1-L2) :-
12 symbol(‘.’,L1-L2).
13 tree_dots_dl(t(X,Left,Right),L1-L4) :-
14 letter(X,L1-L2),
15 tree_dots_dl(Left,L2-L3),
16 tree_dots_dl(Right,L3-L4).
17
18 symbol(X,[X|Xs]-Xs).
19
20 letter(X,L1-L2) :-
21 symbol(X,L1-L2),
22 char_type(X,alpha).
23
行注释:
11. nil匹配列表的内的’.’元素
12. ’.’与nil双向互转
13.
14. 取字符X
15. 递归左树
16. 递归右树
17. 空行
18. 拆分或匹配列表的首尾
19. 空行
20. 字符与差异表,反之亦然双向
21. 取字符
22. 确保X的值是alpha字符集
23.
测试:
?- consult('p4_18.pl').
true.
?- tree_dotstring(t(a,t(b,t(c, nil,nil),t(d, nil, nil)),t(e, t(f, nil,nil), t(g, t(h, nil, nil), nil))),S).
S = 'abc..d..ef..gh...' .
?- tree_dotstring(T,'abc..d..ef..gh...').
T = t(a, t(b, t(c, nil, nil), t(d, nil, nil)), t(e, t(f, nil, nil), t(g, t(h, nil, nil), nil))) .
?-