定义
具有n(n≥)个节点的有穷集合D与D上的关系集合R构成的结构T。即T:=(D,R)。
树的逻辑表示法:树形表示、文氏图表示、凹入表示、嵌套括号表示。
有序树、无序树。
基本概念
双亲节点、祖先节点、兄弟节点、孩子节点、子孙节点
节点的度、树的度、节点的层次、树的深度或称高度。(层次、深度从1起)
根节点、叶子节点(度为0的节点)、分支节点(度非0的节点)、内部节点(非根分支节点)
性质
对于度为k的树:
1、节点数=度数+1
2、第i层最多节点数:k(i-1),i≥1
3、高为i的k叉树节点数最多:(ki-1)/(k-1),i≥1
4、n个节点的k叉树深度最小为:ceil( logk( n(k-1)+1 ) )
存储结构
多重链表:定长链节点个数(二叉树等)、不定长链节点个数
三重链表:每个节点三个指针域(第一个孩子节点、双亲节点、第一个兄弟节点)
对树的操作
构建、遍历、销毁;插入某节点、查找某节点、删除某节点
2、二叉树
二叉树是有序树,有5中基本形态。(度不超过2的树不一定是二叉树,因为二叉树还要求左右子树有序不能颠倒)
n个节点可以构建卡特兰数 f(n)= (k=1~n)Σ(f(k-1)f(n-k)) = (C2n n)/(n+1) ,f(0)=f(1)=1种形态的二叉树。对于有n个节点的有序序列,其BST树也是卡特兰数种。
性质
1、节点数=度数+1
2、第i层节点数:2(i-1),i≥1
3、高为i的二叉树节点数最多:2i-1,i≥1
4、n个节点的二叉树深度最小为:ceil( log2(n+1) ),为理想平衡二叉树时取最小值
5、度为0的节点数=度为2的节点数+1。(因为 节点数n=n0+n1+n2 且 分支数 n-1=n1+2n2,联立可得之)
6、n个节点的完全二叉树从1起对节点从上到下从左到右的编号,编号为i的节点:父节点编号为 floor(i/2),除非该节点已为父节点;左孩子节点编号为2i,除非2i>n即该节点已为叶子节点;右孩子编号为2i+1,除非2i+1>n即右孩子不存在。
推而广之,对于完全m叉树编号为i的节点,其父节点编号为 floor((i+m-2)/m ) ,第j个孩子编号为 mi+j-m+1 。
存储
1、顺序存储:数组。(适用于完全二叉树的存储,一般二叉树可以通过填充虚拟节点当成完全二叉树来存储。缺点是浪费空间)
2、链式存储:
二叉链表(左孩子、右孩子):n个节点的二叉树有n+1个空指针域(空指针域即2n0+n1=n2+1+n0+n1=n+1)。线索二叉树通过利用空指针域指向直接前驱、后继节点来避免遍历时使用堆栈,如中序线索二叉树。
三叉链表(父节点、左孩子、右孩子)
建立
建立:根据输入的序列构建用二叉链表存储的二叉树。这里假定序列为字符串,每个字符对应树中一个节点。
遍历:根据输入序列构建二叉树时需要遍历输入序列,遍历方式有:前序遍历、中序遍历、后序遍历、层次遍历,具体下节介绍。
输入:
首先说下补空,由输入序列(前缀、中缀、后缀皆可)构建二叉树时,如果序列里没有标记一些节点结束信息,则由于无法识别结束或哪些是叶节点从而不能由序列构建出树。所谓补空就是在序列中加入一些特殊字符如'#',加在哪?方式1:通常是序列对应的树的节点的空指针域中,也即度为1和0的节点的空孩子,此时对于n个节点的序列其补空数为n+1;方式2:也可以只对度为1的节点补空。有趣的是如果输入序列是表达式,则用前种补空方式时只有叶节点即操作数补空、用后种补空时没有节点被补空。通常用前种方式补空,某些特殊情况下才用后者。
加括号:把根节点和其左右子树看成一体,在其外围加上括号。左右子树都不存在的节点(叶节点)不用加括号。
百言不如一图,树T按方式1、2分别被补空为T1、T2:
对输入序列:由单一序列就想构建二叉树则需要 序列包含补空信息 或 序列本身包含额外信息(如前缀表达式序列操作符为内部节点操作数为叶节点),要想不补空就能构建二叉树则需多个序列。
总结:(带括号补空:前、中、后序递归; 不带括号补空:前序递归、后序非递归、层次非递归; 不带括号不补空:前后缀表达式、前中序、中后序、中序层次 等),其中,由 不带括号内补空层次序列 构建二叉树最直观最适用,如LeetCode中与树有关的题目的输入。
1、通常而言,输入序列里需要包含补空信息,此时可采用 前序、中序、后序 遍历输入序列来建立二叉树,只要构建过程遍历采用的序与输入序列采用的序一样。分为两种情况:
1)带括号的补空序列(方式1或2)(可按方式1或2补空,不管是T1、T2,有几个内部节点就有几个括号,即为T的节点数或内部节点数)
a、带括号、补空,前序序列(递归构建):
1 void createPreOrder_withBrackets(char prefix[],BTREE &T) 2 { 3 //要求输入的前缀序列带括号 ,并含有对度为0和1的节点的补空特殊字符。此时非特殊字符都成了“内部节点”,有几个非特殊字符就有几个括号。 4 //当然也可以只对度为1的补空,此时有几个非叶节点的非特殊字符就有几个括号。若输入的是前缀表达式,则此情况的内部节点其实就是操作符,叶节点是操作数。 5 //例子:度为0和1的补空:( A( B( D##) ( E( G#( H##) ) #) ) ( C( F##) #) ),只对度为1的补空:( A( BD( E( G#H )# ) )( CF# ) ) 6 //特殊例子(前缀表达式):度为0和1的补空:( *( +( A##) ( /( -( B##) ( C##) ) ( D##) ) ) ( E##) ) ,只对度为1的补空:(*(+A(/(-BC)D))E) 7 char x=nextToken(prefix); 8 9 if(x=='#') 10 { 11 T=NULL; 12 } 13 else 14 { 15 T=(BTREE)malloc(sizeof(BTNode)); 16 T->lchild=NULL; 17 T->rchild=NULL; 18 19 if(x=='(') 20 {//处理括号里的表达式 21 x=nextToken(prefix);//表达式的操作符 22 T->data=x; 23 24 createPreOrder_withBrackets(prefix,T->lchild);//表达式的左操作数 25 26 createPreOrder_withBrackets(prefix,T->rchild);//表达式的右操作数 27 28 nextToken(prefix);//右括号 29 } 30 else 31 { 32 T->data=x; 33 } 34 } 35 }