算法学习笔记(7.6)-贪心算法(霍夫曼编码)

news2024/9/9 1:13:39

目录

1.什么是霍夫曼树

2.霍夫曼树的构造过程

3.霍夫曼编码

3.1具体的作用-频率统计

##实战题目


1.什么是霍夫曼树

给定N个权值作为N个叶子结点,构造一棵二叉树,若该树的带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree)。哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近。

相关知识:
1、路径和路径长度
在一棵树中,从一个结点往下可以达到的孩子或孙子结点之间的通路,称为路径。
通路中分支的数目称为路径长度。若规定根结点的层数为1,则从根结点到第L层结点的路径长度为L-1。
2、结点的权及带权路径长度
若将树中结点赋给一个有着某种含义的数值,则这个数值称为该结点的权。
结点的带权路径长度为:从根结点到该结点之间的路径长度与该结点的权的乘积。
3、树的带权路径长度
树的带权路径长度规定为所有叶子结点的带权路径长度之和,记为WPL。

2.霍夫曼树的构造过程

哈夫曼树的节点个数:
哈夫曼树是一种满二叉树,这意味着除了叶节点外,每个节点都有两个子节点。对于 (N) 个唯哈夫曼树的节点个数与输入的唯一字符数量有关。设字符集合的大小为 (N)(即有 (N) 个唯一字符),则哈夫曼树的节点数最小值和最大值如下:一字符,会有 (N) 个叶节点,每个叶节点代表一个字符。

最小值
最小节点数:当输入的字符集合大小为 (N) 时,最小的哈夫曼树(也是理论上的最小值)会有 (2N - 1) 个节点。这是因为在构建哈夫曼树时,每次合并两个节点会创建一个新的内部节点,而最终只剩下一个节点(根节点)没有被合并。这意味着有(N-1) 次合并操作,每次合并操作增加一个新的内部节点,因此有 (N-1) 个内部节点和 (N) 个叶节点,总共 (2N - 1) 个节点。

最大值
对于哈夫曼树,最大节点数的概念实际上并不适用,因为哈夫曼树的构建是基于字符的频率的,其目的是为了创建一个尽可能高效的编码方案。每个唯一字符都会成为树的一个叶节点,而内部节点的数量总是 (N-1),所以节点总数固定为 (2N - 1)。不会有比这更多的节点数,因为这是根据哈夫曼算法的定义和工作方式所固有的。

结论
对于 (N) 个唯一字符,哈夫曼树的节点数总是 (2N - 1),这里没有最大值概念,因为节点总数是固定的,只要给定了唯一字符的数量。

哈夫曼树的构造过程:

假设有n个权值,则构造出的哈夫曼树有n个叶子结点。 n个权值分别设为 w1、w2、…、wn,则哈夫曼树的构造规则为:

(1) 将w1、w2、…,wn看成是有n 棵树的森林(每棵树仅有一个结点);

(2) 在森林中选出两个根结点的权值最小的树合并,作为一棵新树的左、右子树,且新树的根结点权值为其左、右子树根结点权值之和;

(3)从森林中删除选取的两棵树,并将新树加入森林;

(4)重复(2)、(3)步,直到森林中只剩一棵树为止,该树即为所求得的哈夫曼树。

3.霍夫曼编码

哈夫曼编码是一种压缩编码的编码算法,是基于哈夫曼树的一种编码方式。哈夫曼树又称为带权路径长度最短的二叉树。

哈夫曼编码跟 ASCII 编码有什么区别?

ASCII 编码是对照ASCII 表进行的编码,每一个字符符号都有对应的编码,其编码长度是固定的。而哈夫曼编码对于不同字符的出现频率其使用的编码是不一样的。其会对频率较高的字符使用较短的编码,频率低的字符使用较高的编码。这样保证总体使用的编码长度会更少,从而实现到了数据压缩的目的。

3.1具体的作用-频率统计

哈夫曼树是一个带权的二叉树,而在哈夫曼编码中,字符的出现频率就是字符的权重。因此要根据字符的频率放入优先队列中进行排序。然后根据这些字符构建一棵哈夫曼树

##代码实现

//c++代码实现
#include <iostream>
#include <vector>
using namespace std;
#include <algorithm> // 包含<algorithm>以使用reverse函数
#define MAXCODELEN 100
#define MAXHAFF 256 // 为了简化,假设最多处理256个不同的字符
#define MAXWEIGHT 10000

