数据结构——特殊的二叉树

1.普通二叉树

1.1 定义

二叉树的定义是一种递归定义,它的特点是每个结点至多只存在两棵子树(即二叉树中不存在度大于2的结点),且左右子树有先后顺序之分。一棵二叉树要么是空树,要么是一个根结点加上左、右子树组成的二叉树。其中,左右子树也都是二叉树。

二叉树有5种基本的状态:
在这里插入图片描述

1.2 性质

二叉树的常见性质:

  • 二叉树的第 i i i 层最多有 2 i − 1 2^{i-1} 2i1 个结点
  • 深度为 h h h 的二叉树至多有 2 h − 1 2^h-1 2h1 个结点
  • 任一棵二叉树 T,其叶子结点数为 n 0 n_0 n0, 度为2的结点数为 n 1 n_1 n1,则 n 0 = n 1 + 1 n_0 = n_1 + 1 n0=n1+1
2.满二叉树

一棵深度为 h h h,且有 2 h − 1 2^h-1 2h1 个结点的二叉树称为满二叉树。如下图,特点是每一层上的结点都是最大结点数
满二叉树
假设堆满二叉树的结点进行连续编号,则深度为 h h h,结点树为 n n n 的二叉树,若其结点编号与一个深度为 h h h 的满二叉树一一对应,则称之为 完全二叉树,如下图:
完全二叉树

完全二叉树的特点:

  • 叶子节点只可能在层次最大的两层出现
  • 右子树的最大层次为 l l l ,则左子树的最大层次为 l l l 或者 l + 1 l+1 l+1
  • n n n 个结点的完全二叉树深度 h = ⌊ l o g 2 n ⌋ + 1 h = \left \lfloor log_2n \right \rfloor+1 h=log2n+1
  • 对于有 n n n 个结点的完全二叉树,按照层序编号,从0开始,则
    • 编号为i的左孩子为2i+1,右孩子为2i+2
    • 编号为i的,父亲结点为 ⌊ ( i − 1 ) / 2 ⌋ \left \lfloor (i-1)/2 \right \rfloor (i1)/2

即以下均不是完全二叉树
非完全二叉树

3.哈夫曼树

哈夫曼树(Huffman),又称最优树,是一类带权路径长度最短的树

3.1 最优二叉树

相关概念

  • 路径长度,树中一个结点到另一个结点间的分支构成两个结点间的路径,分支数目称作路径长度;
  • 树的路径长度,树根到每一个结点的路径长度之和。
    之前的完全二叉树就是给定结点数,路径长度最短的二叉树(有点被压缩的感觉)。
  • 考虑带有权值的结点,
    结点的带权路径长度为该结点到根节点的路径长度与结点上权值的乘积,
    树的带权路径长度为树中所有叶子结点的带权路径长度之和,即 W P L = ∑ k = 1 n w k l k WPL = \sum_{k=1}^n w_k l_k WPL=k=1nwklk
  • 最优二叉树
    在这里插入图片描述

构造算法

假设有4个权值,7,5,2,4,则下面三棵二叉树来说,树©的带权路径长度最短,为我们需要的哈夫曼树。
哈夫曼树示例
则我们有个直观的感受:权值越大的需要离根结点越近,权值越小的可以离根结点远一些,这样,最后总的带权路径长度会更小。 所以,我们有如下最优二叉树的构造算法:
哈夫曼树构造算法

如下图给出了我们树©的构造过程:
在这里插入图片描述

3.2 应用——哈夫曼编码

引出

在信息通信中,我们需要给通信字符编码,且通常有2点原则:

  • 前缀编码,任一字符的编码都不是另一个字符的编码的前缀,这样解码无歧义性
  • 最短编码,我们希望经常出现的字符编码短,不经常出现的字符的编码可以长一点

而我们可以用二叉树进行编码,例如对于四个字符A, B, C,D,使用二叉树可以如下编码:
在这里插入图片描述
其肯定是前缀编码,要使得最短编码,即对于每种字符的出现次数 w i w_i wi 和其编码长度 l i l_i li 的乘积总和最小,即 ∑ k = 1 n w k l k \sum_{k=1}^n w_k l_k k=1nwklk 最小。这表明,我们需要根据字符的出现频率构造一棵哈夫曼树。

