c++ 构建哈夫曼树

简介

哈夫曼树是一种用来对字符进行编码的数据结构,可以根据字符的使用频率来决定字符的二进制表示,使得转化后的二进制序列尽可能短。
哈夫曼树的具体介绍见此博客用漫画介绍哈弗曼树

接下来介绍哈夫曼树的构造方法,假设给定n个字符及其对应的频率(出现次数),求出用哈夫曼编码后编码串的长度。
比如:

4 //字符个数
a 1
b 2
c 3
d 4

会返回19。(如果不知道怎么算请看上面链接的博客)

c++构造

方法一

首先可以想到用优先队列,将包含频率信息的节点加入队列,然后每次从队列中拿出两个最小的节点a和b,求出频率和,构造新的节点,其左右子节点分别指向a和b,将新节点加入队列。重复此过程直至队列中只剩一个节点。
在上面的步骤中我们模拟哈夫曼树的构造过程,确确实实地构造出一棵树来,接下来只需要由这棵树的根节点开始遍历,找到每个叶子节点,将它们的值(即频率)和深度(即编码长度)的乘积加起来,就是我们要的结果。
时间复杂度:O(nlogn)
ps:这个代码没有释放指针,但是这不是要点,所以忽略了。

#include<iostream>
#include<queue>
#include<unordered_map>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
struct Node {
    
    
	long long val;
	Node* left, *right;
	Node(long long val) {
    
    
		this->val = val;
		left = NULL;
		right = NULL;
	}
};

struct cmp {
    
    
	bool operator()(const Node* a, const Node* b) const{
    
    
		return a->val > b->val;
	}
};

long long search(Node* root, long long level) {
    
     
	if (root == NULL) return 0;
	if (root->left == NULL && root->right == NULL) return root->val * level;
	return search(root->left, level + 1) + search(root->right, level + 1);
}
int main() {
    
    
	long long n;
	cin >> n;
    if (n == 0) {
    
    
    	cout << 0;
    	return 0;
    }
    long long arr[n];
    char c;
    for (long long i = 0; i < n; ++i) {
    
    
        cin >> c >> arr[i];
    }
    if (n == 1) {
    
    
    	cout << arr[0];
    	return 0;
    }
    priority_queue<Node*, vector<Node*>, cmp> pq;
    for (long long i = 0; i < n; ++i) {
    
    
    	//if (arr[i] <= 0) continue;
    	Node* t = new Node(arr[i]);
    	pq.push(t);
	}
	
	if (pq.size() == 1) {
    
    
		cout << pq.top()->val << endl;
		return 0;
	}
	Node* root;
    while(pq.size() > 1) {
    
    
        Node* a = pq.top();
        pq.pop();
        Node* b = pq.top();
        pq.pop();
        Node* sum = new Node(a->val + b->val);
        sum->left = a;
        sum->right = b;
        root = sum;
        pq.push(sum);
    }
    cout << search(root, 0) << endl;
}
    
    

方法二

如果不想用优先队列,不想自己写比较函数,可以怎样写呢?
注意到一个规律:每次从队列中拿出最小的两个节点按值相加,得到的新节点的值是递增的,假设我们只用普通的数组来存储最初的节点,并且用另一个数组来存储新增的节点,再用两个指针分别指向两个数组的下标,通过一定的控制(从左边数组拿或从右边数组拿)可以实现。具体请看代码。

#include<cstring>
#include<algorithm>
#include<vector>
#include<iostream>
using namespace std;
int n;
vector<long long> a[2];
int idx[2];

inline long long popleft(int i) {
    
    
	return a[i][idx[i]++];
}

inline long long getmin() {
    
    
	if (idx[0] >= (int)a[0].size()) {
    
      
  		return popleft(1);
	}
 	if (idx[1] >= (int)a[1].size()) {
    
    
  		return popleft(0);
 	}
 	if (a[0][idx[0]] < a[1][idx[1]]) {
    
    
  		return popleft(0);
 	}
 	return popleft(1);
}

int main() {
    
    
 	cin >> n;
 	if (n == 0) {
    
    
  		cout << 0 << endl;
  		return 0;
 	}
 	long long t1, t2;
 	char c;
 	for (int i = 0; i < n; ++i) {
    
    
  		cin >> c >> t2;
  		a[0].push_back(t2);
	}
 	if (n == 1) {
    
    
  		cout << t2 << endl;
  		return 0;
 	}
 	sort(a[0].begin(), a[0].end());

 	long long ans = 0;
 	for (int i = 1; i < n; ++i) {
    
    
	  	t1 = getmin();
	  	t2 = getmin();
  		ans += t1 + t2;
  		a[1].push_back(t1 + t2);
 	}
 	cout << ans << endl;
 	return 0;
}

时间复杂度依然是O(nlogn)。

猜你喜欢

转载自blog.csdn.net/weixin_43867940/article/details/106003378