数据结构—树与二叉树(Part Ⅵ)——平衡二叉树&哈夫曼树

平衡二叉树

平衡二叉树的定义

  • 为避免树的高度增长过快,降低二叉排序树的性能,规定在插入和删除二叉树结点时,要保证任意结点的左、右子树高度差的绝对值不超过1,将这样的二叉树称为平衡二叉树(Balanced Binary Tree),简称平衡树。
  • 定义结点左子树与右子树的高度差为该结点的平衡因子,则平衡二叉树结点的平衡因子的值只可能是-1、0或1。因此,平衡二叉树可定义为或者是一棵空树,或者是具有下列性质的二叉树:
    它的左子树和右子树都是平衡二叉树,且左子树和右子树的高度差的绝对值不超过1。

平衡二叉树的插入

调 整 最 小 不 平 衡 子 树 A { L L 型          在 A 的 左 孩 子 的 左 子 树 中 插 入 导 致 不 平 衡 R R 型          在 A 的 右 孩 子 的 右 子 树 中 插 入 导 致 不 平 衡 L R 型          在 A 的 左 孩 子 的 右 子 树 中 插 入 导 致 不 平 衡 R L 型          在 A 的 右 孩 子 的 左 子 树 中 插 入 导 致 不 平 衡 调整最小不平衡子树A\begin{cases} LL型\;\;\;\;在A的左孩子的左子树中插入导致不平衡 \\ RR型\;\;\;\;在A的右孩子的右子树中插入导致不平衡 \\ LR型\;\;\;\;在A的左孩子的右子树中插入导致不平衡 \\ RL型\;\;\;\;在A的右孩子的左子树中插入导致不平衡 \\ \end{cases} ALLARRALRARLA

LL型

  • LL平衡旋转(右单旋转)。由于在结点A的左孩子 ( L ) (L) (L)的左子树 ( L ) (L) (L)上插入了新结点,A的平衡因子由1增至2,导致以A为根的子树失去平衡,需要一次向右的旋转操作。
  • 将A的左孩子B向右上旋转代替A成为根结点,将A结点向右下旋转成为B的右子树的根结点,而B的原右子树则作为A结点的左子树
    在这里插入图片描述

RR型

  • RR平衡旋转(左单旋转)。由于在结点A的右孩子 ( R ) (R) (R)的右子树 ( R ) (R) (R)上插入了新结点,A的平衡因子由-1减至-2,导致以A为根的子树失去平衡,需要一次向左的旋转操作。
  • 将A的右孩子B向左上旋转代替A成为根结点,将A结点向左下旋转成为B的左子树的根结点,而B的原左子树则作为A结点的右子树
    在这里插入图片描述

代码思路

  • ①LL型
    在这里插入图片描述

  • 实现f向右下旋转,p向右上旋转:
    其中f是爹,p为左孩子,gf为f他爹
    ①f->lchild = p->rchild;
    ②p->rchild = f;
    ③gf->lchild/rchild = p;
    BL<B<BR<A<AR

  • ②RR型
    在这里插入图片描述

  • 实现f向左下旋转,p向左上旋转:
    其中d是爹,p为右孩子,gf为f他爹
    ①f->rchild = p->lchild;
    ②p->lchild = f;
    ③gf->lchild/rchild = p;
    AL<A<BL<B<BR
    左旋、右旋操作后可以保持二叉排序树的特性

LR型

  • LR平衡旋转(先左后右双旋转)。由于在A的左孩子 ( L ) (L) (L)的右子树 ( R ) (R) (R)上插入新结点,A的平衡因子由1增至2,导致以A为根的子树失去平衡,需要进行两次旋转操作,先左旋转后右旋转。
  • 先将A结点的左孩子B的右子树的根结点C向左上旋转提升到B结点的位置,然后再把该C结点向右上旋转提升到A结点的位置
    在这里插入图片描述
    在这里插入图片描述

RL型

  • RL平衡旋转(先右后左双旋转)。由于在A的右孩子 ( R ) (R) (R)的左子树 ( L ) (L) (L)上插入新结点,A的平衡因子由-1减至-2,导致以A为根的子树失去平衡,需要进行两次旋转操作,先右旋转后左旋转。
  • 先将A结点的右孩子B的左子树的根结点C向右上旋转提升到B结点的位置,然后再把该C结点向左上旋转提升到A结点的位置
    在这里插入图片描述
    在这里插入图片描述

哈夫曼树和哈夫曼编码

哈夫曼树的定义

  • 在许多应用中,树中结点常常被赋予一个表示某种意义的数值,称为该结点的权。

  • 从树的根到任意结点的路径长度(经过的边数)与该结点上权值的乘积,称为该结点的带权路径长度

  • 树中所有叶结点的带权路径长度之和称为该树的带权路径长度,记为 W P L = ∑ i = 1 n w i l i WPL=\sum_{i=1}^nw_il_i WPL=i=1nwili,其中, w i w_i wi是第 i i i个叶结点所带的权值, l i l_i li是该叶结点到根结点的路径长度。
    在这里插入图片描述

  • 在含有n个带权叶结点的二叉树中,其中带权路径长度(WPL)最小的二叉树称为哈夫曼树,也称最优二叉树。
    在这里插入图片描述

哈夫曼树的构造

  • 给定n个权值分别为 w 1 , w 2 , . . . , w n w_1,w_2,...,w_n w1,w2,...,wn的结点,构造哈夫曼树的算法描述如下:
  1. 将这n个结点分别作为n棵仅含一个结点的二叉树,构成森林F。
  2. 构造一个新结点,从F中选取两棵根结点权值最小的树作为新结点的左、右子树,并且将新结点的权值置为左、右子树上根结点的权值之和。
  3. 从F中删除刚才选出的两棵树,同时将新得到的树加入F中。
  4. 重复步骤2)和3),直至F中只剩下一棵树为止
    在这里插入图片描述
  • 从上述构造过程中可以看出哈夫曼树具有如下特点:
  1. 每个初始结点最终都成为叶结点,且权值越小的结点到根结点的路径长度越大。
  2. 构造过程中共新建了n-1个结点(双分支结点),因此哈夫曼树的结点总数为2n-1。
  3. 每次构造都选择2棵树作为新结点的孩子,因此哈夫曼树中不存在度为1的结点。
  4. 哈夫曼树并不唯一,但WPL必然相同且为最优。

