408数据结构学习笔记——树与二叉树的应用——哈夫曼树和哈夫曼编码、并查集

简介: 408数据结构学习笔记——树与二叉树的应用——哈夫曼树和哈夫曼编码、并查集

1.哈夫曼树和哈夫曼编码

1.1.哈夫曼树的定义

权值:树的结点带有的某种意义的数值

带权路径长度:从树的跟该结点的路径长度(经过的边数与该点的权值的乘积

树的带权路径长度(WPL):所有叶结点的带权路径长度之和(算法题考过)

66308140907d46c4a72ca4f7ee405deb.png哈夫曼树:n个结点形成的所有二叉树中,wpl值最低的树(也称为最优二叉树)

1.2.哈夫曼树的构造

  1. 找到当前权值最低的两个结点,形成一个新的树,其根节点权值为两点之和
  2. 在森林中将两个结点删除,并将该树加入。
  3. 循环进行1、2,直到森林中仅有一棵树为止

6320e1a77d264759a8bee685b26faa5a.png

  1. cd结点权值最低,形成新的结点
  2. b(cd)结点权值最低,形成新的结点
  3. a(b(cd))权值最低,形成新的结点

1.3.哈夫曼树的性质

  1. 哈夫曼树不唯一
  2. 权值越小的结点路径长度越大
  3. 哈夫曼树的结点总数为2n - 1
  1. 哈夫曼树构造前,结点总数为n
  2. 构造哈夫曼树需要执行n - 1次合并,每次合并新增一个结点,即新增n - 1个结点
  3. n + n - 1 = 2n - 1
  1. 哈夫曼树不存在度为1的点(要么为0(叶子结点),要么为2(分支节点))

1.4.哈夫曼编码

前缀编码:没有一个编码是另一个编码的前缀

f5281caa54d8484f84dc38f8c42a13ad.png

由哈夫曼树构造哈夫曼编码,0向左子树,1向右子树(01指向左还是右没有明确规定)

哈夫曼编码的作用是可以不采用固定长度编码方式,从而压缩数据

2.并查集

2.1.并查集的基本操作

并查集:将元素划分为若干个互不相干的子集

并:将两个集合归并为一个集合(让一个树成为另一个树的子树)

查:确定一个指定元素的集合(查看其根节点是哪一个)

采用双亲表示法:并和查仅需更改或查看指向其双亲结点的伪指针

1.初始化:将每个结点的值设置为-1,表示每个结点都是一颗单独的树,即n个子集

99d7ab75e7a74f19863a794e4f8f89c4.png

#define MAXSIZE 100
int UFsets[MAXSIZE];    //集合数组
//初始化并查集
void Initial(int S[]){
    //-1表示集合中(森林)每个元素都是独立的个体(树)
    for (int i = 0; i < MAXSIZE; i++) S[i] = -1;
}

2.查(最坏O(n)):找该结点的根节点

并(O(1)):①两个结点是根节点:直接修改root2的值为root1的下标

②两个结点是非根节点:通过查操作,分别找到root1和root2的根节点,然后将root2的根节点的值为root1的根节点的下标

//查找,传入数组和数组下标
int Find(int S[], int x){
    //循环遍历查找其根节点,根节点的值为-1
    while(S[x] >= 0) x = S[x];
    return x;
}
//并,两个集合合并为一个
void Union(int S[], int root1, int root2){
    //两个元素是同一个集合
    if (root1 == root2) return;
    //将root2的根节点改为root1
    S[root2] = root1;
}

2.2.并查集的Union操作的优化(小树合并到大树)

1.如果每次都是大树合并到小树,则树的高度每次都会 + 1,导致并查集的使用效率降低→find最坏时间复杂度O(n)

2.每次都让让小树合并到大树,可以延缓树的增高→find最坏时间复杂度O(log n)

优化:根节点的数据的绝对值等于其结点总数,结点总数更大的树为大树,将更小的树并入大树

//并优化
void Union(int S[], int root1, int root2){
    //root1的树结点更多,相较下root2为大树
    if (root1 < root2){
        S[root1] += S[root2];    //root1的结点数更新
        S[root2] = root1;    //root2并入root1
    }
    else{    //root2为大树
        S[root2] += S[root1];    //root2的结点数更新
        S[root1] = root2;    //root1并入root2
    }
}

根节点代表的是该树的总结点个数,因此合并时,可以通过两个树根节点的值相加的方式得到合并后的树的总结点个数

39fa7021fa7b4bd18da566c1213cef62.png

2.3.并查集的Find操作的优化(压缩路径)

第一轮循环:和之前find操作一样,找到该结点的根节点

第二轮循环:将该结点find操作经历的每个结点挂到根节点上(修改路径上每个结点的值为根节点的下标)

find操作可以进一步优化为O(α(n)) << O(log n)α(n)通常为常数级

fb1d7a265c2345059644ec1bb4270b86.png

int Find(int S[], int x){
    int root = x;
    //向上循环遍历树,找到其根节点
    while(S[root] >= 0) root = S[root];
    while (x != root){
        int temp = S[x];
        S[x] = root;    //将x的双亲结点改为root
        x = temp;
    }
    return root;
}

3.王道课后题e5f4708cbb24421481f086fe555f607b.png

1.(AB)CDEF→((AB)C)EF→((AB)C)(DE)F→(((AB)C)(DE))F→((((AB)C)(DE))F)

  1. 最坏情况下,两个表的元素是交错的,即m1<n1<m2<n2....<m<n,除了最后一个元素不用比较以外,所有元素都要比较一次
  2. 第一次合并:10 + 35 -1 = 44
  3. 第二次合并:45 + 40 - 1 = 84
  4. 第三次合并:50 + 60 - 1 = 109
  5. 第四次合并:110 + 85 - 1 = 194
  6. 第五次合并:195 + 200 - 1 = 394

2..每次找当前最少的两个表合并成为一个新表

ceb526c2cb7243288ba3258df3557a79.png

  1. 二叉树
  2.   a.遍历当前字符集的每个字符

         b.如果当前扫描到的字符为0,则进入其左子树;如果当前扫描到的字符为1,则进入其右子树,直到碰到叶子结点,取出叶子结点所存储的数据

  1. 扫描字符集

      1.如果在还没有遍历完当前字符集时就到了叶子结点,则不具有前缀特性

       2.如果在遍历完当前字符集,并没有创建新的结点,则不具有前缀特性

       3.1和2都没有发生,则具有前缀特性






相关文章
|
存储 C++
【C++数据结构——树】哈夫曼树(头歌实践教学平台习题) 【合集】
【数据结构——树】哈夫曼树(头歌实践教学平台习题)【合集】目录 任务描述 相关知识 测试说明 我的通关代码: 测试结果:任务描述 本关任务:编写一个程序构建哈夫曼树和生成哈夫曼编码。 相关知识 为了完成本关任务,你需要掌握: 1.如何构建哈夫曼树, 2.如何生成哈夫曼编码。 测试说明 平台会对你编写的代码进行测试: 测试输入: 1192677541518462450242195190181174157138124123 (用户分别输入所列单词的频度) 预
637 14
【C++数据结构——树】哈夫曼树(头歌实践教学平台习题) 【合集】
|
存储 JSON NoSQL
redis基本数据结构(String,Hash,Set,List,SortedSet)【学习笔记】
这篇文章是关于Redis基本数据结构的学习笔记,包括了String、Hash、Set、List和SortedSet的介绍和常用命令。文章解释了每种数据结构的特点和使用场景,并通过命令示例演示了如何在Redis中操作这些数据结构。此外,还提供了一些练习示例,帮助读者更好地理解和应用这些数据结构。
redis基本数据结构(String,Hash,Set,List,SortedSet)【学习笔记】
|
存储 C语言
数据结构基础详解(C语言): 树与二叉树的应用_哈夫曼树与哈夫曼曼编码_并查集_二叉排序树_平衡二叉树
本文详细介绍了树与二叉树的应用,涵盖哈夫曼树与哈夫曼编码、并查集以及二叉排序树等内容。首先讲解了哈夫曼树的构造方法及其在数据压缩中的应用;接着介绍了并查集的基本概念、存储结构及优化方法;随后探讨了二叉排序树的定义、查找、插入和删除操作;最后阐述了平衡二叉树的概念及其在保证树平衡状态下的插入和删除操作。通过本文,读者可以全面了解树与二叉树在实际问题中的应用技巧和优化策略。
740 2
|
机器学习/深度学习 存储
数据结构学习记录——哈夫曼树(什么是哈夫曼树、哈夫曼树的定义、哈夫曼树的构造、哈夫曼树的特点、哈夫曼编码)
数据结构学习记录——哈夫曼树(什么是哈夫曼树、哈夫曼树的定义、哈夫曼树的构造、哈夫曼树的特点、哈夫曼编码)
788 1
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
1347 10
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
441 59
|
存储 C语言 C++
【C++数据结构——栈与队列】顺序栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现顺序栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 1.初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储
1172 77
|
编译器 C语言 C++
栈区的非法访问导致的死循环(x64)
这段内容主要分析了一段C语言代码在VS2022中形成死循环的原因,涉及栈区内存布局和数组越界问题。代码中`arr[15]`越界访问,修改了变量`i`的值,导致`for`循环条件始终为真,形成死循环。原因是VS2022栈区从低地址到高地址分配内存,`arr`数组与`i`相邻,`arr[15]`恰好覆盖`i`的地址。而在VS2019中,栈区先分配高地址再分配低地址,因此相同代码表现不同。这说明编译器对栈区内存分配顺序的实现差异会导致程序行为不一致,需避免数组越界以确保代码健壮性。
281 0
栈区的非法访问导致的死循环(x64)
232.用栈实现队列,225. 用队列实现栈
在232题中,通过两个栈(`stIn`和`stOut`)模拟队列的先入先出(FIFO)行为。`push`操作将元素压入`stIn`,`pop`和`peek`操作则通过将`stIn`的元素转移到`stOut`来实现队列的顺序访问。 225题则是利用单个队列(`que`)模拟栈的后入先出(LIFO)特性。通过多次调整队列头部元素的位置,确保弹出顺序符合栈的要求。`top`操作直接返回队列尾部元素,`empty`判断队列是否为空。 两题均仅使用基础数据结构操作,展示了栈与队列之间的转换逻辑。

热门文章

最新文章