再写 Huffman_Tree

本文详细介绍了构建霍夫曼树的过程,并通过实例展示了如何使用霍夫曼树进行哈夫曼编码。文章包括代码实现、树的构造、遍历观察以及编码输出等关键步骤。

之前写的huffman树的代码

这次的只是对建树过程的代码做了一点改变,对比代码一看就知道了

tree.h

#include<iostream> using namespace std; //栈模版 template<class T> class My_stack; template<class T> class Node //结点类 { private: T data; Node<T> *next; public: Node() { next=NULL; } Node(T d) { data=d; next=NULL; } friend My_stack<T>; }; template<class T> class My_stack { private: Node<T> *head; public: My_stack() { head=new Node<T>(); } ~My_stack() { clean(); delete head; } bool empty() const { return (head->next==0); } void push(T d) //入栈 { Node<T> *p=new Node<T>(d); p->next=head->next; head->next=p; } T top() //返回栈顶元素 { if(empty()) { cout<<"stack is empty."<<endl; exit(1); } Node<T> *p=head->next; T temp=p->data; return temp; } void pop() //弹出栈顶元素 { Node<T> *p=head->next; head->next=p->next; delete p; } void clean() //清除整个栈 { Node<T> *p=head->next; while(p) { head->next=p->next; delete p; head->next=p; } } }; //放叶子结点信息的结构体 struct node { char name; int data; }; class HuffmanTree; class HuffmanNode { char name; int data; int parent,lchild,rchild; friend HuffmanTree; }; class HuffmanTree { public: void Create_HuffmanTree(HuffmanNode *&HT,node w[],int n) { //用w申请的数组暂时保存权值(暂时设定全都大于0) if(n<1) { cout<<"参数不合理"<<endl; return ; } int m=2*n-1; //n个叶子结点的哈夫曼树总共有2*n-1个结点 HT=new HuffmanNode[m]; //分配m个huffman结点 int i,k,lnode,rnode; double min1,min2; //存放两个最小的结点值 for(i=0;i<n;i++) { HT[i].name=w[i].name; HT[i].data=w[i].data; HT[i].lchild=-1; HT[i].rchild=-1; HT[i].parent=-1; } for(int j=i;j<m;j++) { HT[j].name='#'; HT[j].data=0; HT[j].lchild=-1; HT[j].rchild=-1; HT[j].parent=-1; } //结点初始化完成,下面开始构造huffman树 for(i=n;i<2*n-1;i++) { min1=min2=32767; lnode=rnode=-1; for(k=0;k<=i-1;k++) { if(-1 == HT[k].parent) { if(HT[k].data<min1) { min2=min1; rnode=lnode; min1=HT[k].data; lnode=k; } else if(HT[k].data<min2) { min2=HT[k].data; rnode=k; } } } HT[i].data=min1+min2; HT[i].lchild=lnode; HT[i].rchild=rnode; HT[lnode].parent=i; HT[rnode].parent=i; } cout<<"____huffman树构造完成____"<<endl; } void Traverse(HuffmanNode *HT,int n) //观察 { int m=2*n-1; for(int i=m-1;i>=n;i--) { cout<<HT[i].data<<"={"; cout<<HT[HT[i].lchild].data<<','; cout<<HT[HT[i].rchild].data<<'}'; cout<<endl; } } void Huffman_Coding(HuffmanNode *HT,int n) { HuffmanNode *q; int f,temp,p; My_stack<int> s; for(int i=0;i<n;i++) { q=HT; f=i; p=q[f].parent; while(p!=-1) { if(q[p].lchild==f) temp=0; else temp=1; s.push(temp); f=p; p=q[p].parent; } cout<<"第"<<i+1<<"个字符 "<<HT[i].name<<" 的huffman编码为:"; while(!s.empty()) { cout<<s.top()<<" "; s.pop(); } cout<<endl; } } };
main.cpp

#include"tree.h" int main() { int n; node *p; cout<<"输入要编码的字母数目:"; cin>>n; p=new node[n]; cout<<"输入各个字母的名字和频率权值大小:"; for(int i=0;i<n;i++) cin>>p[i].name>>p[i].data; cout<<"____构造huffman树____"<<endl; HuffmanTree tree; HuffmanNode *HT; tree.Create_HuffmanTree(HT,p,n); cout<<"huffman树的结构为:"<<endl; tree.Traverse(HT,n); cout<<endl; cout<<"____Huffman_Coding_____"<<endl; tree.Huffman_Coding(HT,n); return 0; }

测试数据:

输入要编码的字母数目:8 输入各个字母的名字和频率权值大小: a 7 b 19 c 2 d 6 e 32 f 3 g 21 h 10 ____构造huffman树____ ____huffman树构造完成____ huffman树的结构为: 100={40,60} 60={28,32} 40={19,21} 28={11,17} 17={7,10} 11={5,6} 5={2,3} ____Huffman_Coding_____ 第1个字符 a 的huffman编码为:1 0 1 0 第2个字符 b 的huffman编码为:0 0 第3个字符 c 的huffman编码为:1 0 0 0 0 第4个字符 d 的huffman编码为:1 0 0 1 第5个字符 e 的huffman编码为:1 1 第6个字符 f 的huffman编码为:1 0 0 0 1 第7个字符 g 的huffman编码为:0 1 第8个字符 h 的huffman编码为:1 0 1 1 Press any key to continue

""" 对图像哈夫曼编码/解码,大致步骤是根据哈夫曼编码灰度图像,对图像进行编码,并保存成二级制的文件,保存到指定文件中;读取哈夫曼编码的文件,解码成图像,与原图像对比。 """ import cv2 from queue import PriorityQueue import numpy as np import math import struct #哈夫曼树的节点类 class HuffmanNode(object): def __init__(self, value, key=None, symbol='', left_child=None, right_child=None): ''' 初始化哈夫曼树的节点 :param value: 节点的值,i.e. 元素出现的频率 :param key: 节点代表的元素,非叶子节点为None :param symbol: 节点的哈夫曼编码,初始化必须为空字符串 :param left_child: 左子节点 :param right_child: 右子节点 ''' self.left_child = left_child self.right_child = right_child self.value = value self.key = key assert symbol == '' self.symbol = symbol def __eq__(self, other): ''' 用于比较两个HuffmanNode的大小,等于号,根据value的值比较 :param other: :return: ''' return self.value == other.value def __gt__(self, other): ''' 用于比较两个HuffmanNode的大小,大于号,根据value的值比较 :param other: :return: ''' return self.value > other.value def __lt__(self, other): ''' 用于比较两个HuffmanNode的大小,小于号,根据value的值比较 :param other: :return: ''' return self.value < other.value def createTree(hist_dict: dict) -> HuffmanNode: ''' 构造哈夫曼树 可以写一个HuffmanTree的类 :param hist_dict: 图像的直方图,dict = {pixel_value: count} :return: HuffmanNode, 哈夫曼树的根节点 ''' # 借助优先级队列实现直方图频率的排序,取出和插入元素很方便 q = PriorityQueue() # 根据传入的像素值和频率字典构造哈夫曼节点并放入队列中 for k, v in hist_dict.items(): # 这里放入的都是之后哈夫曼树的叶子节点,key都是各自的元素 q.put(HuffmanNode(value=v, key=k)) # 判断条件,直到队列中只剩下一个根节点 while q.qsize() > 1: # 取出两个最小的哈夫曼节点,队列中这两个节点就不在了 l_freq, r_freq = q.get(), q.get() # 增加他们的父节点,父节点值为这两个哈夫曼节点的和,但是没有key值;左子节点是较小的,右子节点是较大的 node = HuffmanNode(value=l_freq.value + r_freq.value, left_child=l_freq, right_child=r_freq) # 把构造的父节点放在队列中,继续排序和取放、构造其他的节点 q.put(node) # 队列中只剩下根节点了,返回根节点 return q.get() def walkTree_VLR(root_node: HuffmanNode, symbol=''): ''' 前序遍历一个哈夫曼树,同时得到每个元素(叶子节点)的编码,保存到全局的Huffman_encode_dict :param root_node: 哈夫曼树的根节点 :param symbol: 用于对哈夫曼树上的节点进行编码,递归的时候用到,为'0'或'1' :return: None ''' # 为了不增加变量复制的成本,直接使用一个dict类型的全局变量保存每个元素对应的哈夫曼编码 global Huffman_encode_dict # 判断节点是不是HuffmanNode,因为叶子节点的子节点是None if isinstance(root_node, HuffmanNode): # 编码操作,改变每个子树的根节点的哈夫曼编码,根据遍历过程是逐渐增加编码长度到完整的 root_node.symbol += symbol # 判断是否走到了叶子节点,叶子节点的key!=None if root_node.key != None: # 记录叶子节点的编码到全局的dict中 Huffman_encode_dict[root_node.key] = root_node.symbol # 访问左子树,左子树在此根节点基础上赋值'0' walkTree_VLR(root_node.left_child, symbol=root_node.symbol + '0') # 访问右子树,右子树在此根节点基础上赋值'1' walkTree_VLR(root_node.right_child, symbol=root_node.symbol + '1') return def encodeImage(src_img: np.ndarray, encode_dict: dict): ''' 用已知的编码字典对图像进行编码 :param src_img: 原始图像数据,必须是一个向量 :param encode_dict: 编码字典,dict={element:code} :return: 图像编码后的字符串,字符串中只包含'0'和'1' ''' img_encode = "" assert len(src_img.shape) == 1, '`src_img` must be a vector' ########## Begin ########## """ 任务1 用已知的编码字典对图像进行编码,对图像中的每个像素点进行编码,返回值为img_encode,返回的图像编码后的字符串, 字符串中只包含'0'和'1',用for循环进行遍历 """ ########## End ########## return img_encode def writeBinImage(img_encode: str, huffman_file: str): ''' 把编码后的二进制图像数据写入到文件中 :param img_encode: 图像编码字符串,只包含'0'和'1' :param huffman_file: 要写入的图像编码数据文件的路径 :return: ''' # 文件要以二进制打开 with open(huffman_file, 'wb') as f: # 每8个bit组成一个byte for i in range(0, len(img_encode), 8): # 把这一个字节的数据根据二进制翻译为十进制的数字 img_encode_dec = int(img_encode[i:i + 8], 2) # 把这一个字节的十进制数据打包为一个unsigned char,大端(可省略) img_encode_bin = struct.pack('>B', img_encode_dec) # 写入这一个字节数据 f.write(img_encode_bin) def readBinImage(huffman_file: str, img_encode_len: int): ''' 从二进制的编码文件读取数据,得到原来的编码信息,为只包含'0'和'1'的字符串 :param huffman_file: 保存的编码文件 :param img_encode_len: 原始编码的长度,必须要给出,否则最后一个字节对不上 :return: str,只包含'0'和'1'的编码字符串 ''' code_bin_str = "" with open(huffman_file, 'rb') as f: # 从文件读取二进制数据 content = f.read() # 从二进制数据解包到十进制数据,所有数据组成的是tuple code_dec_tuple = struct.unpack('>' + 'B' * len(content), content) for code_dec in code_dec_tuple: # 通过bin把解压的十进制数据翻译为二进制的字符串,并填充为8位,否则会丢失高位的0 # 0 -> bin() -> '0b0' -> [2:] -> '0' -> zfill(8) -> '00000000' code_bin_str += bin(code_dec)[2:].zfill(8) # 由于原始的编码最后可能不足8位,保存到一个字节的时候会在高位自动填充0,读取的时候需要去掉填充的0,否则读取出的编码会比原来的编码长 # 计算读取的编码字符串与原始编码字符串长度的差,差出现在读取的编码字符串的最后一个字节,去掉高位的相应数量的0就可以 len_diff = len(code_bin_str) - img_encode_len # 在读取的编码字符串最后8位去掉高位的多余的0 code_bin_str = code_bin_str[:-8] + code_bin_str[-(8 - len_diff):] return code_bin_str def decodeHuffman(img_encode: str, huffman_tree_root: HuffmanNode): ''' 根据哈夫曼树对编码数据进行解码 :param img_encode: 哈夫曼编码数据,只包含'0'和'1'的字符串 :param huffman_tree_root: 对应的哈夫曼树,根节点 :return: 原始图像数据展开的向量 ''' img_src_val_list = [] # 从根节点开始访问 root_node = huffman_tree_root # 每次访问都要使用一位编码 for code in img_encode: # 如果编码是'0',说明应该走到左子树 if code == '0': root_node = root_node.left_child # 如果编码是'1',说明应该走到右子树 elif code == '1': root_node = root_node.right_child # 只有叶子节点的key才不是None,判断当前走到的节点是不是叶子节点 if root_node.key != None: # 如果是叶子节点,则记录这个节点的key,也就是哪个原始数据的元素 img_src_val_list.append(root_node.key) # 访问到叶子节点之后,下一次应该从整个数的根节点开始访问了 root_node = huffman_tree_root return np.asarray(img_src_val_list) def decodeHuffmanByDict(img_encode: str, encode_dict: dict): ''' 另外一种解码策略是先遍历一遍哈夫曼树得到所有叶子节点编码对应的元素,可以保存在字典中,再对字符串的子串逐个与字典的键进行比对,就得到相应的元素是什么。 用C语言也可以这么做。 这里不再对哈夫曼树重新遍历了,因为之前已经遍历过,所以直接使用之前记录的编码字典就可以。 :param img_encode: 哈夫曼编码数据,只包含'0'和'1'的字符串 :param encode_dict: 编码字典dict={element:code} :return: 原始图像数据展开的向量 ''' img_src_val_list = [] decode_dict = {} ########## Begin ########## """ 任务2 对字典进行遍历,构造一个key-value互换的字典,i.e. dict={code:element},后边方便使用 """ ########## End ########## # s用来记录当前字符串的访问位置,相当于一个指针 s = 0 # 只要没有访问到最后 while len(img_encode) > s + 1: # 遍历字典中每一个键code for k in decode_dict.keys(): # 如果当前的code字符串与编码字符串前k个字符相同,k表示code字符串的长度,那么就可以确定这k个编码对应的元素是什么 if k == img_encode[s:s + len(k)]: img_src_val_list.append(decode_dict[k]) # 指针移动k个单位 s += len(k) # 如果已经找到了相应的编码了,就可以找下一个了 break return np.asarray(img_src_val_list) if __name__ == '__main__': src_img_path = 'project/step2/img_data/ex_1.jpg' # 即使原图像是灰度图,也需要加入GRAYSCALE标志 src_img = cv2.imread(src_img_path, cv2.IMREAD_GRAYSCALE) # 记录原始图像的尺寸,后续还原图像要用到 src_img_w, src_img_h = src_img.shape # 把图像展开成一个行向量 src_img_ravel = src_img.ravel() # {pixel_value:count},保存原始图像每个像素对应出现的次数,也就是直方图 hist_dict = {} # {pixel_value:code},在函数中作为全局变量用到了 Huffman_encode_dict = {} # 得到原始图像的直方图,出现次数为0的元素(像素值)没有加入 for p in src_img_ravel: if p not in hist_dict: hist_dict[p] = 1 else: hist_dict[p] += 1 ########## Begin ########## """ 任务3. 构造哈夫曼树,返回值为huffman_root_node,调用构造哈夫曼树的代码 """ ########## End ########## # 遍历哈夫曼树,并得到每个元素的编码,保存到Huffman_encode_dict walkTree_VLR(huffman_root_node) ########## Begin ########## """ 任务4 根据编码字典编码原始图像得到二进制编码数据字符串,需要调用encodeImage()函数进行将图像二进制编码 """ ########## End ########## # 把二进制编码数据字符串写入到文件中,后缀为bin writeBinImage(img_encode, src_img_path.replace('.jpg', '.bin')) # 读取编码的文件,得到二进制编码数据字符串 img_read_code = readBinImage(src_img_path.replace('.jpg', '.bin'), len(img_encode)) # 解码二进制编码数据字符串,得到原始图像展开的向量 # 这是根据哈夫曼树进行解码的方式 img_src_val_array = decodeHuffman(img_read_code, huffman_root_node) # 确保解码的数据与原始数据大小一致 assert len(img_src_val_array) == src_img_w * src_img_h # 恢复原始二维图像 img_decode = np.reshape(img_src_val_array, [src_img_w, src_img_h]) print(img_decode) print(np.sum(img_decode)) cv2.imwrite('project/step2/stu_img/decode_ex_1.jpg', img_decode)
11-22
任务描述 本关任务要求完成哈夫曼树的相关基本操作,分别实现哈夫曼树的创建、编码等基本操作。 相关知识 哈夫曼树结点Node具有5个存储域data、weight、parent、left和right。 data: 存储结点数据。 weight: 存储权值。 parent: 存储父亲结点地址 left: 存储左儿子结点地址。 right: 存储右儿子结点地址。 哈夫曼码结构体具有2个域data、code。 data: 存储结点数据。 code: 存储哈夫曼编码。 链栈涉及的主要操作如下: 构造函数创建哈夫曼树:创建哈夫曼树。该操作函数具体定义如下: huffmanTree( ) 析构函数释放哈夫曼树存储空间:释放哈夫曼树中所有结点的存储空间。该操作函数具体定义如下: ~huffmanTree( ) 创建哈夫曼树:依据结点数据和权值创建哈夫曼树。该操作函数具体定义如下: void createHuffmanTree(const T *d,const int *w) 输出哈夫曼编码:遍历哈夫曼树打印哈夫曼编码。该操作函数具体定义如下: void printHuffmanCode( ) 挑选权值最小元素:挑选当前权值最小元素用于组建哈夫曼树。该操作函数具体定义如下: void selectMin( int m, int &p ) 编程要求 本关任务是实现step1:哈夫曼树-实现/huffmanTree.h中的createHuffmanTree、printHuffmanCode、selectMin等操作。 请注意,为了能够更好的比对输出结构,在参考教材代码的同时,请删除所有提示信息的输出! 基本操作具体要求如下: * createHuffmanTree:创建哈夫曼树。在创建哈夫曼树时权值较小则做为左儿子 * printHuffmanCode:输出哈夫曼编码。左枝为0右枝为1. * selectMin:挑选权值最小元素。 * 输入输出格式请参见后续测试样例 注意:在实现本关任务的基本操作函数时,可调用以完成的其他操作。 测试说明 本关的测试文件是step1哈夫曼树-实现/main.cpp,负责对实现的代码进行测试。具体代码,可以切换平台代码文件查看。 注意:step1哈夫曼树-实现/main.cpp的代码不能被修改。 以下是平台对step1哈夫曼树-实现/main.cpp的测试样例: 样例输入: 7 //编码字符个数 z h a n g b o //编码字符 8 10 2 5 13 1 4 //编码字符权值 样例输出 z:00 h:01 a:10101 n:100 g:11 b:10100 o:1011 // 和书上代码差别:创建的huffman树左边权值小,右边权值大 #include<iostream> #ifndef _HUFFMAN_TREE_H_ #define _HUFFMAN_TREE_H_ using namespace std; template <class T> class huffmanTree{ private: struct Node{ T data; // 结点数据域 int weight; // 结点的权值 int parent, left, right; // 双亲及左右孩子的下标 Node() { // 构造函数 weight = parent = left = right = 0; }; }; struct huffmanCode { T data; string code; // 保存data的哈夫曼编码 huffmanCode(){ code=""; } // 构造函数 }; Node *hfTree; // 顺序结构,保存huffmanhuffmanCode *hfCode; // 顺序结构,保存结点的huffman编码 int size; // 叶结点数 void selectMin(int m,int& p); // 选出当前集合中的最小元素 public: huffmanTree(int initSize); // 构造函数 ~huffmanTree() {delete [] hfTree;delete []hfCode;} // 析构函数 void createHuffmanTree(const T *d, const int *w);//创建哈夫曼树 void huffmanEncoding(); // 获取huffman编码 void printHuffmanCode(); // 输出huffman编码 }; template <class T> huffmanTree<T>::huffmanTree(int initSize){ size = initSize; // size为初始集合中的结点数 hfTree = new Node[2*size]; hfCode = new huffmanCode[size]; } template <class T> void huffmanTree<T>::createHuffmanTree(const T *d, const int *w) { // 请在这里补充代码,完成本关任务 /********** Begin *********/ /********** End **********/ } template<class T> void huffmanTree<T>::selectMin(int m,int& p){ // 请在这里补充代码,完成本关任务 /********** Begin *********/ … /********** End **********/ } template<class T> void huffmanTree<T>::printHuffmanCode(){ for (int i=0; i< size; i++) cout<< hfCode[i].data <<':' << hfCode[i].code << endl; } #endif
最新发布
12-03
Delphi 12.3 作为一款面向 Windows 平台的集成开发环境,由 Embarcadero Technologies 负责其持续演进。该环境以 Object Pascal 语言为核心,并依托 Visual Component Library(VCL)框架,广泛应用于各类桌面软件、数据库系统及企业级解决方案的开发。在此生态中,Excel4Delphi 作为一个重要的社区开源项目,致力于搭建 Delphi 与 Microsoft Excel 之间的高效桥梁,使开发者能够在自研程序中直接调用 Excel 的文档处理、工作表管理、单元格操作及宏执行等功能。 该项目以库文件与组件包的形式提供,开发者将其集成至 Delphi 工程后,即可通过封装良好的接口实现对 Excel 的编程控制。具体功能涵盖创建与编辑工作簿、格式化单元格、批量导入导出数据,乃至执行内置公式与宏指令等高级操作。这一机制显著降低了在财务分析、报表自动生成、数据整理等场景中实现 Excel 功能集成的技术门槛,使开发者无需深入掌握 COM 编程或 Excel 底层 API 即可完成复杂任务。 使用 Excel4Delphi 需具备基础的 Delphi 编程知识,并对 Excel 对象模型有一定理解。实践中需注意不同 Excel 版本间的兼容性,并严格遵循项目文档进行环境配置与依赖部署。此外,操作过程中应遵循文件访问的最佳实践,例如确保目标文件未被独占锁定,并实施完整的异常处理机制,以防数据损毁或程序意外中断。 该项目的持续维护依赖于 Delphi 开发者社区的集体贡献,通过定期更新以适配新版开发环境与 Office 套件,并修复已发现的问题。对于需要深度融合 Excel 功能的 Delphi 应用而言,Excel4Delphi 提供了经过充分测试的可靠代码基础,使开发团队能更专注于业务逻辑与用户体验的优化,从而提升整体开发效率与软件质量。 资源来源于网络分享,仅用于学习交流使用,请勿用于商业,如有侵权请联系我删除!
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值