typedef struct Haffman {
	//权重 
    int weight;
    //字符 
    char ch;
    //父节点,左儿子,右儿子 
    int parent, leftChild, rightChild;
} HaffmanNode;

typedef struct Code {
    int code[MAXCODELEN];
    int start;
} HaffmanCode;

HaffmanNode haffman[MAXHAFF * 2 - 1]; // 扩展数组大小以容纳所有可能的内部节点
HaffmanCode code[MAXHAFF];

//构造哈夫曼树的过程 
void buildHaffman(int all) {
	//对每个节点进行初始化 
	//哈夫曼节点的个数总为 2 * all - 1 
    for (int i = 0; i < 2 * all - 1; i++) {
        haffman[i].weight = 0;
        haffman[i].parent = -1;
        haffman[i].leftChild = -1;
        haffman[i].rightChild = -1;
    }
	
    cout << "请输入需要哈夫曼编码的字符和权重大小" << endl;
    for (int i = 0; i < all; i++) {
    	//获取每个字符及权重 
        cout << "请分别输入第" << i + 1 << "个哈夫曼字符和权重:" << endl;
        cin >> haffman[i].ch >> haffman[i].weight;
    }

    //用来存储两个最小权值的节点以及权重 
	int x1, x2, w1, w2;
    //all个节点,只需要合并all - 1 次,循环all - 1 次即可 
    for (int i = 0; i < all - 1; i++) {
    	//初始化成不可能的值 
        x1 = x2 = -1;
        w1 = w2 = MAXWEIGHT;
        //遍历每一个可能的节点,包括初始的叶节点和已经创建的内部节点。 
        for (int j = 0; j < all + i; j++) {
            if (haffman[j].parent == -1) {
            	//如果当前节点没有父节点 ,即还没有加入到haffman树中,即考虑这个节点 
                if (haffman[j].weight < w1) {
                    w2 = w1;
                    x2 = x1;
                    //利用x1,和w1来接受第一个节点和权值 
                    w1 = haffman[j].weight;
                    x1 = j;
                    
                }
				//利用x2,和w2来接受第二个节点和权值 
				else if (haffman[j].weight < w2) {
                    w2 = haffman[j].weight;
                    x2 = j;
                }
            }
        }
        //循环遍历一遍后,x1,x2,w1,w2即是最小两个节点的选择和权值 
		//将其加入到huffman树中 
        haffman[all + i].leftChild = x1;
        haffman[all + i].rightChild = x2;
        haffman[all + i].weight = w1 + w2;
        haffman[x1].parent = all + i;
        haffman[x2].parent = all + i;
    }
}

void printCode(int all) {
	//遍历每一个字符-我们所输入的 
    for (int i = 0; i < all; i++) {
    	//用来存储每一个字符的haffman编码 
        vector<int> hCode;
        int current = i;
        int parent = haffman[current].parent;
        //通过回溯到根节点,然后确保haffman编码的完整性 
        while (parent != -1) {
        	//左孩子 '0' 
            if (haffman[parent].leftChild == current) {
                hCode.push_back(0);
            }
			//右孩子 '1' 
			else {
                hCode.push_back(1);
            }
            current = parent;
            parent = haffman[current].parent;
        }
        reverse(hCode.begin(), hCode.end());
        cout << haffman[i].ch << ": Huffman Code is: ";
        for (int j = 0 ; j < hCode.size() ; j++)
        {
			cout << hCode[j] ;
		}	
//        for (int bit : hCode) {
//            cout << bit;
//        }
        cout << endl;
    }
}

int main() {
    cout << "请输入有多少个哈夫曼字符:" << endl;
    int all = 0;
    cin >> all;

    if (all <= 0) {
        cout << "你输入的个数有误" << endl;
        return -1;
    }
    buildHaffman(all);
    printCode(all);
    return 0;
}

#python代码
import heapq
from collections import defaultdict, Counter


# 定义树的节点
class Node:
    def __init__(self, char, freq):
        self.char = char
        self.freq = freq
        self.left = None
        self.right = None

    # 为了让节点能在优先队列中按频率排序,定义比较方法
    def __lt__(self, other):
        return self.freq < other.freq


