DS二叉树--赫夫曼树的构建与编码(含代码框架)


一、DS二叉树–赫夫曼树的构建与编码(含代码框架)

1.题目

给定n个权值,根据这些权值构造huffman树,并进行huffman编码

参考课本算法,注意数组访问是从位置1开始

要求:赫夫曼的构建中,默认左孩子权值不大于右孩子权值
在这里插入图片描述

2.代码

#include<iostream>
#include<string>
#include<cstring>
using namespace std;

const int MaxW = 9999999;  // 假设结点权值不超过9999999
// 定义huffman树结点类
class HuffNode
{
public:
	int weight;     // 权值
	int parent;     // 父结点下标
	int leftchild;  // 左孩子下标
	int rightchild; // 右孩子下标
};
// 定义赫夫曼树类
class HuffMan
{
private:
	void MakeTree();    // 建树,私有函数,被公有函数调用
	void SelectMin(int pos, int* s1, int* s2);  // 从 1 到 pos 的位置找出权值最小的两个结点,
												// 把结点下标存在 s1 和 s2 中
public:
	int len;    // 结点数量
	int lnum;   // 叶子数量
	HuffNode* huffTree; // 赫夫曼树,用数组表示
	string* huffCode;   // 每个字符对应的赫夫曼编码
	void MakeTree(int n, int wt[]); // 公有函数,被主函数main调用
	void Coding();  // 公有函数,被主函数main调用
	void Destroy();
};
// 构建huffman树
void HuffMan::MakeTree(int n, int wt[])
{
	// 参数是叶子结点数量和叶子权值
	// 公有函数,对外接口
	int i;
	lnum = n;
	len = 2 * n - 1;
	huffTree = new HuffNode[2 * n];
	huffCode = new string[lnum + 1];    // 位置从 1 开始计算
	// huffCode实质是个二维字符数组,第 i 行表示第 i 个字符对应的编码
	// 赫夫曼树huffTree初始化
	for (i = 1; i <= n; i++)
	{
		huffTree[i].weight = wt[i - 1]; // 第0号不用,从1开始编号
	}
	for (i = 1; i <= len; i++)
	{
		if (i > n)
		{
			huffTree[i].weight = 0;  // 前n个结点是叶子,已经设置
		}
		huffTree[i].parent = 0;
		huffTree[i].leftchild = 0;
		huffTree[i].rightchild = 0;
	}
	MakeTree();  // 调用私有函数建树
}
void HuffMan::SelectMin(int pos, int* s1, int* s2)
{
	// 找出最小的两个权值的下标
	// 函数采用地址传递的方法,找出两个下标保存在 s1 和 s2 中
	int w1, w2, i;
	w1 = w2 = MaxW;  // 初始化w1和w2为最大值,在比较中会被实际的权值替换
	*s1 = *s2 = 0;
	for (i = 1; i <= pos; i++)
	{
		// 比较过程如下:
		// 如果第 i 个结点的权值小于 w1,且第 i 个结点是未选择的结点,提示:如果第 i 结点未选择,它父亲为 0
		// 把第 w1 和 s1 保存到 w2 和 s2,即原来的第一最小值变成第二最小值
		// 把第 i 结点的权值和下标保存到 w1 和 s1,作为第一最小值
		// 否则,如果第 i 结点的权值小于 w2,且第 i 结点是未选择的结点
		// 把第 i 结点的权值和下标保存到 w2 和 s2,作为第二最小值
		if (w1 > huffTree[i].weight && !huffTree[i].parent) {
			w2 = w1;
			*s2 = *s1;
			w1 = huffTree[i].weight;
			*s1 = i;
		}
		else if (w2 > huffTree[i].weight && !huffTree[i].parent) {
			w2 = huffTree[i].weight;
			*s2 = i;
		}
	}
}
void HuffMan::MakeTree()
{
	// 私有函数,被公有函数调用
	int i, s1, s2;
	for (i = lnum + 1; i <= len; i++)
	{
		SelectMin(i - 1, &s1, &s2);  // 找出两个最小权值的下标放入 s1 和 s2 中
		// 将找出的两棵权值最小的子树合并为一棵子树,过程包括
		// 结点 s1 和结点 s2 的父亲设为 i
		// 结点 i 的左右孩子分别设为 s1 和 s2
		huffTree[s1].parent = huffTree[s2].parent = i;
		huffTree[i].leftchild = s1;
		huffTree[i].rightchild = s2;
		// 结点 i 的权值等于 s1 和 s2 的权值和
		huffTree[i].weight = huffTree[s1].weight + huffTree[s2].weight;
	}
}
// 销毁赫夫曼树
void HuffMan::Destroy()
{
	len = 0;
	lnum = 0;
	delete[]huffTree;
	delete[]huffCode;
}
// 赫夫曼编码
void HuffMan::Coding()
{
	char* cd;
	int i, c, f, start;
	// 求 n 个结点的赫夫曼编码
	cd = new char[lnum];    // 分配求编码的工作空间
	cd[lnum - 1] = '\0';    // 编码结束符
	for (i = 1; i <= lnum; ++i)
	{
		// 逐个字符求赫夫曼编码
		start = lnum - 1;   // 编码结束符位置
		// 参考课本P147算法6.12 HuffmanCoding代码
		for (c = i, f = huffTree[i].parent; f != 0; c = f, f = huffTree[f].parent)
			if (huffTree[f].leftchild == c)
			{
				cd[--start] = '0';
			}
			else
			{
				cd[--start] = '1';
			}
		huffCode[i].assign(&cd[start]); // 把cd中从start到末尾的编码复制到huffCode中
	}
	delete[]cd;    // 释放工作空间
}
// 主函数
int main()
{
	int t, n, i, j;
	int wt[800];
	HuffMan myHuff;
	cin >> t;
	while (t--)
	{
		cin >> n;
		for (j = 0; j < n; j++)
		{
			cin >> wt[j];
		}
		myHuff.MakeTree(n, wt);
		myHuff.Coding();
		for (j = 1; j <= n; j++)
		{
			cout << myHuff.huffTree[j].weight << '-';   // 输出各权值
			cout << myHuff.huffCode[j] << endl; // 输出各编码
		}
		myHuff.Destroy();
	}
	return 0;
}

