目录
一、树和二叉树的定义
1、树的基本术语
① 结点的度:结点拥有的子树数量
② 树的度:树内结点度的最大值
③ 叶子:度为0的结点
④ 结点的层次和树的深度:
⑤ 森林:m棵互不相交的树的集合
⑥ 边数+1 = 结点数
2、二叉树的定义
① 每个结点至多有2个棵子树,即二叉树中不存在度大于2的结点
没有子树或有一颗子树都是可以的。
② 二叉树的子树有左右之分,且其次序不能颠倒
③ 即使树的某结点只有一颗子树,也要区分它是左子树和右子树
④ 二叉树不是树的特殊情况,它们是两个概念
⑤ 二叉树的结点个数可以为0
4、二叉树的性质
① 二叉树的第 i 层最多有
个结点,至少有1个结点;
② 深度为 k 的二叉树至多有
个结点,至少有k个结点;
③ 叶子数为
,度为2的结点数为
,则
;
满二叉树
一棵深度为
且有
个结点的二叉树
① 所有分支结点都有左子树和右子树,叶子结点都在最下一层
② 只有度为0和度为2的结点
③ 含n个结点的满二叉树的深度为叶子结点个数为⌊
⌋ (向下取整)
度为2的结点为⌊
⌋ (向下取整)
完全二叉树
① 叶子结点只可能出现在层次最大的两层出现
② 最下一层中的叶子结点都依次排列在该层最左边的位置上
③ 如果有度为1的结点,只可能有一个,且该结点最多只有左孩子而无右孩子④ 满二叉树是特殊的完全二叉树
⑤ 度为1的结点最多有1个
⑥ 含n个结点的完全二叉树的深度为⌊
⌋ +1 (向下取整)
⑦ 含n个结点的完全二叉树,对任意结点 i:
(1)如果i>1,则其双亲是 ⌊ i / 2 ⌋ (向下取整)
(2)如果2i>n,则结点 i 无孩子(结点i为叶子结点);否则其左孩子是结点 2i
(3)如果2i+1>n,则结点 i 无右孩子;否则其右孩子是结点 2i+1
例1、一棵完全二叉树有5000个结点,则其叶结点个数是(2500)
n0+n1+n2=5000,因为n0=n2+1,则2*n2+1+n1=5000,因此n1一定是奇数
因为完全二叉树,其中度为1的结点个数最多为1个,则n1=1,则n2=2499,则n0=2500
例2、二叉链表:n个结点共2n个指针,其中有效指针为n-1,则空指针数为2n-(n-1)=n+1
结点-1 = 有效指针数
三叉链表:n个结点共3n个指针,其中有效指针为n-1,则空指针数为3n-2(n-1)=n+2
5、树和二叉树的区别
① 树的结点个数至少为1,二叉树的结点个数可以为0
② 树的最大度数没有限制,二叉树最大度数不能超过2
③ 树的结点无左右之分,二叉树的结点有左右之分
将树转化为二叉树的目的:
树可采用二叉树的存储结构并利用二叉树的已有算法解决树的有关问题
二、遍历二叉树和线索二叉树
1、创建二叉树
//按先序遍历创建二叉树
Status CreateBiTree( BiTree &T )
{
char c;
scanf("%c",&c);
if(c == ' ') T = NULL;
else{
if(!(T = (BiTNode*)malloc(sizeof(BiTNode)))) return;
T->data = c;
CreateBiTree(T->lchild);
CreateBiTree(T->rchild);
}
}
2、遍历二叉树
1、遍历顺序 (D为根 L为左子树 R为右子树)
① DLR 先序遍历
② LDR 中序遍历
③ LRD 后序遍历
1、先序遍历:根节点 → 左节点 → 右节点;
2、中序遍历:左节点 → 根节点 → 右节点;
3、后序遍历:左节点 → 右节点 → 根节点;
1、前序遍历 DLR
//递归算法
void PreOrderTraverse(BiTree T, int level)
{
if (T)
{
printf("data = %c level = %d\n ", T->data, level);
PreOrderTraverse(T->lchild, level + 1);
PreOrderTraverse(T->rchild, level + 1);
}
}
2、中序遍历 LDR
//递归算法
void InOrderTraverse(BiTree T, int level)
{
if (T)
{
PreOrderTraverse(T->lchild, level + 1);
printf("data = %c level = %d\n ", T->data, level);
PreOrderTraverse(T->rchild, level + 1);
}
}
//非递归算法
void InOrderTraverse(BiTree T)
{
InitStack(S);
p = T;
while(p||!StackEmpty(S))
{
if(p)
{
Push(S,p);
p = p->lchild;
}else{
Pop(S,p);
printf("%c ",p->data);
p = p->rchild;
}
}
}
3、后序遍历 LRD
//递归算法
void PostOrderTraverse(BiTree T, int level)
{
if (T)
{
PreOrderTraverse(T->lchild, level + 1);
PreOrderTraverse(T->rchild, level + 1);
printf("data = %c level = %d\n ", T->data, level);
}
}
4、层次遍历
c++ queue 队列
queue <元素类型> q1;
q.push() //在队尾插入一个元素 q.pop() //删除队列第一个元素 q.size() //返回队列中元素个数 q.empty() //如果队列空则返回true q.front() //返回队列中的第一个元素 q.back() //返回队列中最后一个元素
void levelOrderTraverse (Bitree T)
{
if(T == NULL) return; //空树
queue <BiTNode*> q; //创建队列
q.push(T); //将根节点入队
while(!q.empty())
{
BiTNode *node = q.front(); //取头结点
printf("%d ",node->data);
if(node->left) q.push(node->left);
if(node->right) q.push(node->right);
p.pop(); //头结点出队
}
}
3、二叉树的重构
4、线索化二叉树
1、概念
2、代码实例
#include <stdio.h>
#include <stdlib.h>
typedef char ElemType;
typedef enum PointerTag{Link,Thread}; //枚举类型 Link=0 Thread=1
typedef struct BiThrNode
{
ElemType data;
struct BiThrNode *lchild,*rchild;
PointerTag ltag,rtag;
}BiThrNode, *BiThrTree;
BiThrTree pre; //全局变量
//前序遍历 创建二叉树
void CreateBiThrTree( BiThrTree *T )
{
char c;
scanf("%c",&c);
if(c == ' ') *T = NULL;
else
{
*T = (BiThrNode*)malloc(sizeof(BiThrNode));
(*T)->data = c;
(*T)->ltag = Link; //初始化左右标识
(*T)->rtag = Link;
CreateBiThrTree(&(*T)->lchild);
CreateBiThrTree(&(*T)->rchild);
}
}
//
void InOrderThreading(BiThrTree *p,BiThrTree T)
{
*p = (BiThrTree)malloc(sizeof(BiThrNode));
(*p)->ltag = Link;
(*p)->rtag = Thread;
(*p)->rchild = *p;
if(!T)
{
(*p)->lchild = *p;
}
else {
(*p)->lchild = T;
pre = *p;
InThreading(T);
pre->rchild = *p;
pre->rtag = Thread;
(*p)->rchild = pre;
}
}
//中序遍历线索化
void InThreading( BiThrTree T )
{
if( T )
{
InThreading( T->lchild ); //递归左孩子线索化
if(!T->lchild)
{
T->ltag = Thread; //没有左孩子 ltag改为1
T->lchild = pre;
}
if(!pre->rchild)
{
pre->rtag = Thread;
pre->rchild = T;
}
pre = T;
InThreading( T->rchild ); //递归右孩子线索化
}
}
int main()
{
BiThrTree P,T = NULL;
CreateBiThrTree(&T);
InOrderThreading( &P, T );
return 0;
}
五、二叉树的基本操作
1、查找元素e的节点的位置
BiTNode *SearchNode(BiTree &T,ElemType e)
{
if(!T) return;
if(T->data == e) return T;
else
{
BiTNode *p = SearchNode(T->lchild,e);
if(!p) return SearchNode(T->rchild,e);
return p;
}
}
2、计算二叉树的深度
int BiTreeHigh(BiTree &T)
{
int h1,h2;
if(T)
{
h1 = BiTreeHigh(T->lchild);
h2 = BiTreeHigh(T->rchild);
return h1>h2? ++h1:++h2;
}
return 0;
}
3、计算叶子结点个数
int CountLeaf(BiTree &T)
{
if(T)
{
if(!T->lchild && !T->rchild)
return 1;
else
return CountLeaf(T->lchild) + CountLeaf(T->rchild);
}
return 0;
}
4、计算结点个数
int CountNodes(BiTree &T)
{
if(T)
{
if(!T->lchild && !T->rchild)
return 1;
else
return 1 + CountNodes(T->lchild) + CountNodes(T->rchild);
}
return 0;
}
六、二叉树的转换
1、树与二叉树转换
第一步:把树中所有兄弟结点之间加一条连线
第二步:对于每个结点,除保留其长子的连线外,去掉该结点与其他孩子的连线
第三步:调整位置
2、森林与二叉树转换
第一步:将每棵树转换为二叉树
第二步:将每棵二叉树的根节点视为兄弟,从左至右依次相连
第三步:调整位置
3、二叉树到树到森林的转换
第一步:若结点x是其双亲y的左孩子,则把结点x的右孩子、右孩子的右孩子、……都与双亲y连接起来
第二步:去除所有双亲到右孩子之间的连线
第三步:调整位置
七、哈夫曼树及其应用
1、哈夫曼树基本概念
1、结点的路径长度:从根结点到该结点路径上的连接数
2、树的路径长度:树中每个叶子结点的路径长度之和
3、结点的带权路径长度:结点路径长度 × 结点权值
4、树的带权路径长度(WPL):树中所有叶子结点带权路径长度之和
5、哈夫曼树:由n个带权叶子结点构成的所有二叉树中带权路径长度最短的二叉树
① 哈夫曼树不存在度为1的结点 只有度为0和2大结点
② n个叶子结点的哈夫曼树有2n-1个结点 (结点数是奇数)
证:哈夫曼树中只有度为0和2的结点,那么度为0的叶子结点为n个,设度为2的
结点个数为y个,根据二叉树的性质,n0 = n2 + 1 ,即n = y+1,y = n-1, 总结点数为2n-1。
2、哈夫曼树的构造算法
eg:w = {5,29,7,8,14,23,3,11}
3、哈夫曼编码
1、前缀码:如果在一个编码系统中,任一个编码都不是其他任何编码的前缀,则称该编码系统中的编码是前缀码。例如,一组编码01,001,010,100,110就不是前缀码,因为01是010的前缀,若去掉01或010就是前缀码。
2、哈夫曼编码:对一棵具有n个叶子的哈夫曼树,若对树中的每个左分支赋予0,右分支赋予1,则从根到每个叶子的通路上,各分支的赋值分别构成一个二进制串,该二进制串就称为哈夫曼编码。
八、二叉排序树
1、二叉排序树概念
① 若它左子树不为空,则左子树上所有结点均小于它的根节点的值
② 若它右子树不为空,则右子树上所有结点均大于它的根节点的值
2、二叉排序树——存储结构
typedef struct BiTNode
{
int data;
struct BiTNode *lchild,*rchild;
}BiTNode,*BiTree;
3、二叉排序树——查找
(1)若二叉排序树为空,则查找失败,返回空指针。
(2)若二叉排序树非空,将给定值key与根结点的关键字T->data.key进行比较:
① 若key等于T->data.key,则查找成功,返回根结点地址;
② 若key小于T->data.key,则进一步查找左子树;
③ 若key大于T->data.key,则进一步查找右子树。
//指针f指向T的双亲,初始值为NULL
//若查找成功,p指向该节点
//若查找不成功,p指向查找路径上访问的最后一个结点
Status SearchBST( BiTree T, int key, BiTree f, BiTree *p)
{
if(!T) //查找不成功
{
*p = f;
return 0;
}
else if (key == T->data) //查找成功
{
*p = T;
return 1;
}
else if (key < T->data)
return SearchBST(T->lchild,key,T,p); //在左子树中继续查找
else
return SearchBST(T->rchild,key,T,p); //在右子树中继续查找
}
4、二叉排序树——插入
若二叉排序树为空,则插入结点为根节点
当待插入的值大于根结点的值,且根节点的右子树为空时,则将待插入的结点当作右子树插入
当待插入的值小于根节点的值,且根节点的左子树为空时,则将待插入的结点当作左子树插入
Status InsertBST( BiTree *T, int key )
{
BiTree p,s;
if(!SearchBST(*T,key,NULL,&p)) //查找不到才插入
{
s = (BiTree)malloc(sizeof(BiTNode));
s->data = key;
s->lchild = s->rchild = NULL;
//p指向查找路径上访问的最后一个结点
if(!p) *T = s; //如果为空树 插入s为新根节点
else if(key < p->data)
p->lchild = s;
else
p->rchild = s;
return 1;
}
else return 0;
}
九、平衡二叉排序树(AVL)
1、概念
平衡因子:任一结点的左右子树高度差
任一结点的平衡因子只能取:-1、0 、1;如果树中任意一个结点的平衡因子的绝对值大于1,则这棵二叉树就失去平衡,不再是 AVL树
非平衡二叉排序树 平衡二叉排序树
上图9这个结点的左子树高度为2,右子树高度为0,高度差为2
2、平衡二叉树的平衡旋转
如果在一棵AVL树中插入一个新结点,就有可能造成失衡,此时必须重新调整树的结构,使之恢复平衡。我们称调整平衡过程为平衡旋转。