# 构建哈夫曼树
def build_huffman_tree(text):
    # 统计字符频率
    frequency = Counter(text)

    # 创建优先队列
    priority_queue = [Node(char, freq) for char, freq in frequency.items()]
    heapq.heapify(priority_queue)

    # 循环直到队列中只剩一个元素
    while len(priority_queue) > 1:
        # 弹出两个最小元素
        left = heapq.heappop(priority_queue)
        right = heapq.heappop(priority_queue)

        # 创建新的内部节点,将两个节点作为子节点
        merged = Node(None, left.freq + right.freq)
        merged.left = left
        merged.right = right

        # 将新节点加入优先队列
        heapq.heappush(priority_queue, merged)

    # 队列中剩下的元素就是树的根节点
    return priority_queue[0]


# 生成哈夫曼编码
def generate_codes(node, prefix="", code={}):
    if node is not None:
        if node.char is not None:
            code[node.char] = prefix
        generate_codes(node.left, prefix + "0", code)
        generate_codes(node.right, prefix + "1", code)
    return code


# 编码文本
def encode(text, codes):
    return ''.join(codes[char] for char in text)


# 解码文本
def decode(encoded_text, root):
    decoded_text = ""
    current_node = root
    for bit in encoded_text:
        if bit == '0':
            current_node = current_node.left
        else:
            current_node = current_node.right
        if current_node.char is not None:
            decoded_text += current_node.char
            current_node = root
    return decoded_text


# 主函数
# def main():
#     text = "this is an example for huffman encoding"
#     root = build_huffman_tree(text)
#     codes = generate_codes(root)
#     encoded_text = encode(text, codes)
#     decoded_text = decode(encoded_text, root)
#
#     print(f"Original text: {text}")
#     print(f"Encoded text: {encoded_text}")
#     print(f"Decoded text: {decoded_text}")


# if __name__ == "__main__":
#     main()

# 主函数
def main():
    text = "this is an example for huffman encoding"
    root = build_huffman_tree(text)
    codes = generate_codes(root)

    # 打印每个字符的哈夫曼编码
    print("Huffman Codes for each character:")
    for char, code in codes.items():
        print(f"'{char}': {code}")

    encoded_text = encode(text, codes)
    decoded_text = decode(encoded_text, root)

    print(f"\nOriginal text: {text}")
    print(f"Encoded text: {encoded_text}")
    print(f"Decoded text: {decoded_text}")

if __name__ == "__main__":
    main()

##实战题目

3531. 哈夫曼树 - AcWing题库

##示例代码

//c++代码
#include <iostream>
#include <queue>
using namespace std;

int n, x, ans;
priority_queue <int, vector <int>, greater <int> > q;

int main ()
{
    cin >> n;
    for (int i = 1; i <= n; i ++)
    {
        cin >> x, q.push (x); // 加入二叉堆(优先队列)
    }
    while (-- n)
    {
        x = q.top (), q.pop (), x += q.top (), q.pop (); // 取出两个堆顶(当前最小值)合并
        ans += x, q.push (x); // 统计答案,在放进堆中
    }
    cout << ans;
    return 0;
}

 148. 合并果子 - AcWing题库

 149. 荷马史诗 - AcWing题库

##代码示例

//c++代码示例
#include<bits/stdc++.h>
using namespace std ;

typedef long long LL ;
typedef pair<LL,int> PLI ;
const int N = 1e5 + 10 ;

int n , m ;
priority_queue<PLI,vector<PLI>,greater<PLI>> p ;

