数据结构(浙大慕课):哈夫曼树、集合及运算

哈夫曼树

定义: 最优二叉树或哈夫曼树即为 WPL 最小的二叉树。
在这里插入图片描述
哈夫曼树的构造: 每次把权值最小的两棵二叉树合并

构造代码:

typedef struct TreeNode *HuffmanTree;
struct TreeNode{
	int Weight;
	HuffmanTree Left, Right;
}
HuffmanTree Huffman( MinHeap H )
{ /* 假设H->Size个权值已经存在H->Elements[]->Weight里 */
	int i; HuffmanTree T;
	BuildMinHeap(H); /*将H->Elements[]按权值调整为最小堆*/
	for (i = 1; i < H->Size; i++) { /*做H->Size-1次合并*/
		T = malloc( sizeof( struct TreeNode) ); /*建立新结点*/
		T->Left = DeleteMin(H);
		/*从最小堆中删除一个结点,作为新T的左子结点*/
		T->Right = DeleteMin(H);
		/*从最小堆中删除一个结点,作为新T的右子结点*/
		T->Weight = T->Left->Weight+T->Right->Weight;
		/*计算新权值*/
		Insert( H, T ); /*将新T插入最小堆*/
	}
	T = DeleteMin(H);
	return T;
}

特点:

  • 没有度为 1 的结点;
  • n 个叶子结点的哈夫曼树共有 2n - 1 个结点;
  • 哈夫曼树的任意非叶结点的左右子树交换后仍是哈夫曼树;
  • 对于同一组权值 {w1,w2,…,wn},可能存在不同构的哈夫曼树,但WPL一定相同,例子如下。
    在这里插入图片描述

哈夫曼编码

不等长编码: 出现频率高的字符用的编码短些,出现频率低的字符则编码长些。

用哈夫曼进行不等长编码: 可以避免编码的二义性,因为每个字符所在的结点均为叶子结点,不可能出现在别的字符的路径上,就不会出现二义性,即任何字符的编码都不是另一字符编码的前缀。

编码的二义性: 有些字符的编码可能是另一些字符编码的前缀。例如: a 编码为 1,b 编码为 0,c 编码为10。那倘若现在给定编码为10,需要进行解码,就会出现二义性。10可以解码为 ab 和 c。

如下图所示,用二叉树进行编码:左边的二叉树编码为普通的不等长编码,WPL较大右边为哈夫曼树(也算是一种二叉树)编码,可以保证达到最小的WPL,即编码代价最小。
在这里插入图片描述

哈夫曼树编码规则: 每次把权值最小的两个字母所在二叉树进行合并
在这里插入图片描述

集合及运算

集合的结构表示:
将一个集合里的元素构造成一棵树,用双亲表示法,即孩子指向双亲(之前学的树都是父亲指向左右儿子)。树再用数组进行表示。
数据描述:
采用数组存储形式。Parent处为 -1 则表示根节点;非负数表示双亲节点的下标。可以将Parent为负数进行改进,比如用 -7 表示这个集合里的元素个数。表示如下:
在这里插入图片描述
集合运算
(1)查找某个元素所在集合(用根节点表示)

int Find( SetType S[ ], ElementType X )
{ /* 在数组S中查找值为X的元素所属的集合 */
	/* MaxSize是全局变量,为数组S的最大长度 */
	int i;
	for ( i=0; i < MaxSize && S[i].Data != X; i++) ;//这一步时间复杂度很高T(N) = O(N^2)
	if( i >= MaxSize ) return -1; /* 未找到X,返回-1 */
	for( ; S[i].Parent >= 0; i = S[i].Parent ) ;
	return i; /* 找到X所属集合,返回树根结点在数组S中的下标 */
}

并运算
思路:
1、分别找到X1和X2两个元素所在集合树的根结点
2、如果它们不同根,则将其中一个根结点的父结点指针设置成另一个根结点的数组下标。

void Union( SetType S[ ], ElementType X1, ElementType X2 )
{
 int Root1, Root2;
 Root1 = Find(S, X1);
 Root2 = Find(S, X2);
 if( Root1 != Root2 )S[Root2].Parent = Root1;	//即当 x1 和 x2 不属于同一子集时,才需要合并。
}

改进: 为了改善合并以后的查找性能,可以进行按秩归并:
1、比规模:将规模小的集合合并到规模相对大的集合中。所以将根节点的 Parent 改成可以表示集合元素个数的负数。
2、比高度:将高度小的集合合并到高度相对大的集合中。所以将根节点的 Parent 改成可以表示集合的高度。
在这里插入图片描述

集合的应用

集合的简化表示:
将集合表示成数组形式存储,将集合的元素用下标的标号表示,而对应标号的数组内存储父节点的编号。可以减少存储空间,也可以减少查找操作的时间复杂度。
例如:
在这里插入图片描述
集合简化前操作:

typedef struct{
	ElementType Data;
	int Parent;
}SetType;

int Find( SetType S[ ], ElementType X )
{ /* 在数组S中查找值为X的元素所属的集合 */
	/* MaxSize是全局变量,为数组S的最大长度 */
	int i;
	for ( i=0; i < MaxSize && S[i].Data != X; i++) ;	//这一步时间复杂度很高!!!T(N) = O(N^2)
	if( i >= MaxSize ) return -1; /* 未找到X,返回-1 */
	for( ; S[i].Parent >= 0; i = S[i].Parent ) ;
	return i; /* 找到X所属集合,返回树根结点在数组S中的下标 */
}

集合的简化表示:

typedef int ElementType; /*默认元素可以用非负整数表示*/
typedef int SetName; /*默认用根结点的下标作为集合名称*/
typedef ElementType SetType[MaxSize];

SetName Find( SetType S, ElementType X ){ /* 默认集合元素全部初始化为-1 */
	for ( ; S[X]>=0; X=S[X] ) ;//这里是默认 X 一定在某个集合里面,如果严谨一点,则要多判定一下X是否在集合中
	return X;//返回的是要查找的元素 X 所在集合的父节点
} 
void Union( SetType S, SetName Root1, SetName Root2 ){ /* 这里默认Root1和Root2是不同集合的根结点 */
	S[Root2] = Root1;	// 这里就是进行最简单的并集操作,没有进行按秩归并和路径压缩。
	// 可能最终会退化成单向链表,查找的时间复杂度很大等等后果。
} 

集合的 按秩归并路径压缩 操作具体参考PTA的 05-树8 File Transfer
按秩归并:
根据树高进行归并: 将矮树贴到高树上
在这里插入图片描述
根据树的规模进行归并: 将规模小的树贴到规模大的树上
在这里插入图片描述

路径压缩: 用路径压缩,查找操作的时间复杂度能从 O( Nlog2N) 降为 O( Clog2N) ,其中C为常数。
注意! 路径压缩通常与按规模归并使用更方便。因为如果按树高归并的话,路径压缩会破坏树的高度,导致后续的归并变得比较复杂,还需要重新计算树高,而且计算复杂。但是路径压缩不会破坏树的规模,后续可以正常归并。
在这里插入图片描述

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值