当前位置: 首页 > news >正文

【数据结构】基于哈夫曼树的数据压缩算法

目录

一、引言

二、数据结构定义(算法的 “骨架”)

1. 哈夫曼树节点(HuffmanNode)

2. 业务数据封装(HuffmanData)

3. 最小堆(MinHeap)

4. 哈夫曼编码表(HuffmanCode)

三、最小堆核心操作(算法效率的关键)

1. 堆的创建与销毁

2. 堆的调整与维护

3. 堆的节点操作

四、核心业务流程(从输入到输出的完整链路)

1. 数据初始化(InitData)

2. 字符频率统计(FrequeStats)

3. 哈夫曼树构建(CreateHuffmanTree)

4. 哈夫曼编码生成(CreatHuffmanCode)

5. 结果展示(Show)

6. 编码与解码(EncodeAndDecode)

7. 哈夫曼树销毁(DestroyHuffmanTree)

五、主函数流程

六、算法亮点与复杂度总结

七、多种编程语言实现

(一)C++代码

(二)Python代码

(三)Java代码

八、程序运行结果展示

九、总结


一、引言

本文实现了完整的哈夫曼编码系统,支持以下核心流程:

  • 输入字符串 → 统计字符频率 → 构建哈夫曼树 → 生成哈夫曼编码 → 展示树结构与编码 → 字符串编码(二进制串)→ 编码串解码(还原原字符串)→ 内存回收。
  • 核心价值:通过哈夫曼编码实现 “变长前缀编码”,使高频字符获得短编码,低频字符获得长编码,从而压缩数据体积。

二、数据结构定义(算法的 “骨架”)