3.反思

1)在主函数中。

将输入的权值以数组的方式保存。再通过带参数的MakeTree(int n,int * wt)进行建树,再进行编码,按要求输出后销毁析构该对象。

2)在带参建树时。

a)提及了叶子数和节点数。
	以下是他们的概念:
	(1)节点包括,中间节点(内部节点),叶节点以及根节点。
	(2)度数为0的节点为叶节点(即叶子),对于一棵树,只有唯一一个根节点。
	(3)节点数=叶子数*2-1;

b)建树时,定义HuffTree的HuffNode数组的长度为2n而非2n-1,是因为从位置1开始算。HuffCode同理。

c)初始化HuffTree,将权值逐个赋予到HUffTree中,其余非叶子节点的权值用0进行初始化,且将全部节点的双亲结点,左右孩子节点初始化,接着用无参的私有函数MakeTree()进行建树。

3)无参建树时

a)从叶子数+1开始循环,至等于节点数时停止,调用了SelectMin(int pos, int* s1, int* s2);

b)SelectMin(int pos, int* s1, int* s2)函数
     从 1 到 pos 的位置找出权值最小的两个结点,把结点下标存在 s1 和 s2 中。
     比较过程如下:
	(1)如果第 i 个结点的权值小于 w1,且第 i 个结点是未选择的结点,提示:如果第 i 结点未选择,它父亲为 0:把第 w1 和 s1 保存到 w2 和 s2,即原来的第一最小值变成第二最小值:把第 i 结点的权值和下标保存到 w1 和 s1,作为第一最小值。
	(2)否则,如果第 i 结点的权值小于 w2,且第 i 结点是未选择的结点:把第 i 结点的权值和下标保存到 w2 和 s2,作为第二最小值。

c)将找出的两棵权值最小的子树合并为一棵子树,过程包括
	 (1) 结点 s1 和结点 s2 的父亲设为 i
 	 (2)结点 i 的左右孩子分别设为 s1 和 s2
     (3)结点 i 的权值等于 s1 和 s2 的权值和
d)至此建树完成,进行编码。

4)编码

