第五章 树和二叉树(下)【哈夫曼树、并查集】

1. 哈夫曼树

1.1 哈夫曼树定义

相关概念:

  • 结点的权:有某种现实含义的数值(如:表示结点的重要性等)
  • 结点的带权路径长度:从树的根到该结点的路径长度(经过的边数)与该结点上权值的乘积。
  • 树的带权路径长度:树中所有叶结点的带权路径长度之和 (WPL,Weighted Path Length)。

哈夫曼树的定义:在含有n个带权叶结点的二叉树中,其中带权路径长度(WPL) 最小的二叉树,也称最优二叉树。

1.2 哈夫曼树的构造(重点)

1.2.1 夫曼树的构造算法

给定n个权值分别为w1, W2,..., w,的结点,构造哈夫曼树的算法描述如下:

  1. 将这n个结点分别作为n棵仅含一个结点的二叉树,构成森林F。
  2. 构造一个新结点,从F中选取两棵根结点权值最小的树作为新结点的左、右子树(左右顺序任意),并且将新     结点的权值置为左、右子树上根结点的权值之和。
  3. 从F中删除刚才选出的两棵树,同时将新得到的树加入F中
  4. 重复步骤2和3,直至F中只剩下一棵树为止。

1.2.2 构造哈夫曼树的注意事项:

  • 每个初始结点最终都成为叶结点,且权值越小的结点到根结点的路径长度越大
  • 哈夫曼树的结点总数为2n - 1
  • 哈夫曼树中不存在度为1的结点。
  • 哈夫曼树并不唯一,但WPL必然相同且为最优

1.3 哈曼编码(重点)

相关概念:

  • 固定长度编码:每个字符用相等长度的二进制位表示
  • 可变长度编码:允许对不同字符用不等长的二进制位表示
  • 前缀编码:若没有一个编码是另一个编码的前缀,则称这样的编码为前缀编码,前缀码解码无歧义

由哈夫曼树得到哈夫曼编码--字符集中的每个字符作为一个叶子结点,各个字符出现的频度作为结点的权值,根据之前介绍的方法构造哈夫曼树 。

哈夫曼树不唯 一,因此哈夫 曼编码不唯一

哈夫曼编码的压缩率:(原始文件大小-压缩文件大小)/ 原始文件大小

2. 并查集

 2.1 基本思想

同一子集中的 各个元素,组织成一棵树,用互不相交的树,表示多个“集合”、

  • 如何“查”到一个元素到底属于哪一个集合?—— 从指定元素出发,一路向北,找到根节点
  • 如何判断两个元素是否属于同一个集合?—— 分别查到两个元素的根,判断根节点是否相同即可
  • 如何把两个集合“并”为一个集合?—— 让一棵树成为另一棵树的子树即可

2.2  “并查集”的存储结构

存储方法为双亲表示法。用一个数组 S[ ] 即可表示“集合”关系

集合的两个基本操作——“并”和“查”

  • Find ——“查”操作:确定一个指定元 素所属集合
  • Union ——“并”操作:将两个不相交 的集合合并为一个,,先查到对应节点的根,如果不在一个集合,则把一个根合并到另外一个。

注:并查集(Disjoint Set)是逻辑结构——集合的一种具体实现,只进行”并”和“查”两种基本操作 

#define SIZE 13
 
int UFSets[SIZE];    //集合元素数组
 
//初始化并查集
void Initial(int S[])
{
    for(int i=0; i<SIZE; i++)
        S[i] = -1;
}
 
//Find————查操作,找到x所属集合    最坏时间复杂度:O(h)
int Find(int S[], int x)
{
    while(S[x]>=0)
    {
        x = S[x];    //循环找x的根
    }
    return x;        // 根的S[]小于0
}
 
//Union————并操作,将两个集合合并   时间复杂度:O(1)  全部合并:O(n^2)
void Union(int S[], int index1, int index2)
{
    // 要求index1和index2是不同集合
    int Root1 = find(inde1);
    int Root2 = find(inde1);
    if(Root1 == Root2)  return;
    // 将root2连接到另一个根Root1下面
    S[Root2] = Root1;
}

 2.3 时间复杂度分析

若结点数为n,Find 最坏时间复杂度为 O(n),Union时间复杂度 :O(1)

将n个独立元素通过多次Union合并为一个集合——O(n^2)

2.4 优化思路:

2.4.1 Union优化思路

在每次Union操作构建树的时候,尽可能让树不长高高:

①用根节点的负值表示一棵树的结点总数

②Union操作,让小树合并到大树

 合并前:

合并后:

Union操作优化后,Find 操作最坏时间复杂度 :O(log2n) ,

#define SIZE 13
 
int UFSets[SIZE];    //集合元素数组
 
//初始化并查集
void Initial(int S[])
{
    for(int i=0; i<SIZE; i++)
        S[i] = -1;
}
 
//Find————查操作,找到x所属集合    最坏时间复杂度:O(h)
int Find(int S[], int x)
{
    while(S[x]>=0)
    {
        x = S[x];    //循环找x的根
    }
    return x;        // 根的S[]小于0
}
 
//Union————并操作,将两个集合合并   
void Union(int S[], int index1, int index2)
{
    // 要求index1和index2是不同集合
    int Root1 = find(inde1);
    int Root2 = find(inde1);
    // 要求Root1和Root2是不同集合
    if(Root1 == Root2)  return;
    if (S[Root2]>S[Root1]) {    // Root2结点数更少
        S[Root1] += S[Root2];   // 累加结点总数
        S[Root2] = Root1;       // 小树合并到大树
    } else {
        S[Root2] += S[Root1];   // 累加结点总数
        S[Root1] = Root2;       // 小树合并到大树
    }
}

 2.4.2 Find优化思路压缩路径

先找到根节点,再将查找路径上所有节点都挂到根节点下。

每次 Find 操作,先找根,再 “压缩路径”,可使树的高度不超过O(α(n))。 α(n)是一个增长很缓慢
的函数,对于常见的n值,通常α(n)≤4,因此优化后并查集的Find、Union操作时间开销都很低

#define SIZE 13
 
int UFSets[SIZE];    //集合元素数组
 
//初始化并查集
void Initial(int S[])
{
    for(int i=0; i<SIZE; i++)
        S[i] = -1;
}
 
//Find————查操作,找到x所属集合    最坏时间复杂度:O(h),h<=O(α(n))
int Find(int S[], int x)
{
    int root = x;
    while(S[root]>=0) root = S[root];    //循环找x的根
    while(x !=root)
    {
        int t = S[x];    // t指向x的父结点
        S[x] = root;     // x直接挂靠到根节点下面
        x=t;             // 循环处理x的祖先结点
    }
    return x;        // 返回根节点编号
}
 
//Union————并操作,将两个集合合并   
void Union(int S[], int index1, int index2)
{
    // 要求index1和index2是不同集合
    int Root1 = find(inde1);
    int Root2 = find(inde1);
    if(Root1 == Root2)  return;
    if (S[Root2]>S[Root1]) {    // Root2结点数更少
        S[Root1] += S[Root2];   // 累加结点总数
        S[Root2] = Root1;       // 小树合并到大树
    } else {
        S[Root2] += S[Root1];   // 累加结点总数
        S[Root1] = Root2;       // 小树合并到大树
    }
}

 2.4.3 并查集的优化前后对比

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值