数据结构学习笔记(四)—— 树

article/2025/8/19 5:28:05

文章目录

  • 一.树的基本概念
  • 二.树的遍历
      • 1.先序遍历
      • 2.后序遍历
      • 3.中序遍历
      • 4.层序遍历
  • 三.树的存储表示和操作实现
      • 1.双亲表示法
      • 2.孩子表示法
      • 3.双亲孩子表示法
      • 4.孩子兄弟表示法
  • 四.树的性质
  • 五.二叉树
    • 基本概念与定义
      • 二叉树的特殊形态——斜树
      • 二叉树的特殊形态——满二叉树
      • 二叉树的特殊形态——完全二叉树
    • 二叉树的重要性质
    • 二叉树顺序表
      • 定义
    • 二叉链表
      • 定义
      • 补充:三叉链表
    • 二叉树基本算法实现
      • 创建二叉树
      • 销毁二叉树
      • 求二叉树高度算法
      • 求二叉树结点个数算法
      • 求二叉树叶子结点个数算法
      • 以括号表示法输出二叉树运算算法
    • 二叉树的遍历
      • 先序遍历
      • 中序遍历
      • 后序遍历
      • 层序遍历
    • 二叉树的构造
    • 树和森林与二叉树的转换
      • 树转换为二叉树
      • 二叉树转换为树
      • 树遍历与二叉树遍历的对应关系
      • 森林转换为二叉树
      • 二叉树还原为森林
      • 森林遍历与二叉树遍历的对应关系
    • 哈夫曼树
      • 哈夫曼树算法
      • 哈夫曼树顺序表的定义
      • 构造哈夫曼树
      • 哈夫曼编码

一.树的基本概念

结点:包含一个元素及若干指向子树的分支。
根结点、终端结点、其他结点(仅有1个前驱,多个后继)

结点的度:一个结点所拥有的子树的个数

树的度:树中各结点度的最大值

叶子结点:度为0的结点
分支结点:度不为0的结点

在这里插入图片描述
路径:如果树的结点序列 {n1, n2, …, nk} 满足如下关系:结点 ni 是结点 ni+1 的双亲(其中,1≤i<k),则把 n1、n2、…nk 称为一条由 n1 至 nk 的路径 。
路径长度:路径上经过边的个数
在这里插入图片描述

祖先:如果从结点 x 到 y 有一条路径,那么结点 x 称为 y 的祖先
子孙:如果从结点 x 到 y 有一条路径,那么结点 y 称为 x 的子孙

结点层次:在一棵树中,根结点的层次为 1,其他结点的层次等于其双亲结点的层次加 1
树的深度:树中所有结点的最大层次,也称高度

有序树:如果将树中每个结点各子树看成是从左到右有次序的(即不能互换),则称该树为有序树。
无序树:如果将树中每个结点各子树看成是从左到右无次序的(即可以互换),则称该树为无序树
在数据结构中一般讨论的都是有序树

森林:m(m>=0)棵不相交的树的集合

同构:对两棵树,如果通过对结点适当地重命名,就可以使这两棵树完全相等(结点的位置完全相同),则称这两棵树同构
如图:此两树同构在这里插入图片描述

二.树的遍历

1.先序遍历

访问顺序:根——>左子树——>右子树
在这里插入图片描述

2.后序遍历

访问顺序:左子树——>右子树——>根
在这里插入图片描述

3.中序遍历

访问顺序:左子树——>根——>右子树

4.层序遍历

  • 从树的第一层(即根结点)开始,自上而下逐层遍历
  • 在同一层中,按从左到右的顺序对结点逐个访问
    在这里插入图片描述

三.树的存储表示和操作实现

1.双亲表示法

以一组连续空间存储树的结点,同时在每个结点中附设一个指示器指示其双亲在数组中的位置。采用这种存储结构的树称为双亲顺序表。
实际上是一个静态链表
存储形式如下:
在这里插入图片描述
在这里插入图片描述
双亲顺序表的类型定义

#define Tree_Size 100		// 树中结点的最大个数
typedef struct PTNode
{ ElemType   data;		// 树中结点的数据信息int	  parent;	// 该结点双亲在数组中的下标
}PTNode; 
typedef struct  { PTNode	tnode[Tree_Size];	// 存放树结点的数组int		num;		// 树中的结点个数
} PTree; 
  • 适合求指定结点的双亲或祖先(包括根)的操作
  • 如果求指定结点的孩子或其他后代时,则需要遍历整个结构

在双亲顺序表中求结点双亲的数据信息:
假设T中值为e的结点不多于1个:

  • 如果e不存在,则给出相应信息
  • 如果e是根结点,表示没有双亲,则给出相应信息
  • 如果e是T中除根之外的其他结点,则返回其双亲的数据信息
    代码如下:
//时间复杂度为O(n)
ElemType Parent_PT(PTree T,ElemType e) 
{ //返回双亲顺序表T中结点e的双亲的数据信息for(i=0;((i<T.num)&&T.tnode[i].data!=e);i++);if(i>=T.num)  Error("Node-e is not exist!");k=T.tnode[i].parent; // e双亲在T.tnode[i]中下标if(k==-1)  Error("Node-e has not parent!");else  return T.tnode[k].data;
}

2.孩子表示法

