为了巩固知识,做的笔记,如有错误请指正
一.二叉树的概念
1.定义:二叉树或为空树,或由根及其两个互不相交的左右子树构成,并且其左右子树也为二叉树。
2.特点:
(1)二叉树的每个节点最多有两个子树,即二叉树每个节点度数最大为2。
(2)左右子树不能颠倒---有序树。
(3)二叉树为递归结构。
3.特殊的二叉树
- 满二叉树:深度为k的二叉树且有2^k^-1个节点的二叉树。
- 完全二叉树:深度为k,有n个节点的二叉树,其1-n个节点的排序与满二叉树1-n节点的位置一一对应排排序相同。
满二叉树一定是完全二叉树,完全二叉树不一定是满二叉树。
二.二叉树的性质
1.二叉树在第i层最多有2^i-1^个节点。
2.深度为k的二叉树最多有2^k^-1个节点。
证明:1+2+4+……+2^k-1=1+1+2+4+……+2^k-1-1=2^k^-1
3.具有n个节点的完全二叉树(保证除最后一层外,二叉树的所有层节点数都是满的)有[(log2^n)]+1层。
(log2^n)=总层数-最后一层
4.零度节点=二度节点+1。
n0=n2+1
证明:n=n0+n1+n2,b=n-1(分支数=节点数减1),b=n1+2*n2
5.若对有n个节点的完全二叉树,从上到下从左至右进行1-n编号,对于任意节点i.
(1)[i/2]为其双亲节点,否则为其根节点。
(2)2i是其左孩子节点,若2i=n则恰好有左孩子,2i>n则没有左孩子。
(3)2i+1是其右孩子节点,若2i+1=n则恰好有右孩子,2i+1>n则没有右孩子。
6.n个节点的二叉树中,有n+1个空链域
证明:n个节点有2n个指针域。除根节点外其他节点均需要指针域指出,共需n-1个指针域。所以剩余的指针域为空链域即
2n-(n-1)=n+1
三.二叉树的存储结构
1.顺序二叉树(只适用于完全二叉树)
(1)完全二叉树(满二叉树)
- 利用性质5,采用一维数组,按层序顺序依次存储二叉树的每一个节点。(如下图所示)
(2)畸形(一般)二叉树
- 存储方法:利用虚设节点使其变为完全二叉树,然后利用性质5进行线性存储。
- 缺点:浪费空间较大。
2.链式二叉树
(1)二叉链表的存储方式
每个节点有三个域:lchild|data|rchild,左域指向其左子树,右域指向其右子树。
(2)存储结构表示
typedef struct Node{
DataType data;//DataType可以为任意类型的数据。
struct Node *lch,*rch;
}BinTNode,*BiTree;//*BiTree创建树,BinTNode创建节点
(3)三叉链表(含有其双亲节点引用的二叉树)
typedef struct Node{
DataType data;//DataType可以为任意类型的数据。
struct Node *lch,*rch,*parent;
}BinTNode,*BiTree;
四.二叉树的遍历
1.相关概念
- 遍历:按某种路径对二叉树的每个节点进行访问,且每个节点只能访问一次---各种操作的基础。
- 访问:对节点进行操作的总称,包括输入输出,查找修改删除。
2.遍历方法
(1)先序遍历
a:遍历方法
DLR:从二叉树的根节点开始,先访问(输出等操作)根节点,再遍历左子树,每遍历完一个二叉树左子树(每一个左子树都从它的第一层遍历到最底层)就遍历相应的右子树。
b.遍历算法
c.遍历序列
ABDHECFIG
Status PreOrderTraverse(T){
if(T=NULL) return ok;
else{
cout<<T->data;
PreOrderTraverse(T->lch);
PreOrderTraverse(T->rch);
}
}
(2)中序遍历
a:遍历方法
LDR:从二叉树的根节点开始,先遍历左子树,每遍历完一个二叉树的左子树就访问相应的根节点,然后遍历相应的右子树。
b.遍历算法
c.遍历序列
HDBEAIFCG
Status InOrderTraverse(T){
if(T=NULL) return ok;
else{
InOrderTraverse(T->lch);
cout<<T->data;
InOrderTraverse(T->rch);
}
}
(3)后序遍历
a:遍历方法
LRD:从二叉树的根节点开始,先遍历左子树,每遍历完一个二叉树左的子树就遍历相应的右子树,然后访问相应的根节点。
b.遍历算法
c.遍历序列
HDEBIFGCA
Status PostOrderTraverse(T){
if(T=NULL) return ok;
else{
PostOrderTraverse(T->lch);
PostOrderTraverse(T->rch);
cout<<T->data;
}
}
3.根据遍历序列得到二叉树
思路:先找根节点,再找及其左右子树,最后补全独生叶节点。
独生叶节点:度为0且无兄弟的节点(无兄弟的叶节点)。
(1)根据前序序列和中序序列确定(1和2循环配合进行确定,3在最后只进行一次)
- 先根据先序序列确定主序列(或子序列)根节点——序列的第一个字符为根节点。。
- 再根据根节点,从中序序列确定左右两个子树序列——根节点的左侧为其左子树序列,右侧为其右子树序列。
- 最后根据中序序列最先输出的是最左下左子树的值补全独生叶节点。
(2)根据后序序列和中序序列确定(1和2循环配合进行确定,3在最后只进行一次)
- 先根据先序序列确定其根节点。
- 再根据根节点,从中序序列确定左右两个子树序列。
- 最后根据中序序列最先输出的是第一个最左下左子树的值补全独生叶节点。
(3)无法根据前序序列和后序序列确定
原因:
- 只能找到根节点,无法找到节点的左右子树。
- 也无法找到独生叶节点。
五.二叉树遍历的应用
遍历是二叉树进行各种操作的基础
递归程序中return 后面的表达式其实是承接的具体的值
在递归算法中注意不能越界,如当指针的值不存在时,运行时会出现错误
1.创建二叉树的存储结构---二叉链表
- 创建时先将上一层根节点的指针域指向新的根节点。
- 然后将输入的值赋值给新节点的指针域。
- 接着进入左子树递归循环,直到T->lchild==NULL,返回执行上一层循环的右子树递归循环。
- 进入右子树循环后,继续按照前三条规则进行循环往复递归创建,
void CreateBiTree(BiTree &T){
cin>>ch;
if(ch=='#') T=NULL;//递归出口,当输入#时,结束输入数据。
else{
T=new BiTNode;//生成由上一个根节点的指针域指向的根节点
T->data=ch;//根节点的
CreatBiTree(T->lchild);//递归创建左子树
CreatBiTree(T->rchild);//递归创建右子树
}
}
2.复制二叉树
void CopyBiTree(BiTree &T,BiTree &NewT){
if(T==NULL){//递归出口,当被复制的树的指针域为空时,返回
NewT=NULL;
return;
}
else{
NewT=new BiTNode;
NewT->data=T->data;
CopyBiTree(T->lchild,NewT->lchild);
CopyBiTree(T->rchlid,NewT->rchild);
}
}
3.计算二叉树的深度
int Depth(BiTree &T){
if(T==NULL) return 0;
else{
m=Depth(T->lchild);
n=Depth(T->rchlid);
if(m>n) return (m+1);
else return (n+1);
}
}
4.统计二叉树节点的个数
int NodeCount(BiTree T){
if(T=NULL) return 0;
else return NodeCount(T->lchild)+NodeCount(T->rchlid)+1;
}
5.统计二叉树叶节点数
int LeafCount(BiTree T){
if(T==NULL) return 0;
if(T->lch==NULL&&T->rch==NULL) return 1;
else return LeafCount(T->lch)+LeafCount(T->rch);
}