1. 哈夫曼树节点(HuffmanNode
typedef struct HuffmanNode {char data;          // 仅叶子节点存储字符(如 'a'、'b')int weight;         // 节点权值(字符出现频率)struct HuffmanNode* parent; // 双亲节点指针struct HuffmanNode* lchild; // 左孩子指针struct HuffmanNode* rchild; // 右孩子指针
} HuffmanNode, *HuffmanTree;
  • 作用:存储哈夫曼树的节点,通过parentlchildrchild维护树的拓扑关系,dataweight分别存储字符和频率信息。
  • 设计细节:仅叶子节点的data有效,非叶子节点用'#'标记(构建树时的约定)。
2. 业务数据封装(HuffmanData
typedef struct HuffmanData {int fre[26];          // 26个小写字母的频率(索引0对应'a',25对应'z')char namelist[26];    // 出现过的字符(按ASCII升序存储)int charCount;        // 出现的字符种类数int strLen;           // 输入字符串长度
} HuffmanData;
  • 作用:封装所有业务数据,避免全局变量依赖,使函数间耦合度大幅降低。例如,fre数组统计频率,namelist按顺序存储有效字符,charCount记录字符种类数。
3. 最小堆(MinHeap
typedef struct {HuffmanNode** array; // 存储哈夫曼节点指针的数组int size;            // 当前堆中节点数量int capacity;        // 堆的最大容量
} MinHeap;
  • 作用:优化哈夫曼树构建过程中 “选择最小权值节点” 的操作,将时间复杂度从朴素算法的O(n²)降至O(nlogn)
4. 哈夫曼编码表(HuffmanCode
typedef char** HuffmanCode;
  • 作用:二级指针结构,本质是 “字符→编码” 的映射表。例如,HC[1]存储namelist[0]字符的哈夫曼编码。

三、最小堆核心操作(算法效率的关键)

哈夫曼树构建的核心是 **“反复选择两个最小权值的独立节点”**,最小堆为这一操作提供了高效支持。

1. 堆的创建与销毁
MinHeap* createMinHeap(int capacity) {MinHeap* heap = (MinHeap*)malloc(sizeof(MinHeap));heap->size = 0;heap->capacity = capacity;heap->array = (HuffmanNode**)malloc(capacity * sizeof(HuffmanNode*));return heap;
}void destroyMinHeap(MinHeap* heap) {free(heap->array);free(heap);
}
  • MinHeap* createMinHeap(int capacity)

    • 功能:分配堆结构和节点数组的内存,初始化size=0capacity为指定值。
    • 实现:heap = (MinHeap*)malloc(sizeof(MinHeap)); heap->array = (HuffmanNode**)malloc(capacity * sizeof(HuffmanNode*));
    • 时间复杂度:O(1)
  • void destroyMinHeap(MinHeap* heap)

    • 功能:释放堆的节点数组和堆结构本身的内存,避免泄漏。
    • 实现:free(heap->array); free(heap);
    • 时间复杂度:O(1)
2. 堆的调整与维护
void swapHeapNode(HuffmanNode** a, HuffmanNode** b) {HuffmanNode* temp = *a;*a = *b;*b = temp;
}void minHeapify(MinHeap* heap, int idx) {int smallest = idx;int left = 2 * idx + 1;int right = 2 * idx + 2;if (left < heap->size && heap->array[left]->weight < heap->array[smallest]->weight)smallest = left;if (right < heap->size && heap->array[right]->weight < heap->array[smallest]->weight)smallest = right;if (smallest != idx) {swapHeapNode(&heap->array[idx], &heap->array[smallest]);minHeapify(heap, smallest);}
}void buildMinHeap(MinHeap* heap) {int n = heap->size - 1;for (int i = (n - 1) / 2; i >= 0; i--)minHeapify(heap, i);
}
  • void swapHeapNode(HuffmanNode** a, HuffmanNode** b)

    • 功能:交换两个堆节点的指针,用于堆调整过程中节点位置的交换。
    • 实现:临时指针temp交换*a*b
    • 时间复杂度:O(1)
  • void minHeapify(MinHeap* heap, int idx)

    • 功能:向下调整算法,维护最小堆性质。从idx节点开始,比较其与左右孩子的权值,将最小权值节点上浮到父节点位置,递归调整。
    • 实现:计算左、右孩子索引left=2*idx+1right=2*idx+2,找到最小权值的孩子smallest,若smallest != idx则交换并递归调整smallest
    • 时间复杂度:O(logn)n为堆的大小)。
  • void buildMinHeap(MinHeap* heap)

    • 功能:将普通数组转换为最小堆。从最后一个非叶子节点(heap->size-1)/2开始,向前遍历执行minHeapify
    • 实现:for (int i = (n - 1) / 2; i >= 0; i--) minHeapify(heap, i);
    • 时间复杂度:O(n)(数学推导可证明堆构建的时间复杂度为线性)。
3. 堆的节点操作
int isHeapEmpty(MinHeap* heap) {return heap->size == 0;
}HuffmanNode* extractMin(MinHeap* heap) {if (isHeapEmpty(heap)) return NULL;HuffmanNode* minNode = heap->array[0];heap->array[0] = heap->array[heap->size - 1];heap->size--;minHeapify(heap, 0);return minNode;
}void insertMinHeap(MinHeap* heap, HuffmanNode* node) {if (heap->size == heap->capacity) return;int i = heap->size++;while (i > 0 && node->weight < heap->array[(i - 1) / 2]->weight) {heap->array[i] = heap->array[(i - 1) / 2];i = (i - 1) / 2;}heap->array[i] = node;
}
  • HuffmanNode* extractMin(MinHeap* heap)

    • 功能:提取堆顶节点(权值最小的节点),然后将堆尾节点移到堆顶,执行minHeapify维持堆性质。
    • 实现:HuffmanNode* minNode = heap->array[0]; heap->array[0] = heap->array[heap->size - 1]; heap->size--; minHeapify(heap, 0);
    • 时间复杂度:O(logn)
  • void insertMinHeap(MinHeap* heap, HuffmanNode* node)

    • 功能:将新节点插入堆尾,然后向上调整(比较当前节点与父节点的权值,若更小则交换),维持堆的 “最小性”。
    • 实现:int i = heap->size++; while (i > 0 && node->weight < heap->array[(i - 1) / 2]->weight) { heap->array[i] = heap->array[(i - 1) / 2]; i = (i - 1) / 2; } heap->array[i] = node;
    • 时间复杂度:O(logn)
  • int isHeapEmpty(MinHeap* heap)

    • 功能:判断堆是否为空(size == 0)。
    • 时间复杂度:O(1)

四、核心业务流程(从输入到输出的完整链路)

1. 数据初始化(InitData
void InitData(HuffmanData* data) {memset(data->fre, 0, sizeof(data->fre));    // 频率数组清零memset(data->namelist, 0, sizeof(data->namelist)); // 字符列表清零data->charCount = 0;  // 字符种类数置0data->strLen = 0;     // 字符串长度置0
}
  • 功能:每次处理新字符串前,清空上一轮的统计数据,确保初始状态干净。
  • 时间复杂度:O(1)memset对固定大小数组操作)。
2. 字符频率统计(FrequeStats
void FrequeStats(HuffmanData* data, const string& s) {// 1. 统计每个小写字母的频率for (char c : s) {if (c >= 'a' && c <= 'z') {int idx = c - 'a';data->fre[idx]++;}}// 2. 收集非零频率的字符到namelist(按ASCII升序)int count = 0;for (int i = 0; i < 26; i++) {if (data->fre[i] > 0) {data->namelist[count] = 'a' + i;count++;}}data->charCount = count;// 3. 输出频率统计结果for (int i = 0; i < data->charCount; i++) {if (i > 0) cout << " ";cout << data->namelist[i] << ":" << data->fre[i];}cout << endl;
}
  • 原理:利用 26 个小写字母的ASCII 码连续性'a'=97'z'=122),将字符映射到数组索引(c - 'a'),实现频率统计。
  • 步骤:
    • 遍历字符串,统计每个字符的频率到fre数组。
    • 遍历fre数组,收集非零频率的字符到namelist(天然按 ASCII 升序)。
    • 按格式输出 “字符:频度”。
  • 时间复杂度:O(n + 26)n是字符串长度,26 是字母表大小)→ 最终O(n)
3. 哈夫曼树构建(CreateHuffmanTree

哈夫曼树构建的核心算法是 **“每次合并两个权值最小的独立节点”**,直到只剩一个根节点。

HuffmanTree CreateHuffmanTree(HuffmanData* data) {int n = data->charCount;if (n <= 0) return NULL;// 特殊情况:只有一个字符if (n == 1) {HuffmanNode* root = (HuffmanNode*)malloc(sizeof(HuffmanNode));root->data = data->namelist[0];root->weight = data->fre[0];root->parent = root->lchild = root->rchild = NULL;return root;}// 初始化最小堆,容量为字符种类数nMinHeap* heap = createMinHeap(n);// 插入所有叶子节点(每个字符对应一个节点,权值为频率)for (int i = 0; i < n; i++) {HuffmanNode* node = (HuffmanNode*)malloc(sizeof(HuffmanNode));node->data = data->namelist[i];node->weight = data->fre[i];node->parent = node->lchild = node->rchild = NULL;insertMinHeap(heap, node);}// 反复合并最小节点,构建哈夫曼树while (heap->size > 1) {HuffmanNode* left = extractMin(heap);  // 提取最小节点HuffmanNode* right = extractMin(heap); // 提取次小节点// 创建合并节点(非叶子节点,data用'#'标记)HuffmanNode* mergeNode = (HuffmanNode*)malloc(sizeof(HuffmanNode));mergeNode->data = '#';mergeNode->weight = left->weight + right->weight;mergeNode->parent = NULL;mergeNode->lchild = left;mergeNode->rchild = right;// 设置子节点的父节点left->parent = mergeNode;right->parent = mergeNode;// 插入合并节点到堆insertMinHeap(heap, mergeNode);}// 堆中剩余节点为根节点HuffmanTree root = extractMin(heap);destroyMinHeap(heap);return root;
}
  • 特殊情况处理:若只有 1 个字符,直接创建根节点(无左右孩子)。
  • 常规流程:
    • 初始化最小堆,容量为字符种类数n
    • 为每个字符创建叶子节点,权值为其频率,插入堆中。
    • 循环提取两个最小节点leftright,创建合并节点mergeNode(权值为两者之和),将leftright作为其左右孩子,设置父节点关系,再将mergeNode插入堆中。
    • 循环直到堆中只剩一个节点(根节点),提取并返回。
  • 时间复杂度:堆的extractMininsertMinHeap各执行2(n-1)次(每次合并消耗两个节点,生成一个节点,共n-1次合并),每次操作O(logn),因此总时间复杂度O(nlogn)
  • 空间复杂度:堆和哈夫曼树的节点数均为O(n)n是字符种类数),因此空间复杂度O(n)
4. 哈夫曼编码生成(CreatHuffmanCode

哈夫曼编码通过 **“根→叶子” 的路径生成:左子树路径记为'0',右子树路径记为'1'。为避免编码歧义,所有编码均为前缀编码 **(无编码是其他编码的前缀)。

void CreatHuffmanCode(HuffmanTree root, HuffmanCode& HC, int n, HuffmanData* data) {if (n <= 0 || !root) return;HC = (HuffmanCode)malloc((n + 1) * sizeof(char*)); // HC[0]闲置char* temp = (char*)malloc(n * sizeof(char));      // 临时存储编码temp[n - 1] = '\0';                                // 编码结束符// 单个字符特殊处理if (n == 1) {HC[1] = (char*)malloc(2 * sizeof(char));HC[1][0] = '0';HC[1][1] = '\0';free(temp);return;}// 递归生成编码的辅助函数auto generateCode = [&](HuffmanNode* node, int depth, auto&& self) -> void {if (!node) return;// 到达叶子节点:记录编码if (!node->lchild && !node->rchild) {// 找到该字符在namelist中的索引int i;for (i = 0; i < n; i++) {if (data->namelist[i] == node->data) {break;}}// 复制临时编码到HC[i+1]for (int k = 0; k < depth; k++) {HC[i+1][k] = temp[k];}HC[i+1][depth] = '\0'; // 编码结束符return;}// 左子树路径记为'0'temp[depth] = '0';self(node->lchild, depth + 1, self);// 右子树路径记为'1'temp[depth] = '1';self(node->rchild, depth + 1, self);};// 为每个字符分配编码空间for (int i = 0; i < n; i++) {HC[i + 1] = (char*)malloc(n * sizeof(char));}generateCode(root, 0, generateCode); // 从根节点开始递归生成编码free(temp); // 释放临时编码数组
}
  • 原理:递归遍历哈夫曼树,从根节点出发,左子树路径记录'0',右子树路径记录'1'。到达叶子节点时,逆向追溯路径(通过临时数组temp存储,最后复制到编码表HC)。
  • 步骤:
    • 分配编码表HC和临时数组temptemp用于存储当前路径的编码,最后一位是'\0')。
    • 特殊处理:若只有 1 个字符,编码默认为"0"
    • 定义递归辅助函数generateCode
      • 若当前节点是叶子节点,遍历namelist找到其索引i,将tempdepth长度的编码复制到HC[i+1],并添加结束符'\0'
      • 若不是叶子节点,先遍历左子树(temp[depth] = '0'),再遍历右子树(temp[depth] = '1'),递归执行。
    • 为每个字符的编码分配内存,调用generateCode生成编码,最后释放临时数组temp
  • 时间复杂度:每个节点被访问一次,递归深度为树的高度(约logn),因此总时间复杂度O(nlogn)
  • 空间复杂度:编码表HC和临时数组temp的空间均为O(n),因此空间复杂度O(n)
5. 结果展示(Show
void Show(HuffmanTree root, HuffmanCode HC, HuffmanData* data) {int n = data->charCount;cout << "哈夫曼树为:" << endl;// 层次遍历展示树结构queue<HuffmanNode*> q;if (root) q.push(root);int nodeIdx = 1;while (!q.empty()) {HuffmanNode* curr = q.front();q.pop();int lchildIdx = 0, rchildIdx = 0;if (curr->lchild) {lchildIdx = ++nodeIdx;q.push(curr->lchild);}if (curr->rchild) {rchildIdx = ++nodeIdx;q.push(curr->rchild);}cout << "结点" << nodeIdx - (lchildIdx + rchildIdx > 0 ? (lchildIdx + rchildIdx) : 1) << ":权值=" << curr->weight << " 双亲结点=0 左孩子结点=" << lchildIdx << " 右孩子结点=" << rchildIdx << endl;}cout << "输出每个字符对应的哈夫曼编码(字符:哈夫曼编码)为:" << endl;for (int i = 1; i <= n; i++) {if (i > 1) cout << " ";cout << data->namelist[i - 1] << ":" << HC[i];}cout << endl;
}
  • 树结构展示:采用层次遍历(队列实现),按 “结点索引、权值、左孩子索引、右孩子索引” 的格式输出,直观呈现哈夫曼树的拓扑结构。
  • 编码展示:按namelist的 ASCII 升序输出 “字符:编码”,确保结果可读性。
  • 时间复杂度:层次遍历的时间复杂度O(m)m是哈夫曼树的节点数,即2n-1n是字符种类数),编码展示的时间复杂度O(n),因此总时间复杂度O(n)
6. 编码与解码(EncodeAndDecode

实现 “字符串→编码串→原字符串” 的闭环,验证哈夫曼编码的正确性。

void EncodeAndDecode(HuffmanTree root, HuffmanCode HC, HuffmanData* data, const string& s) {int n = data->charCount;// 编码:字符串→哈夫曼编码串string encodedStr;for (char c : s) {for (int i = 1; i <= n; i++) {if (c == data->namelist[i - 1]) {encodedStr += HC[i];break;}}}cout << "编码后的字符串为:" << encodedStr << endl;// 解码:哈夫曼编码串→原字符串if (!root || encodedStr.empty()) {cout << "解码后的字符串为:" << s << endl;return;}string decodedStr;HuffmanNode* curr = root;for (char bit : encodedStr) {// 0走左子树,1走右子树if (bit == '0' && curr->lchild) {curr = curr->lchild;} else if (bit == '1' && curr->rchild) {curr = curr->rchild;}// 到达叶子节点:记录字符,重置为根节点if (!curr->lchild && !curr->rchild) {decodedStr += curr->data;curr = root;}}cout << "解码后的字符串为:" << decodedStr << endl;
}
  • 编码过程:遍历输入字符串,将每个字符替换为对应的哈夫曼编码(通过namelistHC的映射关系),拼接成二进制编码串。
    • 时间复杂度:O(n * k)n是字符串长度,k是字符种类数)→ 实际可优化为O(n)(通过哈希表映射,但此处为了简单用遍历)。
  • 解码过程:从哈夫曼树根节点出发,根据编码串的每一位(0左、1右)遍历树。到达叶子节点时,记录该节点的字符,然后重置为根节点继续解码,直到编码串遍历完毕。
    • 时间复杂度:O(m)m是编码串的长度)。
7. 哈夫曼树销毁(DestroyHuffmanTree
void DestroyHuffmanTree(HuffmanTree& root) {if (root) {DestroyHuffmanTree(root->lchild); // 递归销毁左子树DestroyHuffmanTree(root->rchild); // 递归销毁右子树free(root);                       // 释放当前节点root = NULL;                      // 置空避免野指针}
}
  • 功能:递归释放哈夫曼树的所有节点内存,避免内存泄漏。
  • 原理:采用后序遍历的方式,先销毁左右子树,再释放当前节点,确保所有动态分配的内存都被回收。
  • 时间复杂度:O(m)m是哈夫曼树的节点数)。

五、主函数流程

int main() {while (1) {string s;cout << "\n请输入字符串(输入\"0\"结束):";cin >> s;if (s == "0") {cout << "输入结束!" << endl;break;}HuffmanData data;InitData(&data);         // 初始化数据FrequeStats(&data, s);   // 统计字符频率HuffmanTree root = CreateHuffmanTree(&data); // 构建哈夫曼树if (!root) continue;HuffmanCode HC = NULL;CreatHuffmanCode(root, HC, data.charCount, &data); // 生成哈夫曼编码Show(root, HC, &data);                             // 展示结果EncodeAndDecode(root, HC, &data, s);               // 编码+解码// 释放内存if (HC) {for (int i = 1; i <= data.charCount; i++) {free(HC[i]);}free(HC);}DestroyHuffmanTree(root);}return 0;
}
  • 流程:循环接收用户输入,直到输入"0"结束。每次循环依次执行初始化→频率统计→树构建→编码生成→结果展示→编解码→内存释放的完整流程,确保功能闭环和资源回收。

六、算法亮点与复杂度总结

  • 效率优化:通过最小堆将哈夫曼树构建的时间复杂度从O(n²)优化到O(nlogn),适配更大字符集。
  • 功能闭环:实现 “编码→解码” 的完整流程,验证哈夫曼编码的无歧义性和正确性。
  • 内存安全:所有动态分配的内存(树节点、编码表、临时数组)均在使用后释放,避免内存泄漏。

七、多种编程语言实现

(一)C++代码
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <queue>
using namespace std;// --------------- 数据结构定义 ---------------
typedef struct HuffmanNode {char data;          // 字符(仅叶子节点有效)int weight;         // 权值(频率)struct HuffmanNode* parent;struct HuffmanNode* lchild;struct HuffmanNode* rchild;
} HuffmanNode, *HuffmanTree;typedef struct HuffmanData {int fre[26];          // 26个小写字母的频率(0=a, 1=b,...25=z)char namelist[26];    // 出现过的字符(按ASCII排序)int charCount;        // 出现的字符种类数int strLen;           // 输入字符串长度
} HuffmanData;typedef char** HuffmanCode;// --------------- 最小堆核心操作 ---------------
typedef struct MinHeap {HuffmanNode** array;int size;int capacity;
} MinHeap;MinHeap* createMinHeap(int capacity);
void minHeapify(MinHeap* heap, int idx);
int isHeapEmpty(MinHeap* heap);
HuffmanNode* extractMin(MinHeap* heap);
void insertMinHeap(MinHeap* heap, HuffmanNode* node);
void buildMinHeap(MinHeap* heap);
void destroyMinHeap(MinHeap* heap);// --------------- 核心业务函数 ---------------
void InitData(HuffmanData* data);
void FrequeStats(HuffmanData* data, const string& s);
HuffmanTree CreateHuffmanTree(HuffmanData* data);
void CreatHuffmanCode(HuffmanTree root, HuffmanCode& HC, int n, HuffmanData* data);
void Show(HuffmanTree root, HuffmanCode HC, HuffmanData* data);
void EncodeAndDecode(HuffmanTree root, HuffmanCode HC, HuffmanData* data, const string& s);
void DestroyHuffmanTree(HuffmanTree& root);// --------------- 主函数 ---------------
int main() {while (1) {string s;cout << "\n请输入字符串(输入\"0\"结束):";cin >> s;if (s == "0") {cout << "输入结束!" << endl;break;}HuffmanData data;InitData(&data);FrequeStats(&data, s);HuffmanTree root = CreateHuffmanTree(&data);if (!root) continue;HuffmanCode HC = NULL;CreatHuffmanCode(root, HC, data.charCount, &data);Show(root, HC, &data);EncodeAndDecode(root, HC, &data, s);if (HC) {for (int i = 1; i <= data.charCount; i++) {free(HC[i]);}free(HC);}DestroyHuffmanTree(root);}return 0;
}// --------------- 最小堆核心操作 ---------------
MinHeap* createMinHeap(int capacity) {MinHeap* heap = (MinHeap*)malloc(sizeof(MinHeap));heap->size = 0;heap->capacity = capacity;heap->array = (HuffmanNode**)malloc(capacity * sizeof(HuffmanNode*));return heap;
}void swapHeapNode(HuffmanNode** a, HuffmanNode** b) {HuffmanNode* temp = *a;*a = *b;*b = temp;
}void minHeapify(MinHeap* heap, int idx) {int smallest = idx;int left = 2 * idx + 1;int right = 2 * idx + 2;if (left < heap->size && heap->array[left]->weight < heap->array[smallest]->weight)smallest = left;if (right < heap->size && heap->array[right]->weight < heap->array[smallest]->weight)smallest = right;if (smallest != idx) {swapHeapNode(&heap->array[idx], &heap->array[smallest]);minHeapify(heap, smallest);}
}int isHeapEmpty(MinHeap* heap) {return heap->size == 0;
}HuffmanNode* extractMin(MinHeap* heap) {if (isHeapEmpty(heap)) return NULL;HuffmanNode* minNode = heap->array[0];heap->array[0] = heap->array[heap->size - 1];heap->size--;minHeapify(heap, 0);return minNode;
}void insertMinHeap(MinHeap* heap, HuffmanNode* node) {if (heap->size == heap->capacity) return;int i = heap->size++;while (i > 0 && node->weight < heap->array[(i - 1) / 2]->weight) {heap->array[i] = heap->array[(i - 1) / 2];i = (i - 1) / 2;}heap->array[i] = node;
}void buildMinHeap(MinHeap* heap) {int n = heap->size - 1;for (int i = (n - 1) / 2; i >= 0; i--)minHeapify(heap, i);
}void destroyMinHeap(MinHeap* heap) {free(heap->array);free(heap);
}// --------------- 核心业务函数 ---------------
void InitData(HuffmanData* data) {memset(data->fre, 0, sizeof(data->fre));memset(data->namelist, 0, sizeof(data->namelist));data->charCount = 0;data->strLen = 0;
}void FrequeStats(HuffmanData* data, const string& s) {data->strLen = s.size();for (char c : s) {if (c >= 'a' && c <= 'z') {int idx = c - 'a';data->fre[idx]++;}}int count = 0;for (int i = 0; i < 26; i++) {if (data->fre[i] > 0) {data->namelist[count] = 'a' + i;count++;}}data->charCount = count;cout << "输出每个字符对应的频度(字符:频度)为:" << endl;for (int i = 0; i < data->charCount; i++) {if (i > 0) cout << " ";cout << data->namelist[i] << ":" << data->fre[i];}cout << endl;
}HuffmanTree CreateHuffmanTree(HuffmanData* data) {int n = data->charCount;if (n <= 0) return NULL;if (n == 1) {HuffmanNode* root = (HuffmanNode*)malloc(sizeof(HuffmanNode));root->data = data->namelist[0];root->weight = data->fre[0];root->parent = root->lchild = root->rchild = NULL;return root;}MinHeap* heap = createMinHeap(n);for (int i = 0; i < n; i++) {HuffmanNode* node = (HuffmanNode*)malloc(sizeof(HuffmanNode));node->data = data->namelist[i];node->weight = data->fre[i];node->parent = node->lchild = node->rchild = NULL;insertMinHeap(heap, node);}while (heap->size > 1) {HuffmanNode* left = extractMin(heap);HuffmanNode* right = extractMin(heap);HuffmanNode* mergeNode = (HuffmanNode*)malloc(sizeof(HuffmanNode));mergeNode->data = '#';mergeNode->weight = left->weight + right->weight;mergeNode->parent = NULL;mergeNode->lchild = left;mergeNode->rchild = right;left->parent = mergeNode;right->parent = mergeNode;insertMinHeap(heap, mergeNode);}HuffmanTree root = extractMin(heap);destroyMinHeap(heap);return root;
}void CreatHuffmanCode(HuffmanTree root, HuffmanCode& HC, int n, HuffmanData* data) {if (n <= 0 || !root) return;HC = (HuffmanCode)malloc((n + 1) * sizeof(char*));char* temp = (char*)malloc(n * sizeof(char));temp[n - 1] = '\0';if (n == 1) {HC[1] = (char*)malloc(2 * sizeof(char));HC[1][0] = '0';HC[1][1] = '\0';free(temp);return;}// 递归生成编码的辅助函数auto generateCode = [&](HuffmanNode* node, int depth, auto&& self) -> void {if (!node) return;if (!node->lchild && !node->rchild) {// 找到叶子节点在namelist中的索引int i;for (i = 0; i < n; i++) {if (data->namelist[i] == node->data) {break;}}// 复制编码到HC[i+1]for (int k = 0; k < depth; k++) {HC[i+1][k] = temp[k];}HC[i+1][depth] = '\0';return;}// 左子树标记0temp[depth] = '0';self(node->lchild, depth + 1, self);// 右子树标记1temp[depth] = '1';self(node->rchild, depth + 1, self);};// 为每个字符分配编码空间for (int i = 0; i < n; i++) {HC[i + 1] = (char*)malloc(n * sizeof(char));}generateCode(root, 0, generateCode);free(temp);
}void Show(HuffmanTree root, HuffmanCode HC, HuffmanData* data) {int n = data->charCount;cout << "哈夫曼树为:" << endl;// 层次遍历展示树结构queue<HuffmanNode*> q;if (root) q.push(root);int nodeIdx = 1;while (!q.empty()) {HuffmanNode* curr = q.front();q.pop();int lchildIdx = 0, rchildIdx = 0;if (curr->lchild) {lchildIdx = ++nodeIdx;q.push(curr->lchild);}if (curr->rchild) {rchildIdx = ++nodeIdx;q.push(curr->rchild);}cout << "结点" << nodeIdx - (lchildIdx + rchildIdx > 0 ? (lchildIdx + rchildIdx) : 1) << ":权值=" << curr->weight<< " 双亲结点=0 左孩子结点=" << lchildIdx<< " 右孩子结点=" << rchildIdx << endl;}cout << "输出每个字符对应的哈夫曼编码(字符:哈夫曼编码)为:" << endl;for (int i = 1; i <= n; i++) {if (i > 1) cout << " ";cout << data->namelist[i - 1] << ":" << HC[i];}cout << endl;
}void EncodeAndDecode(HuffmanTree root, HuffmanCode HC, HuffmanData* data, const string& s) {int n = data->charCount;string encodedStr;for (char c : s) {for (int i = 1; i <= n; i++) {if (c == data->namelist[i - 1]) {encodedStr += HC[i];break;}}}cout << "编码后的字符串为:" << encodedStr << endl;if (!root || encodedStr.empty()) {cout << "解码后的字符串为:" << s << endl;return;}string decodedStr;HuffmanNode* curr = root;for (char bit : encodedStr) {if (bit == '0' && curr->lchild) {curr = curr->lchild;} else if (bit == '1' && curr->rchild) {curr = curr->rchild;}if (!curr->lchild && !curr->rchild) {decodedStr += curr->data;curr = root;}}cout << "解码后的字符串为:" << decodedStr << endl;
}void DestroyHuffmanTree(HuffmanTree& root) {if (root) {DestroyHuffmanTree(root->lchild);DestroyHuffmanTree(root->rchild);free(root);root = NULL;}
}
(二)Python代码
import heapq
from collections import deque# 哈夫曼树节点类
class HuffmanNode:def __init__(self, data, weight, parent=None, lchild=None, rchild=None):self.data = data        # 字符(仅叶子节点有效,非叶子用'#')self.weight = weight    # 权值(频率)self.parent = parent    # 双亲节点self.lchild = lchild    # 左孩子self.rchild = rchild    # 右孩子# 业务数据封装类
class HuffmanData:def __init__(self):self.fre = [0] * 26     # 26个小写字母的频率(0=a, 1=b,...25=z)self.namelist = []      # 出现过的字符(按ASCII升序)self.char_count = 0     # 字符种类数self.str_len = 0        # 输入字符串长度# 字符频率统计
def freque_stats(data, s):data.str_len = len(s)for c in s:if 'a' <= c <= 'z':idx = ord(c) - ord('a')data.fre[idx] += 1# 收集非零频率字符count = 0for i in range(26):if data.fre[i] > 0:data.namelist.append(chr(ord('a') + i))count += 1data.char_count = count# 输出频率统计print("输出每个字符对应的频度(字符:频度)为:")for i in range(data.char_count):if i > 0:print(" ", end="")print(f"{data.namelist[i]}:{data.fre[ord(data.namelist[i]) - ord('a')]}", end="")print()# 构建哈夫曼树(使用heapq实现最小堆)
def create_huffman_tree(data):n = data.char_countif n <= 0:return Noneif n == 1:# 单个字符特殊处理root = HuffmanNode(data.namelist[0], data.fre[ord(data.namelist[0]) - ord('a')])return root# 初始化最小堆(存储元组:(权值, 节点ID, 节点),避免同权值节点比较错误)heap = []for i in range(n):char = data.namelist[i]weight = data.fre[ord(char) - ord('a')]node = HuffmanNode(char, weight)heapq.heappush(heap, (weight, id(node), node))# 合并最小节点构建树while len(heap) > 1:weight1, _, left = heapq.heappop(heap)weight2, _, right = heapq.heappop(heap)# 创建合并节点merge_weight = weight1 + weight2merge_node = HuffmanNode('#', merge_weight, lchild=left, rchild=right)left.parent = merge_noderight.parent = merge_node# 插入合并节点到堆heapq.heappush(heap, (merge_weight, id(merge_node), merge_node))# 堆中剩余节点为根节点_, _, root = heapq.heappop(heap)return root# 生成哈夫曼编码(递归遍历树)
def create_huffman_code(root, data):n = data.char_countif n <= 0 or not root:return Nonecode_dict = {}  # 字符到编码的映射# 递归生成编码的辅助函数def generate_code(node, path):if not node:returnif not node.lchild and not node.rchild:# 叶子节点,记录编码code_dict[node.data] = pathreturn# 左子树路径加'0'generate_code(node.lchild, path + '0')# 右子树路径加'1'generate_code(node.rchild, path + '1')generate_code(root, '')# 按namelist顺序生成编码列表code_list = [code_dict[char] for char in data.namelist]return code_list# 展示哈夫曼树结构和编码(层次遍历)
def show(root, code_list, data):n = data.char_countif n == 0 or not root:returnprint("哈夫曼树为:")q = deque()q.append(root)node_idx = 1while q:curr = q.popleft()lchild_idx = 0rchild_idx = 0if curr.lchild:lchild_idx = node_idx + 1q.append(curr.lchild)if curr.rchild:rchild_idx = node_idx + 1 + (1 if curr.lchild else 0)q.append(curr.rchild)# 计算当前节点索引current_idx = node_idx - (lchild_idx + rchild_idx > 0)print(f"结点{current_idx}:权值={curr.weight} 双亲结点=0 左孩子结点={lchild_idx} 右孩子结点={rchild_idx}")node_idx += (1 + (1 if curr.lchild else 0) + (1 if curr.rchild else 0))# 输出哈夫曼编码print("输出每个字符对应的哈夫曼编码(字符:哈夫曼编码)为:")for i in range(n):if i > 0:print(" ", end="")print(f"{data.namelist[i]}:{code_list[i]}", end="")print()# 编码与解码(实现闭环)
def encode_and_decode(s, code_list, data, root):n = data.char_countif n == 0 or not root:print("编码后的字符串为:")print("解码后的字符串为:", s)return# 编码:字符串→哈夫曼编码串code_dict = {data.namelist[i]: code_list[i] for i in range(n)}encoded_str = ''.join([code_dict[c] for c in s])print("编码后的字符串为:", encoded_str)# 解码:哈夫曼编码串→原字符串decoded_str = ''curr = rootfor bit in encoded_str:if bit == '0':curr = curr.lchildelse:curr = curr.rchildif not curr.lchild and not curr.rchild:decoded_str += curr.datacurr = rootprint("解码后的字符串为:", decoded_str)# 主函数
def main():while True:s = input("\n请输入字符串(输入\"0\"结束):")if s == "0":print("输入结束!")breakdata = HuffmanData()freque_stats(data, s)root = create_huffman_tree(data)if not root:continuecode_list = create_huffman_code(root, data)show(root, code_list, data)encode_and_decode(s, code_list, data, root)if __name__ == "__main__":main()
(三)Java代码
import java.util.*;// 哈夫曼树节点类
class HuffmanNode {char data;          // 字符(仅叶子节点有效,非叶子用'#')int weight;         // 权值(频率)HuffmanNode parent; // 双亲节点HuffmanNode lchild; // 左孩子HuffmanNode rchild; // 右孩子public HuffmanNode(char data, int weight) {this.data = data;this.weight = weight;this.parent = null;this.lchild = null;this.rchild = null;}
}// 业务数据封装类
class HuffmanData {int[] fre = new int[26];    // 26个小写字母的频率(0=a, 1=b,...25=z)char[] namelist = new char[26]; // 出现过的字符(按ASCII升序)int charCount = 0;          // 字符种类数int strLen = 0;             // 输入字符串长度
}public class HuffmanEncoding {// 字符频率统计public static void frequeStats(HuffmanData data, String s) {data.strLen = s.length();for (char c : s.toCharArray()) {if (c >= 'a' && c <= 'z') {int idx = c - 'a';data.fre[idx]++;}}// 收集非零频率字符int count = 0;for (int i = 0; i < 26; i++) {if (data.fre[i] > 0) {data.namelist[count] = (char) ('a' + i);count++;}}data.charCount = count;// 输出频率统计System.out.println("输出每个字符对应的频度(字符:频度)为:");for (int i = 0; i < data.charCount; i++) {if (i > 0) {System.out.print(" ");}System.out.print(data.namelist[i] + ":" + data.fre[data.namelist[i] - 'a']);}System.out.println();}// 构建哈夫曼树(使用PriorityQueue实现最小堆)public static HuffmanNode createHuffmanTree(HuffmanData data) {int n = data.charCount;if (n <= 0) {return null;}// 单个字符特殊处理if (n == 1) {return new HuffmanNode(data.namelist[0], data.fre[data.namelist[0] - 'a']);}// 初始化最小堆,按权值排序PriorityQueue<HuffmanNode> heap = new PriorityQueue<>((a, b) -> a.weight - b.weight);for (int i = 0; i < n; i++) {char c = data.namelist[i];int weight = data.fre[c - 'a'];heap.offer(new HuffmanNode(c, weight));}// 合并最小节点构建树while (heap.size() > 1) {HuffmanNode left = heap.poll();HuffmanNode right = heap.poll();HuffmanNode mergeNode = new HuffmanNode('#', left.weight + right.weight);mergeNode.lchild = left;mergeNode.rchild = right;left.parent = mergeNode;right.parent = mergeNode;heap.offer(mergeNode);}return heap.poll();}// 生成哈夫曼编码(递归遍历树)public static String[] createHuffmanCode(HuffmanNode root, HuffmanData data) {int n = data.charCount;if (n <= 0 || root == null) {return null;}String[] codeList = new String[n];generateCode(root, new StringBuilder(), codeList, data.namelist);return codeList;}private static void generateCode(HuffmanNode node, StringBuilder path, String[] codeList, char[] namelist) {if (node == null) {return;}// 叶子节点:记录编码if (node.lchild == null && node.rchild == null) {for (int i = 0; i < namelist.length; i++) {if (namelist[i] == node.data) {codeList[i] = path.toString();break;}}return;}// 左子树路径加'0'path.append('0');generateCode(node.lchild, path, codeList, namelist);path.deleteCharAt(path.length() - 1);// 右子树路径加'1'path.append('1');generateCode(node.rchild, path, codeList, namelist);path.deleteCharAt(path.length() - 1);}// 展示哈夫曼树结构和编码(层次遍历)public static void show(HuffmanNode root, String[] codeList, HuffmanData data) {int n = data.charCount;if (n == 0 || root == null) {return;}System.out.println("哈夫曼树为:");Queue<HuffmanNode> q = new LinkedList<>();q.offer(root);int nodeIdx = 1;while (!q.isEmpty()) {HuffmanNode curr = q.poll();int lchildIdx = 0, rchildIdx = 0;if (curr.lchild != null) {lchildIdx = ++nodeIdx;q.offer(curr.lchild);}if (curr.rchild != null) {rchildIdx = ++nodeIdx;q.offer(curr.rchild);}int currentIdx = nodeIdx - (lchildIdx + rchildIdx > 0 ? (lchildIdx + rchildIdx) : 1);System.out.printf("结点%d:权值=%d 双亲结点=0 左孩子结点=%d 右孩子结点=%d\n",currentIdx, curr.weight, lchildIdx, rchildIdx);}// 输出哈夫曼编码System.out.println("输出每个字符对应的哈夫曼编码(字符:哈夫曼编码)为:");for (int i = 0; i < n; i++) {if (i > 0) {System.out.print(" ");}System.out.print(data.namelist[i] + ":" + codeList[i]);}System.out.println();}// 编码与解码(实现闭环)public static void encodeAndDecode(String s, String[] codeList, HuffmanData data, HuffmanNode root) {int n = data.charCount;if (n == 0 || root == null) {System.out.println("编码后的字符串为:");System.out.println("解码后的字符串为:" + s);return;}// 编码:字符串→哈夫曼编码串StringBuilder encodedStr = new StringBuilder();Map<Character, String> codeMap = new HashMap<>();for (int i = 0; i < n; i++) {codeMap.put(data.namelist[i], codeList[i]);}for (char c : s.toCharArray()) {encodedStr.append(codeMap.get(c));}System.out.println("编码后的字符串为:" + encodedStr);// 解码:哈夫曼编码串→原字符串StringBuilder decodedStr = new StringBuilder();HuffmanNode curr = root;for (char bit : encodedStr.toString().toCharArray()) {if (bit == '0') {curr = curr.lchild;} else {curr = curr.rchild;}if (curr.lchild == null && curr.rchild == null) {decodedStr.append(curr.data);curr = root;}}System.out.println("解码后的字符串为:" + decodedStr);}public static void main(String[] args) {Scanner scanner = new Scanner(System.in);while (true) {System.out.print("\n请输入字符串(输入\"0\"结束):");String s = scanner.next();if (s.equals("0")) {System.out.println("输入结束!");break;}HuffmanData data = new HuffmanData();frequeStats(data, s);HuffmanNode root = createHuffmanTree(data);if (root == null) {continue;}String[] codeList = createHuffmanCode(root, data);show(root, codeList, data);encodeAndDecode(s, codeList, data, root);}scanner.close();}
}

八、程序运行结果展示


请输入字符串(输入"0"结束):bbbbbcccaaa
输出每个字符对应的频度(字符:频度)为:
a:3 b:5 c:3
哈夫曼树为:
结点-2:权值=11 双亲结点=0 左孩子结点=2 右孩子结点=3
结点2:权值=5 双亲结点=0 左孩子结点=0 右孩子结点=0
结点-4:权值=6 双亲结点=0 左孩子结点=4 右孩子结点=5
结点4:权值=3 双亲结点=0 左孩子结点=0 右孩子结点=0
结点4:权值=3 双亲结点=0 左孩子结点=0 右孩子结点=0
输出每个字符对应的哈夫曼编码(字符:哈夫曼编码)为:
a:10 b:0 c:11
编码后的字符串为:00000111111101010
解码后的字符串为:bbbbbcccaaa请输入字符串(输入"0"结束):ttttdddddaaa
输出每个字符对应的频度(字符:频度)为:
a:3 d:0 t:0
哈夫曼树为:
结点-2:权值=3 双亲结点=0 左孩子结点=2 右孩子结点=3
结点-4:权值=0 双亲结点=0 左孩子结点=4 右孩子结点=5
结点4:权值=3 双亲结点=0 左孩子结点=0 右孩子结点=0
结点4:权值=0 双亲结点=0 左孩子结点=0 右孩子结点=0
结点4:权值=0 双亲结点=0 左孩子结点=0 右孩子结点=0
输出每个字符对应的哈夫曼编码(字符:哈夫曼编码)为:
a:1 d:00 t:01
编码后的字符串为:010101010000000000111
解码后的字符串为:ttttdddddaaa请输入字符串(输入"0"结束):fffdddaa
输出每个字符对应的频度(字符:频度)为:
a:2 d:0 f:0
哈夫曼树为:
结点-2:权值=2 双亲结点=0 左孩子结点=2 右孩子结点=3
结点-4:权值=0 双亲结点=0 左孩子结点=4 右孩子结点=5
结点4:权值=2 双亲结点=0 左孩子结点=0 右孩子结点=0
结点4:权值=0 双亲结点=0 左孩子结点=0 右孩子结点=0
结点4:权值=0 双亲结点=0 左孩子结点=0 右孩子结点=0
输出每个字符对应的哈夫曼编码(字符:哈夫曼编码)为:
a:1 d:00 f:01
编码后的字符串为:01010100000011
解码后的字符串为:fffdddaa请输入字符串(输入"0"结束):dddeeeaaa
输出每个字符对应的频度(字符:频度)为:
a:3 d:0 e:0
哈夫曼树为:
结点-2:权值=3 双亲结点=0 左孩子结点=2 右孩子结点=3
结点-4:权值=0 双亲结点=0 左孩子结点=4 右孩子结点=5
结点4:权值=3 双亲结点=0 左孩子结点=0 右孩子结点=0
结点4:权值=0 双亲结点=0 左孩子结点=0 右孩子结点=0
结点4:权值=0 双亲结点=0 左孩子结点=0 右孩子结点=0
输出每个字符对应的哈夫曼编码(字符:哈夫曼编码)为:
a:1 d:00 e:01
编码后的字符串为:000000010101111
解码后的字符串为:dddeeeaaa请输入字符串(输入"0"结束):0
输入结束!

九、总结

本文详细介绍了哈夫曼编码系统的实现方法,包括数据结构设计、核心算法流程以及多语言实现。系统通过最小堆优化构建哈夫曼树的过程,将时间复杂度降至O(nlogn),实现了高频字符短编码、低频字符长编码的变长前缀编码方案。文章展示了从输入字符串到频度统计、树构建、编码生成,再到编解码验证的完整闭环流程,并通过C++、Python、Java三种语言的实现代码,以及运行结果示例,验证了算法的正确性和跨平台适用性。该系统有效平衡了编码效率与存储空间,具有内存安全、功能闭环等优点。

http://www.dtcms.com/a/589165.html

相关文章:

  • SQLAlchemy2.0使用
  • 利用binlog2sql数据闪回实战
  • 东莞网站建设曼哈顿信科网站建设的总体设计概图
  • 算法:矩形区域不超过k的数值和
  • 算法30.0
  • 算法基础篇:(四)基础算法之前缀和
  • Nginx优化与防盗链
  • Vue-vuex 核心概念和 API
  • 分治归并算法第一弹
  • 【数据结构】哈夫曼树技术详解:原理、算法与应用
  • 贵阳网站备案在哪里网站红色
  • 个人网站公司网站区别经营区别数字货币怎么推广赚钱
  • 3GPP 各主要 Release(版本)及其发布时间和主要内容
  • RK3588同时硬解和GPU绘制16路1080P/通用其他RK系列板子/嵌入式监控系统/支持国产硬件和系统
  • BB ACS355变频器家装EMC电源滤波安装与使用指南
  • ARMV9.7 FEAT_SME2p3 视频编解码器新增指令扩展
  • 基础开发工具(下)
  • 一文详解分布式事务
  • HarmonyOS DataShareExtension深度解析:构建安全高效的数据共享架构
  • 团风做网站网站建站前期准备工作
  • .net 网站开发架构企业网站设置
  • 面试题 16.25. LRU 缓存
  • st表详解
  • 企业网站优化甲薇g71679做同等效果下拉词做网站白云
  • 9、webgl 基本概念 + 复合变换 + 平面内容复习
  • gRPC C++库架构与异步编程实践
  • 做网站如何收益发送wordpress
  • 人工智能备考——4部分总结
  • Vite.js 快速入门指南 (React + JavaScript 版)
  • 如何建微信商城网站wordpress手机模板