树的存储
满二叉树 & 完全二叉树(编号和满二叉树编号相同)
二叉树性质:
1)在二叉树的第i层上至多有2^(i-1)个结点
2)深度为k的二叉树至多有2^k-1个结点
3)任意二叉树,若叶子数为n0,度为2的结点数为n2,则n0=n2+1;
4)具有n个结点的完全二叉树的深度为
完全二叉树编号的性质:【floor-向下取整,ceil-向上取整】
1)若编号i=1,则i是根结点;若i>1,则i的双亲结点编号为floor(i/2)。
2)若2i>n,则i为叶子节点;若2i≤n,则2i为i的左孩子
3)若2i+1>n,则i无右孩子;若2i+1≤n,则2i+1是i的右孩子。
顺序存储
一、存储结构描述
typedef struct{
datatype data[maxsize];
int last;//指示最后一个结点的位置
}sequenlist;
二、二叉树的建立(顺序存储)
通过加入虚结点,将二叉树映射为完全二叉树,用完全二叉树的方式存储,即把结点放入对应编号的数组单元中。
链式存储
一、存储结构描述
//二叉链表
typedef struct node{
datatype data;
struct node *lchild,*rchild;
}bitree;
bitree *root;
//三叉链表:在二叉链表的基础上,加入parent指针域
二、二叉树的建立(链式存储)
//借助队列
bitree* Creatree(){
char ch;//结点信息变量
bitree *Q[maxsize];//队列,指针数组充当
int front,rear;//队头、队尾指示变量
bitree *root,*s;//根结点和中间变量指针
root=NULL;//二叉树置空
fornt=1;rear=0;//设置队列指针变量初值
while((ch=getchar())!='#'){//‘#’是结束字符
s=NULL;//将中间变量置空
if(ch!='@'){//@为虚结点
s=(bitree*)malloc(sizeof(bitree));//当ch不是虚结点时建立新结点
s->data=ch;
s->lchild=NULL;
s->rchild=NULL;
}
rear++;//队尾加1,指向新结点地址在队列中的位置
Q[rear]=s;//无论s是不是虚结点,都入队
//以下开始判断结点间的指向关系
if(rear==1)
root=s;
else{
if(s && Q[front])//孩子和双亲都不是虚结点时进行指向
if(rear%2==0)//左孩子
Q[front]->lchild=s;
else//右孩子
Q[front]->rchile=s;
if(rear%2==1)//右孩子也指向完毕,则出队
front++;
}
}
return root;
}
*图示:
树的遍历
深度优先
先序遍历:DLR,先根,后左右子树
中序遍历:LDR
后序遍历:LRD
一、先序遍历
void preorder(bitree *p){
if(p!=NULL){//结束条件,二叉树为空
printf("%c ",p->data);
preorder(p->lchild);
preorder(p->rchild);
}
return;
}
二、中序遍历
1、递归算法:
void inorder(bitree *p){
if(p!=NULL){
inorder(p->lchild);
printf("%c ",p->data);
inorder(p->rchild);
}
return;
}
2、非递归算法(借助栈)
bitree *stack[N];
void ninorder(bitree *p){
bitree *s;
int top;
if(p!=NULL){
top=-1;
s=p;
while((top!=-1)||(s!=NULL)){
while(s!=NULL){
if(top==N-1){
printf("overflow\n");
return;
}
else{
top++;
stack[top]=s;
s=s->lchild;
}
}//寻找最左结点
s=stack[top];
top--;
printf("%c ",s->data);//中序遍历
s=s->rchild;
}
}
return;
}
三、后序遍历
void postorder(bitree *p){
if(p!=NULL){
postorder(p->lchild);
postorder(p->rchild);
printf("%c",p->data);
}
return;
}
广度优先
适用场景:「层序遍历」、「最短路径」
参考👉讲解
//借助队列
bitree *Q[maxsize];
void Layer(bitree *p){
bitree *s;
if(p!=NULL){//树非空时
rear=1;
front=0;//这时,队头队尾的初始化和构建二叉树时不同
Q[rear]=p;//根节点入队
while(front<rear){//当队列非空时
front++;
s=Q[front];//出队
printf("%c ",s->data);//出队时遍历
if(s->lchild!=NULL){
rear++;
Q[rear]=s->lchild;
}
if(s->rchild!=NULL){
rear++;
Q[rear]=s->rchild;
}
}
}
return;
}
从遍历序列恢复二叉树
一、基本思想
先序和中序,或,中序和后序,能唯一确定二叉树
二、算法
//递归
datatype preod[maxsize],inod[maxsize];//以先序和中序为例
bitree *BPI(datatype preod[],datatype inod[],int i,int j,inj k,int l){
//i、j、k、l分别是先序、中序序列数组的起始和终点下标
int m;
bitree *p;
p=(bitree*)malloc(sizeof(bitree));//构造根节点
p->data=preod[i];
m=k;
while(inod[m]!=preod[i])//寻找根结点在中序序列中的位置
m++;
if(m==k)//构造左子树
p->lchild=NULL;
else
p->lchild=BPI(preod,inod,i+1,i+m-k,k,m-1);//注意参数
if(m==l)//构造右子树
p->rchild=NULL;
else
p->lchild=BPI(preod,inod,i+m-k+1,j,m+1,l);
return p;
}
遍历算法的应用
统计二叉树中叶子节点数
一、计算公式
二、算法
int CountLeaf(bitree *p){
if(!p)
return 0;
else if(!p->lchild && !p->rchild)
return 1;
else
return CountLeaf(p->lchild)+CountLeaf(p->rchild);
}
求二叉树深度
一、计算公式
二、算法
int Height(bitree *p){
int lc,rc;
if(p==NULL)
return 0;
lc=Height(p->lchild)+1;
rc=Height(p->rchild)+1;
return lc>rc?lc:rc;
}
树、森林、二叉树之间的转换
树转换为二叉树
任何一个树转换为二叉树后,二叉树的右子树为空
二叉树转换为树
森林转换成二叉树
树的应用
哈夫曼树
一、定义
在有n个带权叶子结点的所有二叉树中,带权路径长度WPL最小的二叉树为哈夫曼树,也称最优二叉树
树的带权路径长度WPL:树中所有叶子结点的带权路径长度之和;
树的路径长度:树根到树中每一个结点的路径长度之和;
哈夫曼树不唯一,完全二叉树不一定是哈夫曼树
严格二叉树:树中没有度为1的结点
二、哈夫曼树构造算法
1、思想
2、存储结构
#define n 6 //叶子结点个数
#define m 2*n-1 //整个哈夫曼树的结点个数
typedef char datatype;
typedef struct{
float weight;
datatype data;
int lchild,rchild,parent;
}hufmtree;
hufmtree tree[m];
3、实现
#include <float.h>
#define EPS 1e-5
void HUFMAN(hufmtree tree[]){
int i,j,p1,p2;//p1、p2用于存储最小的两个结点的下标
char ch;
float small1,small2,f;//small1、small2用于存储最小的两个权值(small1<small2)
for(i=0;i<m;i++){//初始化
tree[i].parent=-1;
tree[i].lchild=-1;
tree[i].rchild=-1;
tree[i].weight=0.0;
tree[i].data='0';
}
for(i=0;i<n;i++){//读入n个叶子的信息
scanf("%f",&f);tree[i].weight=f;
scanf("%c",&ch);tree[i].data=ch;
}
for(i=n;i<m;i++){//进行n-1次合并,产生n-1个新结点
p1=p2=-1;
small1=small2=FLT_MAX;
for(j=0;j<=i-1;j++){//寻找最小的结点
if(tree[j].parent==-1){
if(tree[j].weight-small1<EPS){//若当前结点权值比最小的还小
small2=small1;//把当前的最小赋给次小
small1=tree[j].weight;//把最最小赋给最小
p2=p1;p1=j;
}
else if(tree[j].weight-small2<EPS){//若当前结点权值小于次小
small2=tree[j].weight;p2=j;
}
}
}
tree[p1].parent=i;//更新合并
tree[p2].parent=i;
tree[i].rchild=p2;
tree[i].lchild=p1;
tree[i].weight=tree[p1].weight+tree[p2].weight;
}
}
哈夫曼编码&译码
编码
一、思想
n个叶子的最大编码长度不会超过n-1
每个叶子的编码都是不一样的,且能区分编码的起始和结束
二、存储结构
typedef char datatype;
typedef struct{
char bits[n];//编码数组,位串
int start;//编码在位串中的起始位置
datatype data;//结点值
}codetype;
codetype code[n];
三、实现
void HUFMANCODE(codetype code[],hufmtree tree[]){
int i,c,p;
codetype cd;//缓冲变量
for(i=0;i<n;i++){ //n为叶子结点数
cd.start=n;//从叶子结点出发向上回溯
c=i;p=tree[c].parent;
cd.data=tree[c].data;
while(p!=-1){
cd.start--;
if(tree[p].lchild==c)
cd.bits[cd.start]='0';
else
cd.bits[cd.start]='1';
c=p;
p=tree[c].parent;
}
code[i]=cd;
}
}
*实例:
译码
上算法
void HUFMANDECODE(codetype code[],hufmtree tree[]){
int i;
char b;
int endflag=-1;
i=m-1;//m-1为根结点下标
scanf("%d",&b);
while(b!=endflag){
if(b==0)
i=tree[i].lchild;
else
i=tree[i].rchild;
if(tree[i].lchild==-1){//判断tree[i]是否为叶子
putchar(code[i].data);
i=m-1;//回到根结点
}
scanf("%d",&b);
}
if(i!=m-1)//电文读完还没到叶子节点,说明输入有错
printf("\nerror\n");
}
二叉排序树及其构建&结点删除
构建
一、定义
二、存储结构
typedef int keytype;
typedef struct node{
keytype key;//关键字项
datatype other;//其他数据项
struct node *lchild,*rchild;
}bstnode;
三、实现
//往二叉排序树中插入一个结点
bstnode *INSERTBST(bstnode *t,bstnode *s){//t为二叉排序树的根指针,s为插入的结点指针
bstnode *f,*p;//f指向p的parent,p用来查找插入点
p=t;
while(p!=NULL){
f=p;
if(s->key==p->key)//树中已有s结点,无需插入
return t;
if(s->key < p->key)
p=p->lchild;
else
p=p->rchild;
}
if(t==NULL)//原数为空,返回s作为根结点
return s;
if(s->key < f->key)
f->lchild=s;
else
f->rchild=s;
return t;
}
//生成二叉排序树
bstnode *CREATBST(){
bstnode *t,*s;
keytype key,endflag=0;//endflag为结点结束标志
datatype data;
t=NULL;
scanf("%d",&key);
while(key!=endflag){
s=(bstnode*)malloc(sizeof(bstnode));
s->lchild=s->child=NULL;
s->key=key;
scanf("%d",&data);
s->other=data;
t=INSERTBST(t,s);
scanf("%d",&key);
}
return t;
}
结点删除
呜呜呜待续