一、二叉树
1.了解二叉树
- 二叉树(Binary Tree)的特点:
每个结点至多只有两棵子树(即二叉树中不存在度大于2的结点)
二叉树的子树有左右之分,其次序不能任意颠倒,分别称为左子树和右子树,左子树和右子树的根称为其双亲的左、右孩子
- 二叉树的性质
性质1: 在二叉树的第i层上至多有2i-1个结点(i≥1)
性质2:深度为k的二叉树至多有2k-1个结点(k≥1).
(一棵深度为k且正好有2k-1个结点的二叉树称为满二叉树)
性质3: 对任何一棵二叉树,如果其终端结点数为n0,度为2的结点数为n2,则n0=n2+1
性质4: 具有n个结点的完全二叉树的深度为log2(n) +1(向下取整)
性质5: 若对一棵有n个结点的完全二叉树结点进行顺序编号,对任一结点i(1≤i≤n),有:
(1)若i=1,则i是二叉树的根,无双亲;
若i>1,则其双亲编号是i/2(向下取整)
(2)若2i>n,则i为叶子,无左孩子;否则,其左孩子编号是2i
(3)若2i+1>n,则i无右孩子;否则,其右孩子编号是2i+1
2.二叉树的存储结构
- 顺序存储结构
适用于完全二叉树
特点:
用一组地址连续的存储单元依次自上而下、自左至右存储完全二叉树的结点。此时可利用性质5很方便地求出结点之间的关系。
对一般二叉树,可将其每个结点与完全二叉树上同一位置上的结点对照,存储在一维数组的相应分量中。可能对存储空间造成极大的浪费
二叉树的顺序存储表示的C语言描述:
#define MAX_TREE_SIZE 100
typedef ElemType SqBiTree[MAX_TREE_SIZE+1];
//0号单元空闲,1号单元存储根结点
- 链式存储结构
//二叉链表
typedef struct BiTNode{
ElemType data;
struct BiTNode *lchild,*rchild;
}BiTNode,*BiTree;
n个结点的二叉树的二叉链表中有n+1个空链域
//三叉链表
typedef struct TriTNode{
ElemType data;
struct TriTNode *lchild,*rchild,*parent;
}TriTNode,*TriTree;
3.基本操作
遍历
- 先序遍历(递归)
void PreTraverse(Tree T)
{
if(T)
{
printf("%c ",T->elem);
PreTraverse(T->left);
PreTraverse(T->right);
}
}
- 中序遍历(递归)
void MidTraverse(Tree T)
{
if(!T)
return ;
MidTraverse(T->left);
printf("%c ",T->elem);
MidTraverse(T->right);
}
- 后序遍历(递归)
void BackTraverse(Tree T)
{
if(!T)
return ;
BackTraverse(T->left);
BackTraverse(T->right);
printf("%c ",T->elem);
}
- 层序遍历
void LevelShow(Tree T)
{
Queue Q;
InitQueue(&Q);
Tree p;
p=T;
EnQueue(&Q,p);
while(!IsQueueEmpty(&Q))
{
DeQueue(&Q,&p);
printf("%c ",p->elem);
if(p->left)
EnQueue(&Q,p->left);
if(p->right)
EnQueue(&Q,p->right);
}
}
- 遍历的应用
利用遍历判断两棵二叉树是否相等。
Status Equal(BiTree T1,BiTree T2) {
if(T1==NULL && T2==NULL) return TRUE;
else if(T1==NULL||T2==NULL) return FALSE;
else {
if(T1->data==T2->data)
if(Equal(T1->lchild,T2->lchild))
if(Equal(T1->rchild,T2->rchild)) return TRUE;
return FALSE;
}
}//Equal
//利用遍历求二叉树的深度
int depth(BiTree T) {
if(!T) return 0;
depthl=depth(T->lchild);
depthr=depth(T->rchild);
return (depthl>depthr?depthl:depthr)+1;
}//depth
//求叶子节点数目
int LeaveNum(Tree T)
{
if(T)
{
if(T->left==NULL&&T->right==NULL)
{
return 1;
}
return LeaveNum(T->left)+LeaveNum(T->right);
}
return 0;
}
//二叉树左右子树互换
void Exchange(Tree T)
{
Tree temp;
if(T)
{
temp=T->left;
T->left=T->right;
T->right=temp;
Exchange(T->left);
Exchange(T->right);
}
}
删除二叉树中所有以值为x的结点为根的子树并释放相应空间。
void delsubtree(BiTree T){
if(T){
delsubtree(T->lchild);
delsubtree(T->rchild);
free(T);
}//if
}//delsubtree
void DelTree(BiTree &T, TElemType x){
if(T) {
if (T->data==x)
{
delsubtree(T);
T=NULL;
}
else{
DelTree(T->lchild,x);
DelTree(T->rchild,x);
}
}//if
}//DelTree
- 用二叉树表示表达式的方法
例:a+b*(c-d)-e/f
描述表达式的二叉树遍历序列
前缀表达式(波兰式):
-+ab-cd/ef
中缀表达式:
a+bc-d-e/f
后缀表达式(逆波兰式):
abcd-*+ef/-
二叉树的线索化
我们无法直接得到结点在任意遍历序列中的前驱和后继信息,这种信息只能在遍历的动态过程中才能得到
如何保存这种在遍历过程中得到的信息?
(1)在每个结点上增加两个指针域fwd和bkwd,分别指向结点在按照某种顺序遍历时得到的前驱和后继。 缺点:存储密度低
(2)在有n个结点的二叉链表中必定存在n+1个空链域,可用它们存放结点的前驱和后继。 若结点有左子树,lchild指向左孩子,否则指向其前驱; 若结点有右子树,rchild指向右孩子,否则指向其后继
线索:指向结点前驱或后继的指针
线索二叉树:加上线索的二叉树。
**线索化:**对二叉树以某种次序遍历使其变为线索二叉树的过程。
引入线索二叉树的目的:充分利用二叉链表的空指针域,保存动态遍历过程中得到的结点前驱和后继的信息
- 树和森林和二叉树之间的转换
二、赫夫曼树
1.WPL的计算
路径: 从树中一个结点到另一个结点之间的分支。
路径长度:路径上的分支数目。
树的路径长度:从树根到每个结点的路径长度之和。
结点的带权路径长度:从该结点到树根的路径长度与结点上的权值的乘积
树的带权路径长度WPL:树中所有叶子结点的带权路径长度之和。
设二叉树中共有n个叶子。
wi:树中第i个叶子结点的权值
li :树中第i个叶子结点到根结点的路径长度
2.huffman树的构造
赫夫曼树:已知n个权值{w1,w2,…wn},构造一棵有n个叶子结点的二叉树,第i个叶子结点的权值是wi,则其中带权路径长度最小的二叉树称为赫夫曼树(Huffman tree)或最优二叉树。
构造过程
步骤1:根据给定的n个权值{w1, w2, …, wn},构造n棵二叉树的集合F = {T1, T2, …, Tn},Ti(1≤i≤n)只有一个带权值wi的根结点,其左、右子树均为空。
步骤2:在F中选取两棵根结点权值最小的二叉树,分别作为左、右子树构造一棵新二叉树。置新二叉树的根结点的权值为其左、右子树上根结点的权值之和。
步骤3: 在F中删去这两棵二叉树,把新的二叉树加入F 。
步骤4: 重复步骤2和步骤3
直到F中仅剩下一棵树为止。
这棵树就是Huffman树。
3.huffman树的编码译码
huffman存储结构
一棵有n个叶子结点的Huffman树共有2n-1个结点
可存储在长度为2n-1的一维数组中。
typedef struct
{
unsigned int weight;
unsigned int parent, lchild, rchild;
}HTNode,*HuffmanTree;
构造huffman树
Status CreateHuffmanTree(HuffmanTree &HT, int *w, int n)
{ if(n<=1) return ERROR;
m=2*n-1;
HT=(HuffmanTree)malloc((m+1)*sizeof(HTNode));//0号单元未用
for(p=HT+1,i=1;i<=n; ++i,++p,++w) *p={*w,0,0,0};
for(;i<=m; ++i, ++p) *p= {0,0,0,0};
for(i=n+1;i<=m; ++i)
{ Select(HT,i-1,s1,s2);//选取两个权值最小的结点
HT[s1].parent=i; HT[s2].parent=i;
HT[i].lchild=s1; HT[i].rchild=s2;
HT[i].weight=HT[s1].weight+HT[s2].weight;
}
return OK;
}
编码
Status HuffmanCoding(HuffmanCode &HC,HuffmanTree HT,int n)
{
HC=(HuffmanCode)malloc((n+1)*sizeof(char*));
cd=(char *)malloc(n*sizeof(char));
cd[n-1]='\0';
for(i=1;i<=n;++i)
{ start=n-1;
for(c=i,f=HT[i].parent;f!=0;c=f,f=HT[f].parent)
if(HT[f].lchild==c)
cd[--start]='0';
else
cd[--start]='1';
HC[i]=(char*)malloc((n-start)*sizeof(char));
strcpy(HC[i],&cd[start]);
}//for
free(cd);
} //HuffmanCoding
译码
void Decoding(HuffmanTree HT,HuffmanCode HC,int n)
{
char ch;
int i=2*n-1;//从根节点开始往下搜索
if(ch=='0')
i=HT[i].lchild;
else if(ch=='1')
i=HT[i].rchild;
if(HT[i].lchild==0&&HT[i].rchild==0)//找到了编码对应的第i个节点,打印出第i个节点所对应的字母即可
{
printf("%c",letter);
i=n*2-1;
}