a)先分配求编码的工作空间,编码的长度等于叶子数-2;
b)设置编码结束符,即数组的最后一位为‘\0’;
c)逐个字符求哈夫曼编码,参考课本P147算法6.12 HuffmanCoding代码,再将cd中从start到末尾的编码复制到huffCode中。

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: DS二叉树,也称赫夫曼树,是一种用于数据压缩的树形结构。它的构建过程是将一组权值作为叶子节点,通过不断合并权值最小的两个节点,最终形成一棵树,使得每个叶子节点到根节点的路径上的权值之和最小。 赫夫曼树编码过程是将每个叶子节点的权值编码为一串二进制数,使得编码后的长度最短,从而实现数据压缩的目的。 以下是赫夫曼树构建编码代码框架: ```python class Node: def __init__(self, value, weight): self.value = value self.weight = weight self.left = None self.right = None def build_huffman_tree(data): # 构建赫夫曼树 nodes = [Node(value, weight) for value, weight in data] while len(nodes) > 1: nodes.sort(key=lambda x: x.weight) left = nodes.pop(0) right = nodes.pop(0) parent = Node(None, left.weight + right.weight) parent.left = left parent.right = right nodes.append(parent) return nodes[0] def encode_huffman_tree(root): # 编码赫夫曼树 codes = {} def dfs(node, code): if node is None: return if node.value is not None: codes[node.value] = code return dfs(node.left, code + '0') dfs(node.right, code + '1') dfs(root, '') return codes # 示例 data = [('a', 5), ('b', 9), ('c', 12), ('d', 13), ('e', 16), ('f', 45)] root = build_huffman_tree(data) codes = encode_huffman_tree(root) print(codes) # 输出:{'a': '1100', 'b': '1101', 'c': '100', 'd': '101', 'e': '111', 'f': '0'} ``` 以上代码中,`Node` 类表示赫夫曼树的节点,包值、权值、左子节点和右子节点四个属性。`build_huffman_tree` 函数接受一个列表 `data`,其中每个元素是一个二元组,表示一个叶子节点的值和权值。该函数返回构建好的赫夫曼树的根节点。 `encode_huffman_tree` 函数接受赫夫曼树的根节点,返回一个字典,表示每个叶子节点的编码。该函数使用深度优先搜索遍历赫夫曼树,对于每个叶子节点,记录其值和编码。 在示例中,我们使用了一个包 6 个叶子节点的数据集,构建了一棵赫夫曼树,并对每个叶子节点进行了编码。最终输出了每个叶子节点的编码结果。 ### 回答2: 1. DS二叉树简介 DS二叉树是一种常见的数据结构,也是二叉树数据结构的一种变种。其特点是每个节点只有两个支,即左子节点和右子节点。DS二叉树在计算机科学中有着重要的应用,例如在文件压缩、加密等领域中,常用DS二叉树构建赫夫曼树,实现数据的压缩和加密。 2. 赫夫曼树简介 赫夫曼树(Huffman Tree)是一种用于数据压缩和加密的方法。它是一棵带权路径最短的树,即树中所有叶子节点到根节点的路径长度乘以该叶子节点的权值之和最小。赫夫曼树构建可以通过DS二叉树来实现,是DS二叉树的一种典型应用。 3. 赫夫曼树构建编码 赫夫曼树构建通过以下步骤实现: 1)将所有的数据项按照权值大小从小到大排序; 2)选取权值最小的两个节点作为新的父节点,将这两个节点从序列中删除,再将新的父节点添加到序列中; 3)重复执行第2步,直到序列中只剩下一个节点,即构建出了一棵赫夫曼树。 在构建赫夫曼树的过程中,可以通过DS二叉树来表示和存储树节点。具体来说,可以定义一个叫做HuffmanNode的结构体,用来存储树节点的权值、左右子节点和父节点等信息。同时,可以定义一个HuffmanTree类,用来实现赫夫曼树构建编码。 HuffmanNode结构体定义如下: ``` struct HuffmanNode { int weight; // 权值 HuffmanNode* parent; // 父节点 HuffmanNode* left; // 左子节点 HuffmanNode* right; // 右子节点 }; ``` HuffmanTree类的成员函数包括: 1)createTree:用来构建赫夫曼树; 2)encode:用来对数据进行编码。 createTree函数的实现如下: ``` void HuffmanTree::createTree() { // 将所有数据项节点插入到序列中 for (int i = 0; i < data.size(); i++) { HuffmanNode* node = new HuffmanNode; node->weight = data[i].weight; node->parent = nullptr; node->left = nullptr; node->right = nullptr; nodes.push(node); } // 不断从序列中选取权值最小的两个节点,构建一颗新的赫夫曼树 while (nodes.size() > 1) { HuffmanNode* node1 = nodes.top(); nodes.pop(); HuffmanNode* node2 = nodes.top(); nodes.pop(); HuffmanNode *parent = new HuffmanNode; parent->weight = node1->weight + node2->weight; parent->parent = nullptr; parent->left = node1; parent->right = node2; node1->parent = parent; node2->parent = parent; nodes.push(parent); } // 保存赫夫曼树的根节点 root = nodes.top(); } ``` encode函数的实现如下: ``` string HuffmanTree::encode(const string& s) { // 构建字符到编码的映射表 buildEncodingTable(root, ""); // 对输入的数据进行编码 string result; for (char c : s) { result += encodingTable[c]; } return result; } void HuffmanTree::buildEncodingTable(HuffmanNode* node, string code) { if (node == nullptr) { return; } if (node->left == nullptr && node->right == nullptr) { // 将字符和对应的编码加入到映射表中 encodingTable[node->ch] = code; } buildEncodingTable(node->left, code + "0"); buildEncodingTable(node->right, code + "1"); } ``` 在encode函数中,首先调用buildEncodingTable函数构建字符到编码的映射表,然后对输入的数据进行编码并返回。而buildEncodingTable函数则是通过递归的方式构建映射表的。在编码时,只需要将每个字符对应的编码拼接起来即可。 以上就是DS二叉树--赫夫曼树构建编码的基本框架,可以根据需要进行调整和优化。 ### 回答3: ds二叉树,也叫赫夫曼树,是一种特殊的二叉树,用于编码和解码数据。在大数据时代,赫夫曼树被广泛应用于数据压缩,因为它可以用最小的比特位编码代表最频繁的字符,从而大大降低文件大小,加速数据的传输和存储。下面我们来看一下赫夫曼树构建编码过程。 赫夫曼树构建 赫夫曼树构建需要按照以下步骤: 1.统计每个字符的出现频率,把每个字符看作一个结点,并按照频率从小到大排序。 2.取出两个频率最小的结点作为左右子结点,生成一棵新的树,其权值为左右子结点权值之和。 3.把新的树的权值插入有序队列中。 4.重复步骤2和3,直到队列中只剩下一个树,即为赫夫曼树。 下面是赫夫曼树构建代码框架: ``` struct Node{ char val; //字符值 int freq; //字符频率 Node *left; //左子结点 Node *right; //右子结点 }; struct cmp { bool operator()(Node *a, Node *b) { return a->freq > b->freq; } }; Node* buildHuffmanTree(string s){ unordered_map<char,int> mp; for(int i=0;i<s.size();i++){ mp[s[i]]++; } priority_queue<Node*, vector<Node*>, cmp> pq; for(const auto &ele:mp){ Node *tmp = new Node; tmp->val = ele.first; tmp->freq = ele.second; tmp->left = nullptr; tmp->right = nullptr; pq.push(tmp); } while(pq.size()>1){ Node *a = pq.top(); pq.pop(); Node *b = pq.top(); pq.pop(); Node *c = new Node; c->freq = a->freq + b->freq; c->left = a; c->right = b; pq.push(c); } return pq.top(); } ``` 赫夫曼编码赫夫曼树中,从根节点一直到每个叶子结点的路径构成了该叶子结点字符的编码,左支为0,右支为1。赫夫曼的编码方式称为前缀编码,即任何一个字符的编码序列都不是另一个字符代码的前缀,这种编码方式保证了解码的唯一性。因为赫夫曼编码是以树为基础构建的,所以我们可以使用深度优先遍历来得到每个字符的编码。 下面是赫夫曼编码代码框架: ``` void dfs(Node* root, string path, unordered_map<char,string> &mp) { if(root->left==nullptr && root->right==nullptr){ mp[root->val] = path; return; } if(root->left){ dfs(root->left,path+"0",mp); } if(root->right){ dfs(root->right,path+"1",mp); } } unordered_map<char,string> buildHuffmanCode(Node* root) { unordered_map<char,string> mp; if(root==nullptr){ return mp; } dfs(root,"",mp); return mp; } ``` 总结 赫夫曼树是一种高效的数据压缩方式,在实际应用中广泛应用于图像、音频、视频等大型文件的传输和存储。它的构建编码过程相对简单,只需要按照一定的规则统计字符频率,然后生成二叉树并得到字符的编码,就可以将文件压缩成更小的大小。希望大家能够掌握赫夫曼树的原理和实现,提高数据处理的效率和精度。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值