int main()
{
    cin >> n >> m ;
    for (int i = 1 ; i <= n ; i++)
    {
        LL w ;
        cin >> w ;
        p.push({w,0}) ;
    }
    //这是因为在每次合并操作中,会从堆中取出 m 个节点并合并为一个新节点,这意味着总节点数将减少 m−1。
    //为了最终能够合并到只剩一个节点,初始的节点数 n 与合并的分支数 m 必须满足 n - 1 是 m - 1 的倍数。
    while ((n - 1) % (m - 1))
    {
        p.push({0ll,0}) ;
        n++ ;
    }
    LL ans = 0 ; 
    while (p.size() > 1)
    {
        LL sum = 0 ;
        int depth = 0 ;
        for (int i = 1 ; i <= m ; i++)
        {
            sum += p.top().first ;
            depth = max(depth, p.top().second) ;
            p.pop() ;
        }
        ans += sum ;
        p.push({sum,depth+1}) ;
    }
    cout << ans << endl << p.top().second ;
    return 0 ;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1807760.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【数据结构与算法】使用数组实现栈:原理、步骤与应用

&#x1f493; 博客主页&#xff1a;倔强的石头的CSDN主页 &#x1f4dd;Gitee主页&#xff1a;倔强的石头的gitee主页 ⏩ 文章专栏&#xff1a;《数据结构与算法》 期待您的关注 ​ 目录 一、引言 &#x1f384;栈&#xff08;Stack&#xff09;是什么&#xff1f; &#x1…

Python私教张大鹏 Vue3整合AntDesignVue之Breadcrumb 面包屑

显示当前页面在系统层级结构中的位置&#xff0c;并能向上返回。 何时使用 当系统拥有超过两级以上的层级结构时&#xff1b; 当需要告知用户『你在哪里』时&#xff1b; 当需要向上导航的功能时。 案例&#xff1a;面包屑导航基本使用 核心代码&#xff1a; <template…

【数据结构(邓俊辉)学习笔记】图07——最短路径

文章目录 0. 概述1. 问题2. 最短路径2.1 最短路径树2.1.1 单调性2.1.2 歧义性2.1. 3 无环性 2.2 Dijkstra 算法2.2.1 贪心迭代2.2.2 实现2.2.3 实例2.2.4 复杂度 0. 概述 学习下最短路径和Dijistra算法 1. 问题 给定带权网络G (V, E)&#xff0c;以及源点&#xff08;source…

【NoSQL数据库】Redis命令、持久化、主从复制

Redis命令、持久化、主从复制 redis配置 Redis命令、持久化、主从复制Redis数据类型redis数据库常用命令redis多数据库常用命令1、多数据库间切换2、多数据库间移动数据3、清除数据库内数据 key命令1、keys 命令2、判断键值是否存在exists3、删除当前数据库的指定key del4、获取…

基于pytorch_lightning测试resnet18不同激活方式在CIFAR10数据集上的精度

基于pytorch_lightning测试resnet18不同激活方式在CIFAR10数据集上的精度 一.曲线1.train_acc2.val_acc3.train_loss4.lr 二.代码 本文介绍了如何基于pytorch_lightning测试resnet18不同激活方式在CIFAR10数据集上的精度 特别说明: 1.NoActive:没有任何激活函数 2.SparseActiva…

【LeetCode 第 401 场周赛】K秒后第 N 个元素的值

文章目录 1. K秒后第 N 个元素的值&#x1f197; 1. K秒后第 N 个元素的值&#x1f197; 题目链接&#x1f517; &#x1f427;解题思路&#xff1a; 前缀和 小规律&#x1f34e; &#x1f34e; 从上图观察可知&#xff0c;规律一目了然&#xff0c;arr[i] arr[i] 对上一…

超越出身与学府:揭秘成功者共有的七大特质

在当今多元化的世界里&#xff0c;个人成功的故事如同繁星点点&#xff0c;照亮了无数追梦者的前行之路。新东方创始人俞敏洪先生曾深刻地指出&#xff0c;真正的成功并不取决于家庭背景的显赫与否&#xff0c;也不在于就读大学的名气大小&#xff0c;而是深深植根于个人内在的…

知识图谱的应用---智慧农业

文章目录 智慧农业典型应用 智慧农业 智慧农业通过生产领域的智能化、经营领域的差异性以及服务领域的全方位信息服务&#xff0c;推动农业产业链改造升级;实现农业精细化、高效化与绿色化&#xff0c;保障农产品安全、农业竞争力提升和农业可持续发展。目前&#xff0c;我国的…

战略引领下的成功产品开发之路

在当今竞争激烈的市场环境中&#xff0c;成功的产品开发不仅仅依赖于创意和技术的卓越&#xff0c;更需要战略性的规划和执行。本文将探讨战略在成功产品开发中的重要性&#xff0c;并结合实际案例&#xff0c;分析如何在战略的指引下&#xff0c;将创意转化为商业化的产品或服…

nginx mirror流量镜像详细介绍以及实战示例

nginx mirror流量镜像详细介绍以及实战示例 1.nginx mirror作用2.nginx安装3.修改配置3.1.nginx.conf3.2.conf.d目录下添加default.conf配置文件3.3.nginx配置注意事项3.3.nginx重启 4.测试 1.nginx mirror作用 为了便于排查问题&#xff0c;可能希望线上的请求能够同步到测试…

TMS320F280049学习3:烧录

TMS320F280049学习3&#xff1a;烧录 文章目录 TMS320F280049学习3&#xff1a;烧录前言一、烧录RAM二、烧录FLASH总结 前言 DSP的烧录分为两种&#xff0c;一种是将程序烧录到RAM中&#xff0c;一种是烧录到FLASH中&#xff0c;烧录ARM中的程序&#xff0c;只要未掉电&#x…

Linux驱动应用编程(四)IIC(获取BMP180温度/气压数据)

本文目录 一、基础1. 查看开发板手册&#xff0c;获取可用IIC总线2. 挂载从机&#xff0c;查看从机地址。3. 查看BMP180手册&#xff0c;使用命令读/写某寄存器值。4. 查看BMP180手册通信流程。 二、IIC常用API1. iic数据包/报2. ioctl函数 三、数据包如何被处理四、代码编写流…

配网终端通讯管理板,稳控装置通讯管理卡,铁路信号通讯管理卡

配网终端通讯管理板 ● 配网终端通讯管理板 ● ARM Cortex™-A5 &#xff0c;533MHz ● 256MB RAM&#xff0c;512MB FLASH 配网终端通讯管理板 ARM Cortex™-A5 &#xff0c;533MHz 256MB RAM&#xff0c;512MB FLASH 2x10/100/1000Mbps LAN&#xff08;RJ45&#xff09; 6x…

FastAPI系列 4 -路由管理APIRouter

FastAPI系列 -路由管理APIRouter 文章目录 FastAPI系列 -路由管理APIRouter一、前言二、APIRouter使用示例1、功能拆分2、users、books模块开发3、FastAPI主体 三、运行结果 一、前言 未来的py开发者请上座&#xff0c;在使用python做为后端开发一个应用程序或 Web API&#x…

MySQL数据库---LIMIT、EXPLAIN详解

分页查询 语法 select _column,_column from _table [where Clause] [limit N][offset M]select * : 返回所有记录limit N : 返回 N 条记录offset M : 跳过 M 条记录, 默认 M0, 单独使用似乎不起作用 limit N,M : 相当于 limit M offset N , 从第 N 条记录开始, 返回 M 条记录…

贪心算法学习三

例题一 解法&#xff08;贪⼼&#xff09;&#xff1a; 贪⼼策略&#xff1a; ⽤尽可能多的字符去构造回⽂串&#xff1a; a. 如果字符出现偶数个&#xff0c;那么全部都可以⽤来构造回⽂串&#xff1b; b. 如果字符出现奇数个&#xff0c;减去⼀个之后&#xff0c;剩下的…

对象存储OSS 客户端签名直传的安全风险和解决方法

1. 前言 阿里云对象存储OSS&#xff08;Object Storage Service&#xff09;是一款海量、安全、低成本、高可靠的云存储服务&#xff0c;可提供99.9999999999%&#xff08;12个9&#xff09;的数据持久性&#xff0c;99.995%的数据可用性。多种存储类型供选择&#xff0c;全面…

AXI Quad SPI IP核中命令的使用

1 双通道SPI和混合内存模式下支持的常用命令 对于配置中Mode设置为Dual且Slave Device设置为Mixed的情况&#xff0c;IP核支持表3-1中列出的命令。这些命令在Winbond、Micron和Spansion内存设备上具有相同的命令、地址和数据行为。 某些命令&#xff0c;如fast read、dual I/…

产品创新:驱动企业增长的核心动力

在当今快速变化的市场环境中&#xff0c;产品创新已成为企业生存和发展的关键。产品创新不仅涉及全新产品或服务的开发&#xff0c;也包括对现有产品或服务的持续改进和优化。本文将深入探讨产品创新的定义、重要性以及如何通过创新驱动企业增长&#xff0c;并结合实际案例进行…

每位比特币人都终将成为一个国际主义者

原创 | 刘教链 周末BTC&#xff08;比特币&#xff09;趁势向着30日均线回归&#xff0c;现于69k一线悬停。7万刀以下加仓的机会窗口&#xff0c;和那蹉跎一生的岁月一样&#xff0c;过一天少一天&#xff0c;在每个纠结和拧巴的日子里&#xff0c;在软弱和彷徨的等待中&#x…