孩子链表
以单链表存储结点的孩子,则 n 个结点有 n 个孩子链表,其头指针采用顺序存储结构按层序存储。采用这种存储结构的树称为孩子链表

在这里插入图片描述
在这里插入图片描述
孩子链表的类型定义

#define Tree_Size  100			// 树中结点的最大个数
typedef struct CTNode 
{ int	 child;		// 孩子结点在表头数组中的下标struct CTNode* next;		// 指向下一个孩子结点
}CTNode,*ChildPtr; 
typedef struct
{ ElemType	data;		// 树中结点的数据信息ChildPtr	firstchild;	// 该结点孩子链表的头指针
}PTNode; 
typedef struct  
{ PTNode	 tnode[Tree_Size];	// 表头数组int  num;		// 树中的结点个数
}CTree; 

孩子链表的特点

  • 适合求指定结点的孩子及孩子兄弟的操作
  • 如果求指定结点的双亲或祖先(包括根),则需要遍历整个结构

在孩子链表中求结点孩子:
假设T中值为e的结点不多于1个:
① 如果e不存在,则给出相应信息;
② 如果e是T中某结点但无孩子,则给出相应信息;
③ 如果e是T中某结点且有孩子,则返回其第i个孩子的数据信息。

//时间复杂度为O(tn+cn),其中tn为树T的结点个数,cn为给定结点e的孩子个数
Elemtype Children_CT(CTree T,ElemType e,int i) 
{//返回孩子链表T中结点e的第i个孩子的数据信息int j;for(j=0;((j<T.num)&&T.tnode[j].data!=e);j++);if(j>=T.num)  Error("Node-e is not exist!");else  {p=T.tnode[j].firstchild;if(!p)  Error("Node-e has not children!");j=1;while(p&&(j<i))  {  p=p->next;  ++j;  }if(!p||(j>i))  Error(" Parameter Error!");else  {k=p->child;		// e第i个孩子在T.tnode[i]中下标return T.tnode[k].data;} }
} // Children_CT

3.双亲孩子表示法

在树的孩子链表的表头结点结构中增加一个双亲域,即将树的双亲顺序表与树的孩子链表结合起来。

采用这种存储结构的树称为双亲孩子链表

结构如下:
在这里插入图片描述
图示如图:
在这里插入图片描述
双亲孩子链表的类型定义

#define Tree_Size  100			// 树中结点的最大个数
typedef struct CTNode 
{ int  child;		// 孩子结点在表头数组中的下标struct CTNode  *next;		// 指向下一个孩子结点
}CTNode,*ChildPtr; 
typedef struct PCTNode  
{ ElemType  data;		// 树中结点的数据信息int	 parent;		// 该结点双亲在表头数组中的下标childPtr   firstchild;	// 该结点孩子链表的头指针
}PCTNode; 
typedef struct  
{ PCTNode	  tnode[Tree_Size];	// 表头数组int	  num;		// 树中的结点个数
} PCTree; 
  • 这种存储结构不仅表示了树中结点的双亲信息,也表示了孩子信息及孩子兄弟及孩子兄弟结点之间的关系
  • 适合求指定结点的双亲或祖先的操作,也适合求指定结点的孩子的操作。

4.孩子兄弟表示法

  • 链表中的结点是同构的,每个结点除了数据域外,还设置了两个指针域,分别指向该结点的第一个孩子和下一个兄弟
  • 采用这种存储结构的树称为孩子兄弟链表,又称为二叉链表

结构体形式如下:
(左第一个孩子,右兄弟)
在这里插入图片描述
在这里插入图片描述
二叉链表的类型定义

typedef struct CSNode  
{ElemType	 data; // 存储树中结点的数据信息struct CSNode  *firstchild;	// 指向该结点的第一个孩子结点struct CSNode  *rightsib;	// 指向该结点的下一个兄弟结点
}CSNode; 
typedef CSNode *CSTree;
  • 利用该存储结构易于实现查找结点孩子等操作
  • 如果要查找某结点双亲,则需遍历整个结构

四.树的性质

  • 树中的结点数等于所有结点的度数加一

  • 度为m的树中第i层上至多有m^(i-1)个结点,此时应满足i>=1
    补充:若一棵m次树的所有叶子结点在同一层,除该层外其余每一层都是满的,这样的树称为满二叉树
    满m次树是所有相同高度的m次树中结点总数最多的树

  • 高度为h的m次树至多有((m^h)-1)/(m-1)个结点
    在这里插入图片描述

【例】 若一棵三次树中度为3的结点为2个,度为2的结点为1个,度为1的结点为2个,则该三次树中总的结点个数和度为0的结点个数分别是多少?

解:设该三次树中总结点个数、度为0的结点个数、度为1的结点个数、度为2的结点个数和度为3的结点个数分别为n、n0、n1、n2和n3。显然,每个度为i的结点在所有结点的度数之和中贡献i个度。依题意有:n1=2,n2=1,n3=2。由树的性质1可知
  n=所有结点的度数之和+1
  =0×n0+1×n1+2×n2+3×n3+1=1×2+2×1+3×2+1=11
  又因为n=n0+n1+n2+n3
  即:n0=n-n1-n2-n3=11-2-1-2=6
  所以该三次树中总的结点个数和度为0的结点个数分别是11和6。

五.二叉树

基本概念与定义