构造

由于哈夫曼树中没有度为1的结点,则根据二叉树的性质,有 n n n 个叶子结点的哈夫曼树的总结点为 n + n − 1 = 2 n − 1 n + n - 1 = 2n-1 n+n1=2n1,则可以存储在长度为 2 n − 1 2n-1 2n1 的一维数组中。

构造完成后,

  • 编码:可以求从叶子结点到根节点的一条路径
  • 解码:从根节点出发到叶子结点的一条路径
  • 也可以反过来

所以,对于每个结点,我们需要知道其孩子和父亲结点,则我们引入下列数据结构:

结点定义
typedef struct {
	int weight;
	int left, right, parent;
}Hnode,*Htree;

哈夫曼编码本
typedef string* HuffmanCode;

编码过程

void HuffmanCoding(Htree& HT, HuffmanCode& HC, int* w, int n) {
	/*
	* w为结点权重,n
	* HT 为哈夫曼树的结点数组表示,2n+1
	* HC 为结点的编码映射,n
	*/
	if (n <= 1) return;
	int m = 2 * n - 1;
	HT = new Hnode[m + 1]; // 0号单元未用,表示初始结点的根结点,代表NULL
	// 初始化各叶子结点
	int i;
	for (i = 0; i < n; i++) 
		HT[i + 1] = { w[i],0,0,0};
	// 初始化要构造的结点
	for (; i < m; i++)
		HT[i + 1] = { 0,0,0,0 };
	// 开始构造内部结点
	for (i = n + 1; i <= m; i++) {
		// 挑选两个权重最小的结点,且它们的父亲结点为0,表示还未加入树
		int s1 = -1, s2 = -1;
		int min1 = INF, min2 = INF;
		for (int j = 1; j < i; j++) {
			if (HT[j].parent != 0) continue;
			if (HT[j].weight < min1) {
				min2 = min1;		   s2 = s1;  // 先更新次小
				min1 = HT[j].weight;   s1 = j;
			}
			else if (HT[j].weight < min2) {
				min2 = HT[j].weight;  s2 = j;
			}
		}
		// 将两个次小的结点加入
		HT[s1].parent = HT[s2].parent = i;
		HT[i].left = s1;  HT[i].right = s2;
		HT[i].weight = HT[s1].weight + HT[s2].weight;
		if (DEBUG) printf("孩子结点%d,%d 父亲结点%d\n", HT[s1].weight, HT[s2].weight, HT[i].weight);
	}
	// 从叶子结点开始构造哈夫曼编码
	HC = new string[n + 1];
	for (i = 1; i <= n; i++) {
		int j, f; // 当前结点和父节点
		for (j = i, f = HT[j].parent; f; j = f, f = HT[j].parent) {
			if (HT[f].left == j)  
				HC[i] += '0';// 左子树,编码为0
			else
				HC[i] += '1'; // 右子树,编码为1
		}
	}
}

解码过程

-- 从根到叶子结点正向解码 --
int HuffmanDecoding(Htree HT, HuffmanCode HC,int c,int n) { //解码
	/*
	 * 输入码位置c(或者编码)
	 * 返回解码出来的位置,如果相等表明正确
	*/
	string code = HC[c];
	int index = 2 * n - 1; // 代表最后是那个根结点
	for (int i = code.size() - 1; i >= 0; i--) {
		if (code[i] == '0') // 向左
			index = HT[index].left;
		else  // 向右
			index = HT[index].right;
	}
	return index;
}

测试用例

int main() {
	Htree T;
	HuffmanCode C;
	int n = 8;
	int w[] = {5,29,7,8,14,23,3,11};
	HuffmanCoding(T, C, w, n);
	// 打印编码结果
	for (int i = 1; i <= n; i++) {
		printf("#%d: 权重%d,编码%s\n", i, w[i - 1], C[i].c_str());
	}
	// 解码
	for (int c = 1; c <= n; c++) {
		printf("第%d位置的编码%s,解码%d\n", c, C[c].c_str(), HuffmanDecoding(T, C, c, n));
	}
	return 0;
}
4.二叉排序树和平衡二叉树

二叉搜索树

5.红黑树

参考文献

《数据结构 C语言描述》 严蔚敏著

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值