1、1,第五章 树与二叉树,数据结构电子教案,殷人昆,2,第五章 树与二叉树,树和森林的概念 二叉树 二叉树遍历 二叉树的计数 线索化二叉树 树与森林 堆 Huffman树,3,树和森林的概念,两种树:自由树与有根树。 自由树:一棵自由树 Tf 可定义为一个二元组Tf = (V, E) 其中V = v1, ., vn 是由 n (n0) 个元素组成的有限非空集合,称为顶点集合。E = (vi, vj) | vi, vj V, 1i, jn 是n-1个序对的集合,称为边集合,E 中的元素 (vi, vj)称为边或分支。,4,自由树,有根树:一棵有根树 T,简称为树,它是n (n0) 个结点的有限集合
2、。当n = 0时,T 称为空树;否则,T 是非空树,记作,5,r 是一个特定的称为根(root)的结点,它只有直接后继,但没有直接前驱; 根以外的其他结点划分为 m (m 0) 个互不相交的有限集合T1, T2, , Tm,每个集合又是一棵树,并且称之为根的子树。 每棵子树的根结点有且仅有一个直接前驱,但可以有0个或多个直接后继。,6,树的基本术语,子女:若结点的子树非空,结点子树的根即为该结点的子女。 双亲:若结点有子女,该结点是子女双亲。,7,兄弟:同一结点的子女互称为兄弟。 度:结点的子女个数即为该结点的度;树中各个结点的度的最大值称为树的度。 分支结点:度不为0的结点即为分支结点,亦称
3、为非终端结点。 叶结点:度为0的结点即为叶结点,亦称为终端结点。 祖先:某结点到根结点的路径上的各个结点都是该结点的祖先。 子孙:某结点的所有下属结点,都是该结点的子孙。,8,结点的层次:规定根结点在第一层,其子女结点的层次等于它的层次加一。以下类推。 深度:结点的深度即为结点的层次;离根最远结点的层次即为树的深度。,9,高度:规定叶结点的高度为1,其双亲结点的高度等于它的高度加一。 树的高度:等于根结点的高度,即根结点所有子女高度的最大值加一。 有序树:树中结点的各棵子树 T0, T1, 是有次序的,即为有序树。 无序树:树中结点的各棵子树之间的次序是不重要的,可以互相交换位置。 森林:森林
4、是m(m0)棵树的集合。,10,树的抽象数据类型,template class Tree /对象: 树是由n (0) 个结点组成的有限集合。在 /类界面中的 position 是树中结点的地址。在顺序 /存储方式下是下标型, 在链表存储方式下是指针 /型。T 是树结点中存放数据的类型, 要求所有结 /点的数据类型都是一致的。 public:Tree (); Tree ();,11,BuildRoot (const T/在结点 p 下插入值为 value 的新子女, 若插/入失败, 函数返回false, 否则返回true,12,bool DeleteChild (position p, int
5、i);/删除结点 p 的第 i 个子女及其全部子孙结/点, 若删除失败, 则返回false, 否则返回truevoid DeleteSubTree (position t);/删除以 t 为根结点的子树bool IsEmpty ();/判树空否, 若空则返回true, 否则返回falsevoid Traversal (void (*visit)(position p); /遍历以 p 为根的子树 ;,13,二叉树的五种不同形态,L,L,R,R,二叉树 (Binary Tree),二叉树的定义一棵二叉树是结点的一个有限集合,该集合或者为空,或者是由一个根结点加上两棵分别称为左子树和右子树的、互不
6、相交的二叉树组成。,14,二叉树的性质,性质1 若二叉树结点的层次从 1 开始, 则在二叉树的第 i 层最多有 2i-1 个结点。( i1)证明用数学归纳法性质2 深度为 k 的二叉树最少有 k 个结点,最多有 2k-1个结点。( k1 )因为每一层最少要有1个结点,因此,最少结点数为 k。最多结点个数借助性质1:用求等比级数前k项和的公式20 +21 +22 + +2k-1 = 2k-1,15,性质3 对任何一棵二叉树,如果其叶结点有 n0 个, 度为 2 的非叶结点有 n2 个, 则有n0n21若设度为 1 的结点有 n1 个,总结点数为n,总边数为e,则根据二叉树的定义,n = n0+n
7、1+n2 e = 2n2+n1 = n-1因此,有 2n2+n1 = n0+n1+n2-1n2 = n0-1 n0 = n2+1,16,定义1 满二叉树 (Full Binary Tree) 定义2 完全二叉树 (Complete Binary Tree) 若设二叉树的深度为 k,则共有 k 层。除第 k 层外,其它各层 (1k-1) 的结点数都达到最大个数,第k层从右向左连续缺若干结点,这就是完全二叉树。,17,性质4 具有 n (n0) 个结点的完全二叉树的深度为 log2(n+1) 设完全二叉树的深度为k,则有2k-1-1 n 2k-1变形 2k-1 n+12k 取对数 k-1 log2
8、(n+1) k 有log2(n+1) = k,上面k-1层结点数,包括第k层的最大结点数,23-1,24-1,18,性质5 如将一棵有n个结点的完全二叉树自顶向下,同一层自左向右连续给结点编号1, 2, , n,则有以下关系: 若i = 1, 则 i 无双亲 若i 1, 则 i 的双亲为i2 若2*i = n, 则 i 的左子女为 2*i,若2*i+1 = n, 则 i 的右子女为2*i+1 若 i 为奇数, 且i != 1, 则其左兄弟为i-1, 若 若 i 为偶数, 且i != n, 则其右兄弟为i+1,19,二叉树的抽象数据类型,template class BinaryTree /对象
9、: 结点的有限集合, 二叉树是有序树 public:BinaryTree (); /构造函数BinaryTree (BinTreeNode *lch, BinTreeNode *rch, T item);/构造函数, 以item为根, lch和rch为左、右子/树构造一棵二叉树int Height (); /求树深度或高度int Size (); /求树中结点个数,20,bool IsEmpty (); /判二叉树空否? BinTreeNode *Parent (BinTreeNode *t);/求结点 t 的双亲BinTreeNode *LeftChild (BinTreeNode *t);
10、/求结点 t 的左子女BinTreeNode *RightChild (BinTreeNode *t);/求结点 t 的右子女bool Insert (T item); /在树中插入新元素bool Remove (T item); /在树中删除元素bool Find (T /取得结点数据,21,BinTreeNode *getRoot (); /取根void preOrder (void (*visit) (BinTreeNode *t); /前序遍历, visit是访问函数void inOrder (void (*visit) (BinTreeNode *t);/中序遍历, visit是访问
11、函数void postOrder (void (*visit) (BinTreeNode *t);/后序遍历, (*visit)是访问函数void levelOrder (void (*visit)(BinTreeNode *t);/层次序遍历, visit是访问函数 ;,22,正则二叉树 理想平衡二叉树,满的,23,完全二叉树 一般二叉树 的顺序表示 的顺序表示,二叉树的顺序表示,24,极端情形: 只有右单支的二叉树,25,二叉树结点定义:每个结点有3个数据成员,data域存储结点数据,leftChild和rightChild分别存放指向左子女和右子女的指针。,二叉链表,二叉树的链表表示(二
12、叉链表),26,三叉链表,二叉树的链表表示(三叉链表),每个结点增加一个指向双亲的指针parent,使得查找双亲也很方便。,27,二叉树链表表示的示例,二叉树 二叉链表 三叉链表,28,二叉链表的静态结构,29,二叉树的类定义,template struct BinTreeNode /二叉树结点类定义T data; /数据域BinTreeNode *leftChild, *rightChild;/左子女、右子女链域BinTreeNode () /构造函数 leftChild = NULL; rightChild = NULL; BinTreeNode (T x, BinTreeNode *l
13、= NULL, BinTreeNode *r = NULL) data = x; leftChild = l; rightChild = r; ;,30,template class BinaryTree /二叉树类定义 public:BinaryTree () : root (NULL) /构造函数BinaryTree (T value) : RefValue(value), root(NULL) /构造函数BinaryTree (BinaryTree /求结点数,31,BinTreeNode *Parent (BinTreeNode *t) return (root = NULL | ro
14、ot = t) ?NULL : Parent (root, t); /返回双亲结点BinTreeNode *LeftChild (BinTreeNode *t) return (t != NULL)?t-leftChild : NULL; /返回左子女BinTreeNode *RightChild (BinTreeNode *t) return (t != NULL)?t-rightChild : NULL; /返回右子女BinTreeNode *getRoot () const return root; /取根,32,void preOrder (void (*visit) (BinTree
15、Node *t) preOrder (root, visit); /前序遍历void inOrder (void (*visit) (BinTreeNode *t) inOrder (root, visit); /中序遍历void postOrder (void (*visit) (BinTreeNode *t) postOrder (root, visit); /后序遍历void levelOrder (void (*visit)(BinTreeNode *t); /层次序遍历int Insert (const T item); /插入新元素 BinTreeNode *Find (T ite
16、m) const; /搜索,33,protected:BinTreeNode *root; /二叉树的根指针T RefValue; /数据输入停止标志void CreateBinTree (istream /查找,34,BinTreeNode *Copy (BinTreeNode *r); /复制int Height (BinTreeNode *subTree); /返回树高度int Size (BinTreeNode *subTree); /返回结点数BinTreeNode *Parent (BinTreeNode * subTree, BinTreeNode *t); /返回父结点BinT
17、reeNode *Find (BinTreeNode * subTree, T /搜寻x,35,void Traverse (BinTreeNode *subTree, ostream /后序遍历,36,friend istreamtemplate BinTreeNode *BinaryTree: Parent (BinTreeNode *subTree, BinTreeNode *t) ,部分成员函数的实现,37,/私有函数: 从结点 subTree 开始, 搜索结点 t 的双 /亲, 若找到则返回双亲结点地址, 否则返回NULLif (subTree = NULL) return NULL
18、;if (subTree-leftChild = t | subTree-rightChild = t ) return subTree; /找到, 返回父结点地址BinTreeNode *p;if (p = Parent (subTree-leftChild, t) != NULL) return p; /递归在左子树中搜索else return Parent (subTree-rightChild, t);/递归在左子树中搜索 ;,38,template void BinaryTree: destroy (BinTreeNode * subTree) /私有函数: 删除根为subTree的
19、子树if (subTree != NULL) destroy (subTree-leftChild); /删除左子树destroy (subTree-rightChild); /删除右子树delete subTree; /删除根结点 ;,39,template istream,40,二叉树遍历,二叉树的遍历就是按某种次序访问树中的结点,要求每个结点访问一次且仅访问一次。 设访问根结点记作 V遍历根的左子树记作 L遍历根的右子树记作 R 则可能的遍历次序有前序 VLR 镜像 VRL中序 LVR 镜像 RVL后序 LRV 镜像 RLV,41,中序遍历二叉树算法的框架是: 若二叉树为空,则空操作;
20、否则 中序遍历左子树 (L); 访问根结点 (V); 中序遍历右子树 (R)。遍历结果a + b * c - d - e / f,中序遍历 (Inorder Traversal),-,-,/,+,*,a,b,c,d,e,f,42,二叉树递归的中序遍历算法,template void BinaryTree:InOrder (BinTreeNode * subTree, void (*visit) (BinTreeNode *t) if (subTree != NULL) InOrder (subTree-leftChild, visit); /遍历左子树visit (subTree); /访问根
21、结点InOrder (subTree-rightChild, visit);/遍历右子树 ;,43,前序遍历二叉树算法的框架是: 若二叉树为空,则空操作; 否则 访问根结点 (V); 前序遍历左子树 (L); 前序遍历右子树 (R)。遍历结果- + a * b - c d / e f,前序遍历 (Preorder Traversal),-,-,/,+,*,a,b,c,d,e,f,44,二叉树递归的前序遍历算法,template void BinaryTree:PreOrder (BinTreeNode * subTree, void (*visit) (BinTreeNode *t) if (
22、subTree != NULL) visit (subTree); /访问根结点PreOrder (subTree-leftChild, visit);/遍历左子树PreOrder (subTree-rightChild, visit);/遍历右子树 ;,45,后序遍历二叉树算法的框架是: 若二叉树为空,则空操作; 否则 后序遍历左子树 (L); 后序遍历右子树 (R); 访问根结点 (V)。遍历结果a b c d - * + e f / -,后序遍历 (Postorder Traversal),-,-,/,+,*,a,b,c,d,e,f,46,二叉树递归的后序遍历算法,template vo
23、id BinaryTree:PostOrder (BinTreeNode * subTree, void (*visit) (BinTreeNode *t ) if (subTree != NULL ) PostOrder (subTree-leftChild, visit); /遍历左子树PostOrder (subTree-rightChild, visit); /遍历右子树visit (subTree); /访问根结点 ;,47,template int BinaryTree:Size (BinTreeNode * subTree) const /私有函数:利用二叉树后序遍历算法计算二叉
24、 /树的结点个数if (subTree = NULL) return 0; /空树else return 1+Size (subTree-leftChild) + Size (subTree-rightChild); ;,应用二叉树遍历的事例,48,template int BinaryTree:Height ( BinTreeNode * subTree) const /私有函数:利用二叉树后序遍历算法计算二叉 /树的高度或深度if (subTree = NULL) return 0; /空树高度为0else int i = Height (subTree-leftChild);int j
25、= Height (subTree-rightChild);return (i j) ? j+1 : i+1; ;,49,利用二叉树前序遍历建立二叉树,以递归方式建立二叉树。 输入结点值的顺序必须对应二叉树结点前序遍历的顺序。并约定以输入序列中不可能出现的值作为空结点的值以结束递归, 此值在RefValue中。例如用“”或用“-1”表示字符序列或正整数序列空结点。,50,如图所示的二叉树的前序遍历顺序为 A B C D E G F ,51,template void BinaryTree:CreateBinTree (ifstream,52,CreateBinTree (in, subTree
26、-leftChild); /递归建立左子树CreateBinTree (in, subTree-rightChild);/递归建立右子树else subTree = NULL; /封闭指向空子树的指针 ;,53,c,d c,c,访问 a 进栈 c 左进 b,访问 b 进栈 d 左进 空,退栈 d 访问 d 左进 空,退栈 c 访问 c 左进 e,访问 e 左进 空 栈空 结束,利用栈的前序遍历非递归算法,54,利用栈的前序遍历非递归算法,template void BinaryTree: PreOrder (void (*visit) (BinTreeNode *t) ) stack* S;B
27、inTreeNode *p = root; S.Push (NULL);while (p != NULL) visit(p); /访问结点if (p-rightChild != NULL)S.Push (p-rightChild); /预留右指针在栈中,55,if (p-leftChild != NULL) p = p-leftChild; /进左子树else S.Pop(p); /左子树为空 ;template void BinaryTree: InOrder (void (*visit) (BinTreeNode *t) stack* S;,利用栈的中序遍历非递归算法,56,a,c,d,e
28、,b a,a,d a,a,左空,退栈 访问,左空,退栈 访问,退栈 访问,左空,e c,退栈访问,c,c,右空,退栈访问 栈空结束,利用栈的中序遍历非递归算法,57,BinTreeNode *p = root; do while (p != NULL) /遍历指针向左下移动S.Push (p); /该子树沿途结点进栈p = p-leftChild;if (!S.IsEmpty() /栈不空时退栈S.Pop (p); visit (p); /退栈, 访问p = p-rightChild; /遍历指针进到右子女 while (p != NULL | !S.IsEmpty (); ;,58,在后序遍
29、历过程中所用栈的结点定义template struct stkNode BinTreeNode *ptr; /树结点指针 enum tag L, R; /退栈标记stkNode (BinTreeNode *N = NULL) :ptr(N), tag(L) /构造函数;tag = L, 表示从左子树退回还要遍历右子树; tag = R,表示从右子树退回要访问根结点。,利用栈的后序遍历非递归算法,59,60,后序遍历的非递归算法,template void BinaryTree: PostOrder (void (*visit) (BinTreeNode *t) Stack S; stkNode
30、 w; BinTreeNode * p = root; /p是遍历指针do while (p != NULL) w.ptr = p; w.tag = L; S.Push (w); p = p-leftChild; int continue1 = 1; /继续循环标记, 用于R,61,while (continue1 ,62,层次序遍历二叉树就是从根结点开始,按层次逐层遍历,如图:,遍历顺序,层次序遍历二叉树的算法,63,这种遍历需要使用一个先进先出的队列,在处理上一层时,将其下一层的结点直接进到队列(的队尾)。在上一层结点遍历完后,下一层结点正好处于队列的队头,可以继续访问它们。 算法是非递归
31、的。,64,a,访问a, 进队,a出队 访问b, 进队 访问c, 进队,b,c,b出队 访问d, 进队,c,d,c出队 访问e, 进队,d,e,d出队,e,e出队,65,层次序遍历的(非递归)算法,template void BinaryTree: levelOrder (void (*visit) (BinTreeNode *t) if (root = NULL) return;Queue * Q;BinTreeNode *p = root; visit (p); Q.EnQueue (p); while (!Q.IsEmpty () Q.DeQueue (p);if (p-leftChil
32、d != NULL) ,66,visit (p-leftChild);Q.EnQueue (p-leftChild);if (p-rightChild != NULL) visit (p-rightChild);Q.EnQueue (p-rightChild); ;,67,二叉树的计数,二叉树遍历的结果是将一个非线性结构中的数据通过访问排列到一个线性序列中。 前序序列:a b d c e 特点是第一个访问的a一定是树根,只要左子树非空,后面紧跟的b 一定是根的左子女, 中序序列:b d a e c 特点是树根 a 把整个中序分成两部分,a 左侧子序列是根的左子树上的结点数据,右侧子序列是根的右
33、子树上的结点数据。,68,由二叉树的前序序列和中序序列可唯一地确定一棵二叉树。 例, 前序序列 A B H F D E C K G 和中序序列 H B D F A E K C G , 构造二叉树过程如下:,69,前序序列 A B H F D E C K G ,70,如果前序序列固定不变,给出不同的中序序列,可得到不同的二叉树。固定前序排列,选择所有可能的中序排列,可能构造多少种不同的二叉树?,71,例如, 有 3 个数据 1, 2, 3 ,可得 5 种不同的二叉树。它们的前序排列均为 123,中序序列可能是 321,231,213,132,123。前序序列为 123,中序序列为 312 的二叉
34、树不存在。,72,有0个, 1个, 2个, 3个结点的不同二叉树如下,73,计算具有n个结点的不同二叉树的棵数,Catalan函数,bi,bn-i-1,1,74,线索化二叉树 (Threaded Binary Tree),又称为穿线树。 通过二叉树的遍历,可将二叉树中所有结点的数据排列在一个线性序列中,可以找到某数据在这种排列下它的前驱和后继。 希望不必每次都通过遍历找出这样的线性序列。只要事先做预处理,将某种遍历顺序下的前驱、后继关系记在树的存储结构中,以后就可以高效地找出某结点的前驱、后继。,75,线索 (Thread),增加前驱Pred指针和后继Succ指针的二叉树,76,这种设计的缺点
35、是每个结点增加两个指针,当结点数很大时存储消耗较大。 改造树结点,将 pred 指针和 succ 指针压缩到 leftChild 和 rightChild 的空闲指针中,并增设两个标志 ltag 和 rtag,指明指针是指示子女还是前驱后继。后者称为线索。ltag (或rtag) = 0,表示相应指针指示左子女(或右子女结点);当ltag (或rtag) = 1, 表示相应指针为前驱(或后继)线索。,77,线索化二叉树及其链表表示,78,线索化二叉树的类定义,template struct ThreadNode /线索二叉树的结点类int ltag, rtag; /线索标志ThreadNode
36、 *leftChild, *rightChild; /线索或子女指针T data; /结点数据ThreadNode ( const T item) /构造函数: data(item), leftChild (NULL),rightChild (NULL), ltag(0), rtag(0) ;,79,template class ThreadTree /线索化二叉树类 protected:ThreadNode *root; /树的根指针void createInThread (ThreadNode *current, ThreadNode * /寻找结点t的双亲结点 public:Thread
37、Tree () : root (NULL) /构造函数,80,void createInThread(); /建立中序线索二叉树ThreadNode *First (ThreadNode *current);/寻找中序下第一个结点ThreadNode *Last (ThreadNode *current);/寻找中序下最后一个结点ThreadNode *Next (ThreadNode *current);/寻找结点在中序下的后继结点ThreadNode *Prior (ThreadNode *current);/寻找结点在中序下的前驱结点;,81,通过中序遍历建立中序线索化二叉树,templ
38、ate void ThreadTree:createInThread () ThreadNode *pre = NULL; /前驱结点指针if (root != NULL) /非空二叉树, 线索化createInThread (root, pre); /中序遍历线索化二叉树pre-rightChild = NULL; pre-rtag = 1; /后处理中序最后一个结点 ;,82,template void ThreadTree: createInThread (ThreadNode *current,ThreadNode * ,83,if (pre != NULL ,84,0 A 0, 0
39、B 0,0 C 0 , 0 D 0 , 0 E 0 ,root,pre = NULL,current,85,0 A 0, 1 B 0,0 C 0 , 0 D 0 , 0 E 0 ,root,pre = NULL,current,86,0 A 0, 1 B 0,0 C 0 ,1 D 0 , 0 E 0 ,root,pre,current,87,0 A 0, 1 B 0,0 C 0 ,1 D 1, 0 E 0 ,root,pre,current,88,0 A 0, 1 B 0,0 C 0 ,1 D 1,1 E 0 ,root,pre,current,89,0 A 0, 1 B 0,0 C 0 ,1
40、 D 1,1 E 1,root,pre,current,90,0 A 0, 1 B 0,0 C 1 ,1 D 1,1 E 1,root,pre,后处理,91,if (current-rtag =1) 后继为current-rightChild else /current-rtag = 0后继为当前结点右子树的中序下的第一个结点,寻找当前结点在中序下的后继,A,B,D,E,C,F,H,I,K,G,J,92,寻找当前结点在中序 下的前驱,if (current-ltag = 1)前驱为current-leftChild else /current-ltag = 0前驱为当前结点左子树中序下的最后一
41、个结点,93,在中序线索化二叉树中部分 成员函数的实现,template ThreadNode * ThreadTree : First (ThreadNode * current) /函数返回以*current为根的线索化二叉树中的中 /序序列下的第一个结点ThreadNode * p = current;while (p-ltag = 0) p = p-leftChild;return p; ;,94,template ThreadNode * ThreadTree : Next (ThreadNode * current) /函数返回在线索化二叉树中结点*current在中序 /下的后继
42、结点ThreadNode *p = current-rightChild;if (current-rtag = 0) return First (p);/rtag = 0, 表示有右子女else return p;/rtag = 1, 直接返回后继线索 ;,95,template void ThreadTree : Inorder (void (*visit) (BinTreeNode *t) /线索化二叉树的中序遍历ThreadNode * p; for (p = First (); p != NULL; p = Next () ) visit (p); ;,96,前序线索化二叉树,在前序线
43、索化二叉树中寻找当前结点的后继,97,后序序列D B E C A,在后序线索化二 叉树中寻找当前 结点的后继,后序线索化二叉树,98,树的存储表示,A(B(E, F), C, D(G) 结点的utype域没有画出,树与森林,1、广义表表示,99,2、双亲表示,树中结点的存放顺序一般不做特殊要求,但为了操作实现的方便,有时也会规定结点的存放顺序。例如,可以规定按树的前序次序存放树中的各个结点,或规定按树的层次次序安排所有结点。,100,3、子女链表表示,无序树情形链表中各结点顺序任意,有序树必须自左向右链接各个子女结点。,101,4、子女指针表示,一个合理的想法是在结点中存放指向每一个子女结点的
44、指针。但由于各个结点的子女数不同,每个结点设置数目不等的指针,将很难管理。 为此,设置等长的结点,每个结点包含的指针个数相等,等于树的度(degree)。 这保证结点有足够的指针指向它的所有子女结点。但可能产生很多空闲指针,造成存储浪费。,102,等数量的链域,空链域2n+1个,103,5、子女-兄弟表示,也称为树的二叉树表示。结点构造为:firstChild 指向该结点的第一个子女结点。无序树时,可任意指定一个结点为第一个子女。 nextSibling 指向该结点的下一个兄弟。任一结点在存储时总是有顺序的。 若想找某结点的所有子女,可先找firstChild,再反复用 nextSibling
45、 沿链扫描。,104,树的子女 -兄弟表示,105,用子女-兄弟表示实现的 树的类定义,template struct TreeNode /树的结点类T data; /结点数据TreeNode *firstChild, *nextSibling;/子女及兄弟指针TreeNode (T value = 0, TreeNode *fc = NULL, TreeNode *ns = NULL) /构造函数: data (value), firstChild (fc), nextSibling (ns) ;,106,template class Tree /树类 private:TreeNode *root, *current; /根指针及当前指针int Find (TreeNode *p, T value); /在以p为根的树中搜索valuevoid RemovesubTree (TreeNode *p); /删除以p为根的子树bool FindParent (TreeNode *t, TreeNode *p); public:,