二叉树
- 每个结点最多有两孩子(二叉树中不存在度大于2的结点)。
- 子树有左右之分,其次序不能颠倒。
- 二叉树可以是空集合,根可以有空的左子树或空的右子树
- 二叉树不是树的特殊情况,他们是两个概念。
- 树当结点只有一个孩子时,就无须区分他的次序,这是二叉树和树的最主要的差别。
二叉树的性质
1、在二叉树的第i层至多有2^(i-1)个结点(i>=1),至少有1个结点。
2、深度为k的二叉树至多有2^k-1个结点(k>=1),至少有k个结点。
3、对任何一棵二叉树T,如果其叶子数为n0,度为2的结点数为n2,则n0=n2+1。
4、具有n个结点的完全二叉树的深度为【log2(n)]+1,[】表示不大于x的最大整数
5、如果对一棵有n个结点的完全二叉树(深度为【log2(n)】+1),结点按层序编号(从第1层到第【log2(n)+1】层,每层从左到右),则对任一结点i(1<=i<=n),有:
- 如果i=1,则结点i是二叉树的根,无双亲;如果i>1,则其双亲是结点【i/2】.
- 如果2i>n,则结点i为叶子结点,无左孩子,否则,他的左孩子结点是2i.
- 如果2i+1>n,则结点i无右孩子,否则,他的右孩子结点是2i+1.
满二叉树
- 深度为k,结点有2^k-1个
- 每层都满
- 叶子结点全部在最底层
- 编号规则:从上往下,自左而右
完全二叉树
在满二叉树中,从最后一个结点开始,连续去掉任意个结点,即是一棵完全二叉树。
- 叶子只可能分布在层次最大的两层
- 对任一结点,如果其右子树的最大层次为i,则其左子树的最大层次必为i或i+1.
- 满二叉树一定是完全二叉树,反之不一定。
二叉树的存储结构
顺序存储结构:
按满二叉树的结点层次编号,依次存放二叉树中的数据元素
//二叉树顺序存储表示
#define MAXTSIZE 100
typedef TElemType SqBiTree[MAXTSIZE];
SqBiTree bt;
缺点:深度为k的且只有k个结点的单支树需要长度为2^k-1的一维数组。
特点:结点间关系蕴含在其存储位置中,浪费空间,适于存满二叉树和完全二叉树
二叉树的链式存储结构:
//二叉链表存储结构:
typedef struct BiNode{
TElemType data;
struct BiNode *lchild, *rchild; //左右孩子指针
}BiNode, *BiTree;
在n个结点的二叉链表中,有n+1个空指针域
三叉链表
//三叉链表
typedef struct TriNode{
Telemtype data;
struct TriTNode *lchild, *parent, *rchild;
}TriTNode, *TriTree;
遍历二叉树
- DLR:先(根)序遍历
- LDR:中(根)序遍历
- LRD:后(根)序遍历
用二叉树表示算术表达式
- 先序:-+a*b-cd/ef; 表达式的前缀表示(波兰式)
- 中序:a+b*c-d-e/f; 表达式的中缀表示
- 后序:abcd-*x+ef/-; 表达式的后缀表示(逆波兰式)
由二叉树的先序序列和中序序列,或由二叉树的后序序列和中序序列可以确定唯一一棵二叉树
递归算法:
二叉树的先序递归遍历算法
//二叉树先序遍历算法
Status PreOrderTraverse(BiTree T){
if(T == NULL) return OK; //空二叉树
else{
visit(T); //访问根结点 例如,输出根结点printf("%d\t",T->data);
PreOrderTraverse(T->lchild); //递归遍历左子树
PreOrderTraverse(T->rchild); //递归遍历右子树
}
}
二叉树的中序递归遍历算法
//二叉树中序遍历算法
Status InOrderTraverse(BiTree T){
if(T == NULL) return OK; //空二叉树
else{
InOrderTraverse(T->lchild); //递归遍历左子树
visit(T); //访问根结点 例如,输出根结点printf("%d\t",T->data);
InOrderTraverse(T->rchild); //递归遍历右子树
}
}
二叉树的后序递归遍历算法
//二叉树的后序遍历算法
Status PostOrderTraverse(BiTree T){
if(T == NULL) return OK; //空二叉树
else{
PostOrderTraverse(T->lchild); //递归遍历左子树
PostOrderTraverse(T->rchild); //递归遍历右子树
visit(T); //访问根结点 例如,输出根结点printf("%d\t",T->data);
}
}
中序遍历的非递归算法(栈)
基本思想:
- 建立一个栈
- 根结点进栈,遍历左子树
- 根结点出栈,输出根结点,遍历右子树
//中序遍历的非递归算法
Status InOrderTraverse(BiTree T){
BiTree p; InitStack(S); p = T;
while(p || !StackEmpty(S)){
if(p){Push(S, p); p = p->lchild;}
else{
Pop(S, q); printf("%c", q->data);
p = q->rchild;
}
}//while
return OK;
}
层次遍历算法(队列)
算法思路:
- 将根结点进队
- 队不空是循环:从队列中出列一个结点*p,访问他;
- ①若他有左孩子结点,将左孩子进点进队;
- ②若他有右孩子结点,将右孩子结点进队。
//层次遍历(队列)
typedef struct{
BTNode data[MAXSIZE]; //存放队中元素
int front, rear; //队头和队尾指针
}SqQueue; //顺序循环队列类型
//层次遍历算法
void LevelOrder(BTNode *b){
BTNode *p; SqQueue *qu;
InitQueue(qu); //初始化队列
EnQueue(qu, b); //根结点指针进入队列
while(!QueueEmpty(qu)){ //队不为空,则循环
DeQueue(qu, p); //出队结点p
printf("%c", p->data); //访问结点p
if(p->lchild != NULL)
EnQueue(qu, p->lchild); //有左孩子是将其入队
if(p->rchild != NULL)
EnQueue(qu, p->rchild); //有右孩子是将其入队
}
}
建立二叉树算法
按先序遍历序列建立二叉树的二叉链表(递归)
先序序列是ABCDEFG
//先序序列构造二叉树(递归)
Status CreateBiTree(BiTree &T){
scanf(&ch); //cin>>ch;
if(ch == "#") T = NULL;
else{
if(!(T = (BiTNode*)malloc(sizeof(BiTNode))))
exit(OVERFLOW); //T = new BiTNode;
T->data = ch; //生成根结点
CreateBiTree(T->lchild); //生成左子树
CreateBiTree(T->rchild); //构造右子树
}
return OK;
}
复制二叉树
- 如果是空树,递归结束
- 否则,申请新结点空间,复制根结点
-
- 递归复制左子树
-
- 递归复制右子树
//复制二叉树
int Copy(BiTree T, BiTree &NewT){
if(T == NULL){ //如果是空树返回0
NewT = NULL; return 0;
}
else{
NewT = new BiTNode;
NewT->data = T->data;
Copy(T->lChild, NewT->lchild);
Copy(T->rChild, NewT->rchild);
}
}
计算二叉树的深度
- 如果是空树,则深度为0;
- 否则,递归计算左子树的深度记为m,递归计算右子树的深度记为n,二叉树的深度则为m与n的较大者加1
//计算二叉树的深度
int Depth(BiTree T){
if(T == NULL) return 0; //如果是空树返回0
else{
m = Depth(T->lChild);
n = Depth(T->rChild);
if(m > n) return(m+1);
else return(n+1);
}
}
计算二叉树结点总数
- 如果是空时,则结点个数为0
- 否则,结点个数为左子树的结点个数+右子树的结点个数再+1
//计算二叉树结点总数
int NodeCount(BiTree T){
if(T == NULL)
return 0;
else
return NodeCount(T->lchild)+NodeCount(T->rchild)+1;
}
线索二叉树
如果某个结点的左孩子为空,则将空的左孩子指针域改为指向其前驱,同理,为空的右孩子指针域改为指向其后继。
二叉树按某种遍历次序使其变为线索二叉树的过程叫线索化
为区分左右孩子指针到底是指向孩子的指针还是指向前驱或者后继的指针,对二叉链表中每个结点增设两个标志域ltag和rtag
- ltag = 0 指向左孩子
- ltag = 1 指向前驱
- rtag = 0 指向右孩子
- rtag = 1 指向后继
树与二叉树的转换
树变二叉树:兄弟相连留长子
二叉树变树:左孩右右连双亲,去掉原来右孩线
森林与二叉树的转换
森林变二叉树:树变二叉根相连
二叉树变森林:去掉全部右孩线,孤立二叉再还原
哈夫曼树
- 树的路径长度:从树根到每一个结点的路径长度之和。记为TL。
完全二叉树是路径长度最短的二叉树 - 权(weight)赋予结点的数值
- 带权路径长度:从根结点到该结点之间的路径长度与该结点的权的乘积,WPL
- 最有二叉树,即哈夫曼树:WPL最短的二叉树
- 满二叉树不一定是哈夫曼树,哈夫曼树中权越大的叶子离根很近
- 具有相同带权结点的哈夫曼树不唯一
构造哈夫曼的方法(贪心算法)
- 构造森林全是根
- 选用两小造新树
- 删除两小添新人
- 重复2,3剩单根
- 哈夫曼树的结点度数为0或2,没有1
- 包含n个叶子结点的哈夫曼树共有2n-1个结点
- 包含n棵树的森林要经过n-1次合并才能形成哈夫曼树,共产生n-1个新结点。
算法5.10 哈夫曼算法
//算法5.10 哈夫曼算法
void CreatHuffmanTree(HuffmanTree HT, int n){
if(n <= 1) return;
m = 2*n-1; //数组共2n-1个元素
HT = new HTNode[m + 1]; //0号单元未用,HT[m]表示根结点
for(i = 1l i < = m; ++i){
//将2n-1个元素的lch,rch,parent为0
HT[i].lch = 0;
HT[i].rch = 0;
HT[i].parent = 0;
}
for(i = 1; i<=n; ++i)
cin>>HT[i].weight; //输入前n个元素的weight值
//初始化结束,下面开始建立哈夫曼树
for(i = n+1; i <= m; i++){ //合并产生n-1个结点
Select(HT, i-1, s1, s2) //在HTp[k]中选择两个其双亲域为0
//且权值最小的结点,返回他们在HT中的序号s1和s2
HT[s1].parent = i;
HT[s2].parent = i; //从F中删除s1,s2
HT[i].lch = s1;
HT[i].rch = s2; //s1,s2分别作为i的左右孩子
HT[i].weight = HT[s1].weight + HT[s2].weight; //i的权值为左右孩子权值之和
}
}