哈夫曼树的结点类型

typedef struct
{
	char data; //结点值
	double weight; //权重
	int parent; //双亲结点
	int lchild; //左孩子结点
	int rchild; //右孩子结点
}HTNode;

哈夫曼树构造的算法

void CreatHT(HTNode ht[], int n0)
{
	int i, k, lnode, rnode;
	double min1, min2;
	for (i = 0; i < 2 * n0 - 1; i++) ht[i].parent = ht[i].lchild = ht[i].rchild = -1; //所有结点的相关域置为-1
	for (i = n0; i < 2 * n0 - 2; i++) //构造哈夫曼树的n0-1个分支节点
	{
		min1 = min2 = 32676; //lnode和rnode为最小权重的两个结点位置
		lnode = rnode = -1;
		for(k=0;k<i-1;k++) //在ht[0..i-1]中找权值最小的两个结点
			if (ht[k].parent == -1) //只在尚未构造的二叉树的结点中查找
			{
				if (ht[k].weight < min1)
				{
					min2 = min1;
					rnode = lnode;
					min1 = ht[k].weight;
					rnode = k;
				}
				else if (ht[k].weight < min2)
				{
					min2 = ht[k].weight;
					rnode = k;
				}
			}
		ht[i].weight = ht[lnode].weight + ht[rnode].weight;
		ht[i].lchild = lnode;
		ht[i].rchild = rnode;
		ht[lnode].parent = i; //ht[i]作为双亲结点
		ht[rnode].parent = i;
	}
}

哈夫曼编码

  • 在数据通信中,若对每个字符用相等长度的二进制位表示,称这种编码方式为固定长度编码。若允许对不同字符用不等长的二进制位表示,则这种编码方式称为可变长度编码
  • 可变长度编码比固定长度编码要好得多,其特点是对频率高的字符赋以短编码,而对频率较低的字符则赋以较长一些的编码,从而可以使字符的平均编码长度减短,起到压缩数据的效果。
  • 哈夫曼编码是一种被广泛应用而且非常有效的数据压缩编码。若没有一个编码是另一个编码的前缀,则称这样的编码为前缀编码。
  • 由哈夫曼树得到哈夫曼编码是很自然的过程。首先,将每个出现的字符当作一个独立的结点,其权值为它出现的频度(或次数),构造出对应的哈夫曼树。显然,所有字符结点都出现在叶结点中。我们可将字符的编码解释为从根至该字符的路径上边标记的序列,其中边标记为0表示“转向左孩子”,标记为1表示“转向右孩子”。
  • !注意:0和1究竟是表示左子树还是右子树没有明确规定。左、右孩子结点的顺序是任意的,所以构造出的哈夫曼树并不唯一,但各哈夫曼树的带权路径长度WPL相同且为最优。此外,如有若千权值相同的结点,则构造出的哈夫曼树更可能不同,但WPL必然相同且是最优的。

哈夫曼编码的结点类型

typedef struct
{
	char cd[N]; //存放当前结点的哈夫曼编码
	int start; //表示cd[start..n0]部分是哈夫曼码
}HCode;

哈夫曼编码的算法

void CreateCode(HTNode ht[], HCode hcd[], int n0)
{
	int i, f, c;
	HCode hc;
	for (i = 0; i < n0; i++) //根据哈夫曼树求哈夫曼编码
	{
		hc.start = n0;
		c = i;
		f = ht[i].parent;
		while (f != -1) //循环直到无双亲结点,即到达根结点
		{
			if (ht[f].lchild == c) hc.cd[hc.start--] = '0'; //当前结点是双亲结点的左孩子
			else hc.cd[hc.start--] = '1'; //当前结点是双亲结点的右孩子
			c = f;
			f = ht[f].parent; //再对双亲结点进行同样的操作
		}
		hc.start++; //start指向哈夫曼编码最开始的字符
		hcd[i] = hc;
	}
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值