二叉树是 n(n≥0)个结点的有限集合。
当 n=0 时,称为空二叉树;
任意一棵非空二叉树满足以下两个条件:

  • 有且仅有一个特定的称为根的结点;
  • 当 n>1 时,除根结点外的其余结点最多被分成两个互不相交的有限集合,其中每个集合又是一棵二叉树,并称为这个根结点的左子树和右子树

二叉树是有序的,其次序不能任意颠倒,即使树中某个结点只有一棵子树也要区分它是左子树还是右子树,因此二叉树和树是两种不同的树结构

二叉树的结点数可以为0,度为2的树至少有三个结点

二叉树的特殊形态——斜树

所有结点都只有左子树或者所有结点都只有右子树的二叉树称为斜树
在这里插入图片描述

在斜树中,每层只有一个结点,所以斜树结点个数与其深度相同

二叉树的特殊形态——满二叉树

一棵深度为k且有2^k-1个结点的二叉树称为满二叉树
在这里插入图片描述
在这里插入图片描述

二叉树的特殊形态——完全二叉树

对一棵具有n个结点的二叉树按层序遍历,若编号为i(1<=i<=n)结点与同深度满二叉树中编号为i结点在树中位置完全相同,则称之为完全二叉树
在这里插入图片描述

即不一定要每层的结点都满,但是要按顺序编号的时候不能缺

特点:

  • 在满二叉树最下面一层,从最右边开始连续删去若干个结点后得到的是一棵完全二叉树
  • 在完全二叉树中,如果某个结点没有左孩子,则它一定没有右孩子,且该结点必定是叶结点

二叉树的重要性质

  • 在二叉树的第 i 层上至多有 2^(k-1) 个结点(i >= 1)
  • 深度为k的二叉树至多有 2^k-1 个结点(k >= 1)
  • 对于任意一棵二叉树,如果其叶结点数为n0,度为2的结点数为n2,则有关系:n0 = n2 + 1

【例】 一棵二叉树中总结点个数为200,其中单分支结点个数为19,求其叶子结点个数。

解:依题意,n=200,n1=19。又n=n0+n1+n2,
由性质1得,n2=n0-1,
所以有:n=2n0-1+n1,
即n0=(n-n1+1)/2=91。

  • 具有n个结点的完全二叉树的深度为在这里插入图片描述

    (括号表示取整函数)

  • 对一棵具有n个结点的完全二叉树中的结点从1开始层序遍历,则对于任意的编号为i(1<=i<=n)的结点,有:
    (1) 若 i>1,则结点 i 双亲编号为 [i/2];否则结点 i 是根,无双亲
    (2) 若 2i≤n,则结点 i 左孩子编号为 2i;否则结点 i 无左孩子,且该结点为叶子结点
    (3) 若 2i+1≤n,则结点 i 右孩子编号为 2i+1;否则结点 i 无右孩子

【例】 一棵完全二叉树中总结点个数为200,求其叶子结点个数。
解:依题意,n=200,由于n为偶数,所以n1=1。
又n=n0+n1+n2,由性质1得,n2=n0-1,
所以有:n=2n0-1+n1,n0=(n-n1+1)/2=100。
这样的完全二叉树中叶子结点个数为100。

二叉树顺序表

定义

对于一般二叉树,增添“虚结点”,使之“转化”为一棵完全二叉树
以一组连续空间按层序存储二叉树中所有的“实结点”和“虚结点”,采用这种存储结构的二叉树称为二叉树顺序表
(虚节点的存储位置用 " 空 ")
在这里插入图片描述

typedef TElemType SqBiTree[MaxSize];
SqBiTree bt;// 将下标为0的位置空着,值为'#'的结点为空结点。

二叉链表

定义

链表中的结点是同构的,每个结点除了数据域 data 外,还设置了两个指针域 lchild 和 rchild,分别指向该结点的左孩子和右孩子
采用这种存储结构的二叉树称为二叉链表
在这里插入图片描述
在这里插入图片描述
类型定义

typedef struct tnode  
{ElemType data;	//二叉树中结点的数据信息struct tnode  *lchild, *rchild; // 结点左右孩子指针
} BTNode; 

补充:三叉链表

在这里插入图片描述
在这里插入图片描述
类型定义

typedef struct TriTNode
{  TelemType data;TriTNode *lchild,*parent,*rchild;
}TriTNode,*TriTree;

二叉树基本算法实现

创建二叉树

