哈夫曼树(Huffman Tree),也叫最优二叉树,是一种带权路径长度最短的二叉树。
WPL(带权路径长度,Weighted Path Length):树中所有叶子结点的权值与它到根的路径长度(层数-1)的乘积之和,即
WPL = Σ (w_i × l_i)
其中 w_i 是第 i 个叶子权值,l_i 是它的路径长度。哈夫曼树就是使 WPL 达到最小的二叉树。
哈夫曼算法(构造过程):
哈夫曼编码:给哈夫曼树中的左分支标 0、右分支标 1,从根到某个叶子的路径上的 0/1 序列,就是该叶子权值对应的哈夫曼编码。
适用场景:数据压缩(ZIP、JPEG 等)、无损编码、最优判定树、最优归并顺序等。
| 操作 | 时间复杂度 | 空间复杂度 |
|---|---|---|
| 建哈夫曼树 | O(n log n) | O(n) |
| 生成哈夫曼编码(前序遍历) | O(n) | O(n)(编码表)+ O(树高) 递归栈 |
为什么:建树需要做 n-1 次合并,每次从最小堆中取出两个最小元素并插入一个新元素,堆操作各为 O(log n),故总 O(n log n)。若用"每次排序取最小两个"则更慢(O(n²))。生成编码是遍历整棵树,访问 n 个叶子,为 O(n);编码表存储 n 个叶子编码,总长度 O(n log n) 量级。空间主要用于存储 n 个结点与最小堆,为 O(n)。
下面是 4 种语言的完整实现,均演示:给定一组权值(如字符出现频率),用最小堆/优先队列构建哈夫曼树,并用前序遍历生成并输出每个权值的哈夫曼编码。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 哈夫曼树结点
typedef struct HTNode {
int weight; // 权值
char code[64]; // 编码(仅在叶子使用)
struct HTNode *left, *right;
} HTNode;
// 最小堆(按权值排序)
typedef struct {
HTNode **data;
int size, cap;
} MinHeap;
MinHeap *createHeap(int cap) {
MinHeap *h = (MinHeap *)malloc(sizeof(MinHeap));
h->data = (HTNode **)malloc(sizeof(HTNode *) * cap);
h->size = 0;
h->cap = cap;
return h;
}
void swap(HTNode **a, HTNode **b) { HTNode *t = *a; *a = *b; *b = t; }
void siftDown(MinHeap *h, int i) {
int smallest = i;
int l = 2 * i + 1, r = 2 * i + 2;
if (l < h->size && h->data[l]->weight < h->data[smallest]->weight) smallest = l;
if (r < h->size && h->data[r]->weight < h->data[smallest]->weight) smallest = r;
if (smallest != i) {
swap(&h->data[i], &h->data[smallest]);
siftDown(h, smallest);
}
}
void siftUp(MinHeap *h, int i) {
while (i > 0 && h->data[(i - 1) / 2]->weight > h->data[i]->weight) {
swap(&h->data[(i - 1) / 2], &h->data[i]);
i = (i - 1) / 2;
}
}
void push(MinHeap *h, HTNode *node) {
h->data[h->size] = node;
siftUp(h, h->size);
h->size++;
}
HTNode *pop(MinHeap *h) {
HTNode *top = h->data[0];
h->data[0] = h->data[h->size - 1];
h->size--;
siftDown(h, 0);
return top;
}
HTNode *newNode(int w) {
HTNode *n = (HTNode *)malloc(sizeof(HTNode));
n->weight = w;
n->left = n->right = NULL;
n->code[0] = '\0';
return n;
}
// 建哈夫曼树
HTNode *buildHuffman(int *w, int n) {
MinHeap *h = createHeap(n * 2);
for (int i = 0; i < n; i++) push(h, newNode(w[i]));
while (h->size > 1) {
HTNode *a = pop(h);
HTNode *b = pop(h);
HTNode *parent = newNode(a->weight + b->weight);
parent->left = a;
parent->right = b;
push(h, parent);
}
return pop(h);
}
// 前序遍历生成哈夫曼编码:左0 右1
void generateCodes(HTNode *node, char *path, int depth) {
if (!node) return;
// 叶子:记录编码并输出
if (!node->left && !node->right) {
path[depth] = '\0';
strcpy(node->code, path);
printf("权值 %d -> 编码 %s\n", node->weight, path);
return;
}
path[depth] = '0';
generateCodes(node->left, path, depth + 1);
path[depth] = '1';
generateCodes(node->right, path, depth + 1);
}
int main() {
int weights[] = {5, 9, 12, 13, 16, 45};
int n = sizeof(weights) / sizeof(weights[0]);
printf("权值: ");
for (int i = 0; i < n; i++) printf("%d ", weights[i]);
printf("\n");
HTNode *root = buildHuffman(weights, n);
char path[64];
printf("哈夫曼编码:\n");
generateCodes(root, path, 0);
return 0;
}
#include <iostream>
#include <queue>
#include <string>
#include <vector>
using namespace std;
// 哈夫曼树结点
struct Node {
int weight;
string code;
Node *left, *right;
Node(int w) : weight(w), left(nullptr), right(nullptr) {}
};
// 比较器:最小堆
struct Compare {
bool operator()(Node *a, Node *b) { return a->weight > b->weight; }
};
// 建哈夫曼树
Node *buildHuffman(const vector<int>& weights) {
priority_queue<Node*, vector<Node*>, Compare> pq;
for (int w : weights) pq.push(new Node(w));
while (pq.size() > 1) {
Node *a = pq.top(); pq.pop();
Node *b = pq.top(); pq.pop();
Node *parent = new Node(a->weight + b->weight);
parent->left = a;
parent->right = b;
pq.push(parent);
}
return pq.top();
}
// 前序遍历生成编码
void generateCodes(Node *node, string path) {
if (!node) return;
if (!node->left && !node->right) {
node->code = path;
cout << "权值 " << node->weight << " -> 编码 " << path << endl;
return;
}
generateCodes(node->left, path + "0");
generateCodes(node->right, path + "1");
}
int main() {
vector<int> weights = {5, 9, 12, 13, 16, 45};
cout << "权值: ";
for (int w : weights) cout << w << " ";
cout << endl;
Node *root = buildHuffman(weights);
cout << "哈夫曼编码:" << endl;
generateCodes(root, "");
return 0;
}
import java.util.PriorityQueue;
public class Huffman {
// 结点
static class Node {
int weight;
String code = "";
Node left, right;
Node(int w) { weight = w; }
}
// 建哈夫曼树:优先队列(最小堆)
static Node buildHuffman(int[] weights) {
PriorityQueue<Node> pq = new PriorityQueue<>((a, b) -> a.weight - b.weight);
for (int w : weights) pq.offer(new Node(w));
while (pq.size() > 1) {
Node a = pq.poll();
Node b = pq.poll();
Node parent = new Node(a.weight + b.weight);
parent.left = a;
parent.right = b;
pq.offer(parent);
}
return pq.poll();
}
// 前序遍历生成编码:左0 右1
static void generateCodes(Node node, String path) {
if (node == null) return;
if (node.left == null && node.right == null) {
node.code = path;
System.out.println("权值 " + node.weight + " -> 编码 " + path);
return;
}
generateCodes(node.left, path + "0");
generateCodes(node.right, path + "1");
}
public static void main(String[] args) {
int[] weights = {5, 9, 12, 13, 16, 45};
System.out.print("权值: ");
for (int w : weights) System.out.print(w + " ");
System.out.println();
Node root = buildHuffman(weights);
System.out.println("哈夫曼编码:");
generateCodes(root, "");
}
}
import heapq
from collections import namedtuple
class Node:
"""哈夫曼树结点"""
def __init__(self, weight, left=None, right=None):
self.weight = weight
self.left = left
self.right = right
self.code = ""
def build_huffman(weights):
"""用最小堆构建哈夫曼树,返回根结点"""
# 用 (weight, 自增序号) 避免两个相等权值比较结点时报错
counter = 0
heap = []
for w in weights:
heapq.heappush(heap, (w, counter, Node(w)))
counter += 1
while len(heap) > 1:
_, _, a = heapq.heappop(heap)
_, _, b = heapq.heappop(heap)
parent = Node(a.weight + b.weight, a, b)
heapq.heappush(heap, (parent.weight, counter, parent))
counter += 1
return heap[0][2]
def generate_codes(node, path=""):
"""前序遍历生成哈夫曼编码:左0 右1"""
if node is None:
return
if node.left is None and node.right is None:
node.code = path
print(f"权值 {node.weight} -> 编码 {path}")
return
generate_codes(node.left, path + "0")
generate_codes(node.right, path + "1")
if __name__ == "__main__":
weights = [5, 9, 12, 13, 16, 45]
print("权值:", weights)
root = build_huffman(weights)
print("哈夫曼编码:")
generate_codes(root)