二叉树的性质
二叉树的性质1
根据满二叉树的特点可知,普通二叉树每层的节点数是不会比满二叉树还多的,由于满二叉树的前后层数上的结点数目是按照比例2递增的,a1 = 1,a2 = 2,a3 = 4,a4 = 8.。。。所以ai = 2^( i - 1)
在二叉树的第 i 层上至多有2^( i - 1个结点。
二叉树的性质2
讨论二叉树总共多少结点,以满二叉树位例,假设满二叉树的深度为4,则总结点数是比例为2的等比数列求和
a
1
∗
(
1
−
q
i
−
1
)
/
(
1
−
q
)
a1*(1 - q^{i - 1})/(1-q)
a1∗(1−qi−1)/(1−q)
带入a1 = 1,q = 2.得
2
n
−
1
2^{n} - 1
2n−1
所以有深度为i的二叉树至多有
2
n
−
1
2^{n} - 1
2n−1个结点。
二叉树的性质3
辅助记忆:1,2,4,8。等比数列的前四项都有如下关系,1+2 = 4-1,1+2+4 = 8-1,也就是某一层的结点数是前面的结点数加起来再加1。这样就有
对任何一颗二叉树T,如果其终端节点数为n0,度为2的结点数n2,则有n0 = n2 +1
二叉树的性质4
已知满二叉树的结点数是
n
1
=
2
k
−
1
n1 = 2^{k} - 1
n1=2k−1,完全二叉树的结点n不会大于满二叉树的结点,但是不会小于
n
2
=
2
k
−
1
−
1
n2 = 2^{k - 1} - 1
n2=2k−1−1
所以 n2 <= n <= n1.。不等式全部加1, n2 + 1<= n +1 <= n1 + 1,两边再取对数,
深度k<log2(n + 1) +1
具有n个结点的完全二叉树的深度为【log2(n)】+1
二叉树的性质5
如果对一颗有n个结点的完全二叉树的结点按层序编号,对任意结点 i 有:
1 如果 i= 1,则结点 i 是二叉树的根,无双亲;如果 i> 1,则有双亲,双亲序号为【 i/2 】取整。
2 如果2i > n,则结点 i 无左孩子。否则左孩子序号为2i。
3 如果2i+1>n,则结点i无右孩子;否则其右孩子结点为2i+1.
二叉树的存储结构
顺序存储结构只用于完全二叉树,若是其它类型的树,如右斜树,会造成空间的浪费。
链式存储结构
结点分为数据域和指针域两个部分
数据域:存储数据
指针域:lchild指针指向左孩子的地址,rchild指针指向右孩子的地址
typedef struct link_Tree
{
char data;
struct link_Tree *lchild,*rchild;
}TreeNODE,*TREEPTR;
遍历二叉树
二叉树的遍历是指从根结点出发,按照某种次序一次访问二叉树中所有的结点,使得每个结点被访问一次且仅被访问一次。
二叉树遍历方法
前序遍历法
若二叉树为空,则空操作返回,否则先访问根结点,然后遍历左子树,再前序遍历右子树。
代码如下:
void lookup_tree(TREEPTR T)
{
if(T == NULL)
return;
printf("%c",T->data);
lookup_tree(T ->lchild);
lookup_tree(T ->rchild);
}
中序遍历法
若树为空,则操作返回,否则从根结点开始,中序遍历根结点的左子树,然后是访问根结点,最后中序遍历右子树。
void lookup_tree_bymin(TREEPTR T)
{
if(T == NULL)
return;
lookup_tree(T ->lchild);
printf("%c",T->data);
lookup_tree(T ->rchild);
}
后序遍历法
若树为空,则空操作返回,否则从左到右先叶子后结点的方式遍历访问左右子树,最后是访问根结点。
void lookup_tree_bylast(TREEPTR T)
{
if(T == NULL)
return;
lookup_tree(T ->lchild);
lookup_tree(T ->rchild);
printf("%c",T->data);
}
所以:已知前序和中序遍历序列能确定一棵树,已知后序和中序序列也能确定一棵树。
但是已知前序和后序是不能确定树的。
二叉树的建立
约定输入#符号代表空树,输入其它字符则开创一个结点,存储数据。
与遍历树一样,这里使用前序遍历的思想,创建一颗树。
- 传入树结点的指针
- 接受输入字符
- 判断输入字符,若是#,则指向NULL;若是其它字符,则开辟一个结构体类型的空间,并把地址赋给指针
- 再次调用函数本身,把当前结点的 lchild 指针的地址作为参数
- 再次调用函数本身,把当前结点的 rchild 指针的地址作为参数
代码如下:
void creat_tree(TREEPTR T)
{
char ch;
scanf("%c",&ch);
if(ch == '#')
T = NULL;
else
{
T = (TREEPTR)malloc(sizeof(TreeNODE));
if(!T)
exit(OVERFLOW);
T ->data = ch;
creat_tree(&T ->lchild);
creat_tree(&T ->rchild);
}
}
线索二叉树
充分利用二叉树结点中指向NULL的空指针,若使用中序遍历,使lchild指针指向前驱,rchild指针指向后继结点,这些指针就称为线索,加上线索的二叉树链表称为线索链表,相应的二叉树就是线索二叉树了。
为了区分左右孩子指针是指向孩子还是前驱或者后继,需要增加两个标记,ltag和rlag,它们是存放0和1的布尔型变量
所以每个结点有 lchild ltag data rtag rchild 五个元素
其中,
- ltag为0时指向该结点的左孩子,为1时指向该结点的前驱
- rtag为0时指向该结点的右孩子,为1时指向该结点的后继
经过上述操作后,二叉树实际上被转换成一个双向链表了,链表的结点顺序与根据中序遍历二叉树的结点顺序相同。
线索二叉树结构实现
正如前面所述,结点内包含 lchild ltag data rtag rchild 五个元素。
typedef enum {Link,Thread} PointerTag;
typedef struct BiThrNode
{
char data;
struct BiThrNode *lchid,*rchild;
PointerTag ltag;
PointerTag rtag;
}BiThrNode,*BiThrTree;
中序遍历线索化的递归函数代码如下:
1 判断树的根结点是否为空
2 根据中序遍历思想,调用递归函数,先遍历左子树,不断入栈,直到最左边的叶子,没有左孩子,开始出栈。函数层层往上出栈。
3 到了双亲结点层面的函数,执行前驱,后继线索的赋值,再递归调用函数,遍历右孩子。
4 倒数第二层函数结束后,就出栈第三层函数。
函数代码如下:
BiThrTree pre = NULL;
BiThrTree InThreading(BiThrTree current,BiThrTree head)
{
if(current)
{
InThreading(current->lchid);
if(!current->lchid)
{
if(pre == NULL)
{
current->ltag =Thread;
current->lchid = head;
}
current->ltag = Thread;
current->lchid = pre;
}
if(!pre->rchild)
{
pre->rtag = Thread;
pre->rchild = current;
}
pre = current;
InThreading(current->rchid);
}
return current;
}
head->lchid = current;
BiThrTree last = InThreading(current,head);
last->rchild = head;
last->rtag = Thread;
head->rchild = last;
head->rtag = Thread;