步骤如下:
创建二叉树A(B(D,E(G,H)),C(,F(I)))
其中只有4类字符,各类字符的处理方式如下:

  • 若ch=’(’:
    表示前面刚创建的p结点存在着孩子结点需将其进栈,以便建立它和其孩子结点的关系(如果一个结点刚创建完毕,其后一个字符不是’(’,表示该结点是叶子结点,不需要进栈)。然后开始处理该结点的左孩子,因此置k=1;
  • 若ch=’)’:
    表示以栈顶结点为根结点的子树创建完毕,将其退栈
  • 若ch=’,’:
    表示开始处理栈顶结点的右孩子结点,置k=2
  • 其他情况:
    只能是单个字符,表示要创建一个新结点p,根据k值建立p结点与栈顶结点之间的联系,当k=1时,表示p结点作为栈顶结点的左孩子结点,当k=2时,表示p结点作为栈顶结点的右孩子结点
    代码如下:
void CreateBTree(BTNode * &bt,char *str)
{  BTNode *St[MaxSize],*p=NULL;int top=-1,k,j=0;char ch;bt=NULL;		//建立的二叉树初始时为空ch=str[j];while (ch!='\0')	//str未扫描完时循环{	 switch(ch){ case '(':top++;St[top]=p;k=1; break;//为左孩子结点case ')':top--;break;case ',':k=2; break;			//为右孩子结点default:p=(BTNode *)malloc(sizeof(BTNode));p->data = ch;p->lchild = p->rchild=NULL;if (bt==NULL)	//*p为二叉树的根结点bt=p;else	//已建立二叉树根结点{     switch(k) {case 1:St[top]->lchild=p;break;case 2:St[top]->rchild=p;break;}}}j++; ch=str[j]; }
}

销毁二叉树

当bt=NULL时 :
f(bt) = 不做任何事情

其他情况时:
f(bt) = f(bt->lchild);
f(bt->rchild);
free(bt);

void DestroyBTree(BTNode *&bt)
{   if (bt!=NULL){	DestroyBTree(bt->lchild);DestroyBTree(bt->rchild);free(bt);}
}

求二叉树高度算法

若bt=NULL:
f(bt)=0
其他情况:
f(bt)=max{f(bt->lchild),f(bt->rchild)}+1

代码如下:

int BTHeight(BTNode *bt) 
{   int lchilddep,rchilddep;if (bt==NULL) return(0); 		//空树的高度为0else{	lchilddep=BTHeight(bt->lchild);	//求左子树的高度为lchilddeprchilddep=BTHeight(bt->rchild);	//求右子树的高度为rchilddepreturn (lchilddep>rchilddep)? (lchilddep+1):(rchilddep+1);}
}

求二叉树结点个数算法

当bt=NULL:
f(bt)=0
其他情况:
f(bt)=f(bt->lchild)+f(bt->rchild)+1

代码如下:

int NodeCount(BTNode *bt)	//求二叉树bt的结点个数
{    int num1,num2;if (bt==NULL)			//为空树时返回0return 0;else{	num1=NodeCount(bt->lchild);	//求左子树结点个数num2=NodeCount(bt->rchild);	//求右子树结点个数return (num1+num2+1);		//返回和加上1}
}

求二叉树叶子结点个数算法

当bt=NULL:
f(bt)=0
当*bt为叶子结点:
f(bt)=1
其他情况:
f(bt)=f(bt->lchild)+f(bt->rchild)

int LeafCount(BTNode *bt) //求二叉树bt的叶子结点个数
{       int num1,num2;if (bt==NULL)		//空树返回0return 0;else if (bt->lchild==NULL && bt->rchild==NULL) return 1;		//为叶子结点时返回1else{	num1=LeafCount(bt->lchild);	//求左子树叶子结点个数num2=LeafCount(bt->rchild); 	//求右子树叶子结点个数return (num1+num2);		//返回和}
}	

以括号表示法输出二叉树运算算法

对于非空二叉树bt:

  • 先输出其元素值
  • 当存在左孩子结点或右孩子结点时,输出一个“(”符号
  • 递归处理左子树,输出一个“,”符号
  • 递归处理右子树,最后输出一个“)”符号
void DispBTree(BTNode *bt) 
{   if (bt!=NULL){	printf("%c",bt->data);if (bt->lchild!=NULL || bt->rchild!=NULL){	printf("(");		//有子树时输出'('DispBTree(bt->lchild);	//递归处理左子树if (bt->rchild!=NULL)	//有右子树时输出','printf(",");DispBTree(bt->rchild);	//递归处理右子树printf(")");	//右子树输出完毕,再输出一个')'}  }
}

二叉树的遍历

先序遍历

若二叉树非空,则:
① 访问根结点;
② 先序遍历左子树;
③ 先序遍历右子树。
代码如下:

void PreOrder(BTNode *bt)
{   if (bt!=NULL){	printf("%c ",bt->data);PreOrder(bt->lchild);PreOrder(bt->rchild);}
}

中序遍历

若二叉树非空,则:
① 中序遍历左子树;
② 访问根结点;
③ 中序遍历右子树。

void InOrder(BTNode *bt)
{      if (bt!=NULL){	InOrder(bt->lchild);printf("%c ",bt->data);InOrder(bt->rchild);}
}

后序遍历

若二叉树非空,则:
① 后序遍历左子树;
② 后序遍历右子树;
③ 访问根结点。

void PostOrder(BTNode *bt)
{   if (bt!=NULL){	PostOrder(bt->lchild);PostOrder(bt->rchild);printf("%c ",bt->data);}
}

层序遍历

层次遍历是从根结点出发,按照从上向下,同一层从左向右的次序访问所有的结点。
在层次遍历算法中采用一个循环队列来实现。

  • 实现过程:
    先将根结点进队,在队不空时循环。
    从队列中出队一个结点*p,访问它;
    若它有左孩子结点,将左孩子结点进队;
    若它有右孩子结点,将右孩子结点进队。
    如此操作直到队空为止。
    在这里插入图片描述
    总结:
    三种算法的访问路径是相同的,只是访问结点的时机不同
    在这里插入图片描述
    时间效率:O(n) //每个结点只访问一次
    空间效率:O(n) //栈占用的最大辅助空间

重要结论:
若二叉树中各结点的值均不相同,则:

  • 由二叉树的前序序列中序序列,或由其后序序列中序序列唯一地确定一棵二叉树,
  • 前序序列后序序列不一定能唯一地确定一棵二叉树。

二叉树的构造

给定某些遍历序列,反过来唯一地确定该二叉树的形态,这个过程称为二叉树的构造

若二叉树的先序遍历和中序遍历顺序一致,则该树为右单支树
在这里插入图片描述

  • 由先序遍历和中序遍历可以唯一确定一棵二叉树:
    在这里插入图片描述
  • 由中序遍历和后序遍历可以唯一确定一棵二叉树:
    在这里插入图片描述
  • 由层次遍历序列和中序遍历序列可以唯一确定一棵二叉树:

层次遍历序列的第一个结点是二叉树的根结点,确定根结点后,到二叉树的中序遍历序列中找到该结点,这个结点将二叉树分为左子树、根和右子树三部分。
左子树中所有结点在层次遍历序列中出现的次序对应左子树的层次遍历序列,右子树中所有结点在层次遍历序列中出现的次序对应右子树的层次遍历序列,再采用同样的方式构造左、右子树,从而构造出整棵二叉树。

树和森林与二叉树的转换

树转换为二叉树

  • 加线:在各兄弟结点间加一个连线,将其隐含的兄弟关系以“父-右子”关系表现出来
  • 抹线:对于任何节点结点,除了最左边孩子外,抹掉该结点与其他孩子间的 “父-子”关系
  • 调整:以树根作为二叉树根,将其与其最左孩子间“父-子”关系改为“父-左子”关系,且将各节点按二叉树层次排列

经过该方法转换后的二叉树是唯一的,且具有以下特点:
1.此二叉树根结点只有左子树,无右子树
2.转换生成的二叉树中各结点左孩子是它在树中最左孩子,右孩子是它在树中下一个兄弟

二叉树转换为树

  • 加线:在各结点双亲与该结点右链上每个结点间加一连线,以“父-子”关系显示表示出来
  • 在这里插入图片描述
  • 抹线:抹掉二叉树中所有的双亲结点与其右孩子之间的“父-右子”关系
  • 在这里插入图片描述
  • 调整:以二叉树根节点作为树根结点,将各结点按照树的层次排列,形成树的结构

一棵没有右子树的二叉树经过该方法还原的树是为唯一的。

树遍历与二叉树遍历的对应关系

  • 树的先序遍历对应二叉树的先序遍历
  • 树的后序遍历对应二叉树的中序遍历
  • 树的层序遍历不对应二叉树任何遍历

森林转换为二叉树

  • 将森林中的每一棵子树按上述规则转换为二叉树
  • 在这里插入图片描述
  • 连线:从第二棵二叉树开始,依次把后一棵二叉树的根节点作为前一棵二叉树根节点的右孩子
  • 调整:以森林中第一棵树作为二叉树根,第一棵树子树森林转换后作为二叉树左子树,将森林中删去第一棵树后,其余树构成的森林转换后作为二叉树右子树,且将各结点按照二叉树的层次排列,形成二叉树的结构

经过该方法转换后对应的二叉树是唯一的,并具有以下特点:
● 二叉树根有左子树,也有右子树;
● 转换生成的二叉树根的左子树是原来森林中的第一棵子树,而右子树是其余树的构成。

二叉树还原为森林

  • 抹线:抹掉二叉树根结点右链上所有结点间的“父-右子”关系,分成若干个以右链上的结点为根节点的子二叉树
  • 转换和排列:将分好的二叉树按照上述规则还原为树,并排成一排,形成森林结构

一棵具有左子树和右子树的二叉树经过这种方法还原的森林是唯一的

森林遍历与二叉树遍历的对应关系

森林先序遍历定义:
如果森林为空,则空操作返回;否则:
① 访问森林中第一棵树的根结点;
② 先序遍历第一棵树中根结点的子树森林;
③ 先序遍历除去第一棵树之后剩余的树构成的森林

  • 森林的先序遍历对应二叉树的先序遍历

森林中序遍历定义:
如果森林为空,则空操作返回;否则:
① 中序遍历第一棵树中根结点的子树森林;
② 访问森林中第一棵树的根结点;
③ 中序遍历除去第一棵树之后剩余的树构成的森林。

  • 森林的中序遍历对应二叉树的中序遍历。

哈夫曼树

哈夫曼树是 n 个带权叶子结点构成的所有二叉树中带权路径长度最短的二叉树,有着非常广泛的应用。

路径:从树中一个结点到另一个结点之间的分支
路径长度:路径上的分支数目
树的路径长度:从树根到每一个结点的路径长度之和。在结点数目相同的二叉树中,完全二叉树的路径长度最短
结点的权:在一些应用中,赋予树中结点一个有某种意义的实数
结点的带权路径长度:结点到树根之间路径长度与结点上权乘积
树的带权路径长度:树中所有叶子结点带权路径长度之和,也称为树的代价
在这里插入图片描述

如:

在这里插入图片描述
结点a到g的路径
路径上的分支数目为3
树的路径长度为01+12+22+32 = 12
结点的权在这里插入图片描述
结点d的带权路径长度为24 = 8
树的带权路径长度为(注意是叶子结点):2
1+42+73+5*3 = 47

  • n个带权叶子结点的二叉树中,满二叉树或完全二叉树不一定是哈夫曼树;只有当叶子权值均相同时,满二叉树或完全二叉树才是哈夫曼树
  • 在哈夫曼树中,权越大的叶子离根越近
  • 哈夫曼树的形态不唯一,但其 WPL 最小

哈夫曼树算法

  • 根据给定的 n 个权值 w1、w2、…、wn,构造 n 棵二叉树的森林F={T1, T2, … , Tn},其中每棵二叉树 Ti 中都只有一个带权 wi 的根结点,其左子树和右子树均为空
  • 在森林 F 中选出两棵根结点权值最小的树作为左右子树构造一棵新二叉树,且置新二叉树根结点权值为其左右子树上根结点的权值之和
  • 在森林 F 中删除上面选中的那两棵根结点权值最小的二叉树,同时将新得到的二叉树加入森林 F 中
  • 重复第 2 步和第 3 步,直到森林 F 中只剩下一棵树为止。这棵树便是哈夫曼树

n 个叶子哈夫曼树合并 n-1 次,产生 n-1 个新结点,共包含 2n-1 个结点
在哈夫曼树中没有度数为 1 的分支结点

哈夫曼树顺序表的定义

一组连续空间存储哈夫曼树结点,每个结点中包含一个权值域 weight 存储结点权值,三个指示器 parent、lchild 和 rchild 分别指示其双亲和左右孩子在数组中位置
采用这种存储结构的哈夫曼树称为哈夫曼树顺序表
在这里插入图片描述
在这里插入图片描述
类型定义代码如下:

typedef struct  { int  weight;// 结点权值;若为实数,则转换为整数int  parent, lchild, rchild;	// 双亲及左右孩子在数组中的下标
} HTNode;
typedef HTNode *HuffmanTree;

构造哈夫曼树

假设:数组的0号单元不用,从1单元开始使用,共n+n-1=2n-1个存储空间

设哈夫曼树有n个叶子结点,哈夫曼树顺序表为HT。

① 初始化:首先动态申请2n个单元,然后循环2n-1次,从1单元开始,依次将1至2n-1所有单元的双亲、左孩子、右孩子的下标初始化为0;循环n次输入前n个单元中叶子结点的权值。

② 创建树:循环n-1次,通过n-1次的选择、删除与合并来创建哈夫曼树。选择是从森林中选择双亲为0且权值最小的两个根结点,删除是指将结点的双亲改为非0,合并即为将两个结点的权值和作为一个新结点的权值依次存入数组的第n+1之后的单元中,同时记录这个新结点左孩子下标和右孩子下标

代码如下:

该算法的执行时间依赖于初始化、输入权、合并树三个操作,其中:
① 初始化操作的时间依赖于哈夫曼树的结点个数2n-1,其时间复杂度为O(n);
② 输入权操作的时间依赖于哈夫曼树叶子个数n,其时间复杂度为O(n);
③ 合并树操作的时间依赖于构造哈夫曼树非叶子的个数n-1,由于构造一个非叶子的时间复杂度为O(i)(n≤i≤2n-1),因此其时间复杂度为O(n2)。
因此算法5.10的时间复杂度为O(n2)
void CreateHuffmanTree(HuffmanTree &HT,int n) 
{// 构造并返回n个叶子的哈夫曼树HTif(n<=1)  return;m=2*n–1;HT=new HTNode[m+1];		// 初始化:创建大小为m的HTfor(i=1;i<=m;i++) {			// 输入权:输入叶子权值HT[i].parent=0;HT[i].lchild=0;HT[i].rchild=0;    }for(i=1;i<=n;i++) 			// 输入权:输入叶子权值cin>>HT[i].weight;   for(i=n+1;i<=m;++i) {	// 通过n-1次选择、删除、合并树:构造HTSelect(HT,i-1,s1,s2);	// 在HT[0..i-1]中选择两个最小权值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;}
} // CreateHuffmanTree

哈夫曼编码

  • 数据压缩过程称为编码,即将文件中的每个字符均转换为一个唯一的二进制位串
  • 数据还原过程称为解码,即将二进制位串转换为对应的字符

等长编码与变长编码
等长编码:将给定大小为 n 的字符集 C 中的每个字符设置同样的码长,即 在这里插入图片描述

向上取整,如在这里插入图片描述

变长编码:将给定大小为 n 的字符集 C 中频度高的字符编码设置较短,频度低的字符编码设置较长

前缀编码:对字符集进行编码时,要求字符集中任一字符的编码都不是其他字符编码的前缀。
等长编码是一个前缀编码
最优前缀编码:平均码长或文件总长最小的前缀编码。最优前缀编码对文件的压缩效果也最佳

哈夫曼编码的定义:

用字符 ci 作为叶子,wi 做为叶子 ci 的权,构造一棵哈夫曼树,并将树中左分支和右分支分别标记为 0 和
1;将从根到叶子路径分支上的二进制组成字符串作为该叶子所表示字符的编码,称为哈夫曼编码。哈夫曼编码就是最优前缀编码

在这里插入图片描述
在这里插入图片描述
哈夫曼编码的存储表示:
因为各字符的编码长度不等,所以应该按照编码的实际长度动态分配空间。由此给出哈夫曼编码表的类型定义。

typedef char **HuffmanCode;	// 动态分配数组存储哈夫曼编码表

在给定字符集的哈夫曼树HT生成之后,依次以叶子HT[i](0≤i≤n-1)为出发点,向上回溯至根为止。向上回溯时,走左分支则生成代码0,走右分支则生成代码1。
① 由于生成的编码与要求的编码反序,因此将生成的代码先从后往前依次存放在一个临时数组cd中,并附设一个指针start指示编码在cd中的起始位置(start初始时指示cd的结束位置);
② 当某个字符编码完成时,再从cd的start处将编码复制到存储空间。
代码如下:

//该算法的执行时间依赖于对n个字符构造其哈夫曼编码。
//求解一个字符哈夫曼编码的时间是由叶子为出发点,向上回溯至根为止,最长路径长度为n-1,其时间复杂度为O(n);
因此算法的时间复杂度为O(n2)
void CreatHuffmanCode(HuffmanTree HT,HuffmanCode &HC,int n)  
{// 从叶子到根逆向求每个字符的哈夫曼编码,存储在哈夫曼编码HCHC=new char*[n+1];	 // 分配n个字符编码的头指针数组cd=new char[n]; // 分配临时存放每个字符编码动态数组空间cd[n-1]='\0';			// 置编码结束符for(i=1;i<=n;++i)  {	// 逐个字符求哈夫曼编码start=n–1;	// 指向最后,设置编码结束符位置初值c=i;f=HT[i].parent    // f指向结点c的双亲结点while(f!=0)  {	// 从叶子结点开始向上回溯到树根为止--start;        //回溯一次start向前指一个位置    if(HT[f].lchild==c) cd[start]='0'; //结点c是f的左孩子,则生成代码0else cd[start]='1';  //结点c是f的右孩子,生成代码1c=f; f=HT[f].parent; //继续向上回溯      }HC[i]=new char[n-start]; // 为第i个字符编码分配空间strcpy(HC[i],&cd[start]);	// 将求得的编码从临时空间cd复制到HC当前行   }delete cd;			// 释放工作空间
} // HuffmanCoding

http://chatgpt.dhexx.cn/article/yEIJAMPj.shtml

相关文章

sklearn学习(集成算法:随机森林)

随机森林树 一.概述【1】集成算法概述1.概念与应用2.集成算法的目标3.其他定义 【2】sklearn中的集成算法1.sklearn中的集成算法模块ensemble&#xff08;1&#xff09;类与类的功能 2.复习&#xff1a;sklearn中的决策树3.sklearn的基本建模流程 二.RandomForestClassifier【1…

数据结构与算法之树(三)AVL树

数据结构与算法之树 数据结构与算法之树&#xff08;一&#xff09;二叉树概念及遍历方式&#xff08;图文并茂&#xff09; 数据结构与算法之树&#xff08;二&#xff09;二叉查找树 数据结构与算法之树&#xff08;三&#xff09;AVL树 数据结构与算法之树&#xff08;四…

C语言数据结构总结:树

树 一&#xff0c;树的定义二&#xff0c;树的基本术语三&#xff0c;二叉树的定义四&#xff0c;二叉树的性质和存储结构五&#xff0c;关于二叉树的算法 一&#xff0c;树的定义 树是n&#xff08;n>0&#xff09;个结点的有限集合。 若n0&#xff0c;称为空树。 若n>…

【C++从入门到入土】第二十一篇:二叉搜索树之AVL树

AVL树 文章目录 AVL树一、AVL树1.特点2.操作旋转插入删除查找 一、AVL树 在计算机科学中&#xff0c;AVL树是最先发明的自平衡二叉查找树。在AVL树中任何节点的两个子树的高度最大差别为1&#xff0c;所以它也被称为高度平衡树。增加和删除可能需要通过一次或多次树旋转来重新平…

数据结构--二叉搜索树

二叉搜索树 一丶概念以及特点二丶相关操作定义TreeMap类put()操作--插入节点get()操作--得到key对应的value值getOrDefault()操作containsKey()操作--检查key是否存在containsValue()操作--检查value是否存在remove()操作--删除操作思路&#xff08;1&#xff09;叶子结点&…

Java数据结构--树2

Java数据结构--树 一、平衡树1.1 2-3 查找树1.1.1 2-3查找树的定义1.1.2 查找1.1.3 插入1.1.3.1 向2-结点中插入新键1.1.3.2 向一棵只含有一个3-结点的树中插入新键1.1.3.3 向一个父结点为2-结点的3-结点中插入新键1.3.1.4 向一个父结点为3-结点的3-结点中插入新键1.3.1.5 分解…

数据结构之多路查找树

多路查找树 一、2-3树1.1 查找1.2 2-3树的插入实现1.3 2-3树的删除节点 二、2-3-4树三、总结 二叉排序树简单的实现在多数情况能够达到预期的查找效率&#xff0c;但是每个节点只能存储一个元素和只能有两个孩子&#xff0c;使得在大量数据下会造成二叉排序树的深度特别大&…

【数据结构 7】二叉查找树及其Java实现

【数据结构 1】顺序表及其Java实现 【数据结构 2】单向链表及其Java实现 【数据结构 3】双向链表及其Java实现 【数据结构 4】栈及其Java实现 【数据结构 5】队列及其Java实现 【数据结构 6】符号表及其Java实现&#xff08;使用链表实现&#xff09; 【数据结构 7】二叉查找树…

C++从入门到精通(第十篇) :二叉搜索树

二叉搜索树 一&#xff1a;二叉搜索树概念二&#xff1a; 二叉搜索树实现节点的定义二叉搜索树实现 三&#xff1a;二叉搜索树的应用四&#xff1a;二叉树有关面试题ps 很多小伙伴为了刷题发愁 今天为大家推荐一款刷题神奇哦&#xff1a;刷题面试神器牛客 各大互联网大厂面试真…

数据结构与算法之树(二)二叉查找树

数据结构与算法之树 数据结构与算法之树&#xff08;一&#xff09;二叉树概念及遍历方式&#xff08;图文并茂&#xff09; 数据结构与算法之树&#xff08;二&#xff09;二叉查找树 数据结构与算法之树&#xff08;三&#xff09;AVL树 数据结构与算法之树&#xff08;四…

【算法修炼】二叉搜索树

学习自&#xff1a;https://labuladong.gitee.io/algo/2/19/26/ 二叉搜索树 一、BST的中序遍历230、二叉搜索树中第k小的元素&#xff08;中等&#xff09;1038、从二叉搜索树到更大和树&#xff08;中等&#xff09; 二、判断BST的合法性※98、验证二叉搜索树&#xff08;中等…

数据结构(C语言)-树

树 一、树1、树的定义2、树的基本术语3、树结构和线性结构的比较 二、二叉树1、二叉树的定义2、二叉树的形态与树的形态3、二叉树的性质4 、二叉树的存储结构5、遍历二叉树6、二叉树的其他操作7、线索二叉树 三、树与二叉树的转换1、树转换成二叉树2、二叉树变树 四、哈夫曼树1…

【数据结构与算法】程序内功篇六--树

程序内功篇六--树 一、树1、树的含义2、树的特点(选看)3、树的逻辑结构 二、二叉树1、二叉树的含义2、二叉树性质3、二叉树-顺序存储4、二叉树-链式存储5、二叉树的遍历6、二叉树创建与遍历C程序的实现&#xff08;1&#xff09;二叉树的创建&#xff08;2&#xff09;前序遍历…

数据结构---与树相关的知识

与树有关的一系列知识: 树,二叉树,满二叉树,完全二叉树,文章还没完,我会后序补充的 一: 树(了解就行)1.1 概念1.2 一些与树相关的重要概念1.3 树的表示形式 二: 二叉树(非常重要,重点掌握)2.1 概念2.2 两种特殊的二叉树2.2.1 满二叉树2.2.2 完全二叉树 2.3 二叉树的性质2.4 二叉…

Java数据结构--树1

Java数据结构--树 一、二叉树入门1.1 树的基本定义1.2 树的相关术语1.3 二叉树的基本定义1.4 二叉查找树的创建1.4.1 二叉树的结点类1.4.2 二叉查找树API设计1.4.3 二叉查找树实现1.4.4 二叉查找树其他便捷方法1.4.4.1 查找二叉树中最小的键1.4.4.2 查找二叉树中最大的键 1.5 二…

树 一

文章目录 1.查找二分查找判定树 2. 树(Tree)2.1 树的术语2.2树的表示&#xff1a;儿子兄弟表示法 3. 二叉树&#xff08;Binary Tree&#xff09;3.1 特殊结构二叉树3.2 二叉树的性质3.3 二叉树的存储3.4二叉树的遍历 分层次组织管理上更有效地操作。 1.查找 静态查找&#xf…

树一:邂逅入门篇

一、树的概念 树是一种典型的非线性结构&#xff0c;是表达有层次特性的图结构的一种方法。 1.1 基本术语 术语描述空树当n0 时称为空树。根结点根结点是一个没有双亲结点的结点。一棵树最多一个。例如&#xff1a;A边结点之间的连线叶子结点没有孩子结点的结点。例如&#x…

树一:定义及存储

树的定义&#xff1a; 树是一种非线性的数据结构。树是由 n (n > 0) 个结点组成的有序集合。 如果 n 为0&#xff0c;称为空树&#xff1b;如果 n > 0, 则&#xff1a; 有一个结点称为根结点&#xff08;root&#xff09;&#xff0c;它有直接后继&#xff0c;但没有直接…

mysql 创建索引规则

1、表的主键、外键必须有索引&#xff1b; 2、数据量超过300的表应该有索引&#xff1b; 3、经常与其他表进行连接的表&#xff0c;在连接字段上应该建立索引&#xff1b; 4、经常出现在Where子句中的字段&#xff0c;特别是大表的字段&#xff0c;应该建立索引&#xff1b; 5、…

mysql分区表之三:MySQL分区建索引,唯一索引

介绍 mysql分区后每个分区成了独立的文件&#xff0c;虽然从逻辑上还是一张表其实已经分成了多张独立的表&#xff0c;从“information_schema.INNODB_SYS_TABLES”系统表可以看到每个分区都存在独立的TABLE_ID,由于Innodb数据和索引都是保存在".ibd"文件当中&#…