数据结构笔记--haffman树与haffman编码分析
2016-07-23 19:14
375 查看
构建haffman树思想是把所有的字符挂到一颗完全二叉树的叶子节点,任何一个非页子节点的左节点出现频率不大于右节点。算法为把统计信息转为Node存放到一个数组里面,每一次从数组里面删选两个最小的节点,构建一个新的父Node(非叶子节点), 字符内容刚弹出来的两个节点字符内容之和,最开始的弹出来的作为左子节点,后面一个作为右子节点,并且把刚构建的父节点放到数组里面。重复以上的动作N-1次,N为不同字符的个数(每一次数组里面个数减1)。结束以上步骤,数组里面剩一个节点,弹出作为树的根节点。代码重点在与从数组中找出最小与次小值。
定义一颗haffman树节点的结构体如下:
给出函数原型:HaffTree* CreateHaff(int* weight, int len, HaffTree *pHT);
void HaffCode(HaffTree* pHT, int len, vector* vCode);
首先,要创建一颗haffman树,需要一个整形数组,根据数组元素创建。有数组,为了可扩展性,那就把长度也传进来吧。注意,我们认为,haffman编码的时候,给定一颗haffman树,第二个参数len保存的是weight数组的长度,如果你的需求不一样,可以自行修改。只需注意,weight数组的长度len1与创建后的haffman树数组长度len2的对应关系是len2 = len1*2-1。
在这里先解释一下haffman编码算法,我们在创建出来一颗二叉树之后,就需要编码,编码的原则是左路径为0,右路径为1。我们定义指针pChild,pParent (数组的指针就是整形下标),让pChild指向第一个叶子节点,让pParent指向pChild指向节点的父节点。
然后判断pChild指向的节点是左孩子还是右孩子,分别赋0 或者 1,接着让pChild指向它自己的父节点,同样让pParent指向它自己的父节点,直到pChild指向了根节点(因为在初始化的时候,根节点的父节点被初始化为-1,而且创建完成之后,仅有根节点的父节点为-1,所以循环结束条件就是判断pChild指向节点的parent是否等于-1)。这是一次编码。遍历完所有的叶子节点,编码结束。因为haffman编码是从根到叶子节点依次编码,而我们的算法是从叶子节点遍历到根节点,所以我们在给pTempCode数组赋值的时候从后往前赋值。
尽量让代码符合编程规范,尽量提高可读性与健壮性,但是能力有限,有错误或者不规范的地方请提出来,大家一起交流。
9fba
定义一颗haffman树节点的结构体如下:
typedef struct hafftree { int m_weight; // 权重 int m_lchild; // 左孩子节点位置 int m_rchild; // 右孩子节点位置 int m_parent; // 父节点位置 bool m_flag; // 是否遍历过 }HaffTree;
给出函数原型:HaffTree* CreateHaff(int* weight, int len, HaffTree *pHT);
void HaffCode(HaffTree* pHT, int len, vector* vCode);
首先,要创建一颗haffman树,需要一个整形数组,根据数组元素创建。有数组,为了可扩展性,那就把长度也传进来吧。注意,我们认为,haffman编码的时候,给定一颗haffman树,第二个参数len保存的是weight数组的长度,如果你的需求不一样,可以自行修改。只需注意,weight数组的长度len1与创建后的haffman树数组长度len2的对应关系是len2 = len1*2-1。
#define MAX 0x7fffffff // haffman树的创建 void CreateHaff(const int* weight, const int len, HaffTree* pHT) { int i = 0, k = 0; // 初始化工作 for (; i < (2 * len) - 1; ++i) { if (i < len) { pHT[i].m_weight = weight[i]; } else { pHT[i].m_weight = -1; } pHT[i].m_lchild = -1; pHT[i].m_rchild = -1; pHT[i].m_parent = -1; pHT[i].m_flag = false; } // 开始创建haffman树 for (i = 0; i < len - 1; ++i) { // 找到数组中最小与次小的值,并保存它们的位置 int min1 = MAX; int min2 = MAX; int index1 = -1; int index2 = -1; for (k = 0; k < len + i; ++k) { if (!pHT[k].m_flag && min1 > pHT[k].m_weight) { min2 = min1; index2 = index1; min1 = pHT[k].m_weight; index1 = k; } else if (!pHT[k].m_flag && min2 > pHT[k].m_weight) { min2 = pHT[k].m_weight; index2 = k; } } // 找到之后开始赋值 pHT[k].m_weight = min1 + min2; pHT[k].m_lchild = index1; pHT[k].m_rchild = index2; pHT[index1].m_flag = true; pHT[index2].m_flag = true; pHT[index1].m_parent = k; pHT[index2].m_parent = k; } }
在这里先解释一下haffman编码算法,我们在创建出来一颗二叉树之后,就需要编码,编码的原则是左路径为0,右路径为1。我们定义指针pChild,pParent (数组的指针就是整形下标),让pChild指向第一个叶子节点,让pParent指向pChild指向节点的父节点。
然后判断pChild指向的节点是左孩子还是右孩子,分别赋0 或者 1,接着让pChild指向它自己的父节点,同样让pParent指向它自己的父节点,直到pChild指向了根节点(因为在初始化的时候,根节点的父节点被初始化为-1,而且创建完成之后,仅有根节点的父节点为-1,所以循环结束条件就是判断pChild指向节点的parent是否等于-1)。这是一次编码。遍历完所有的叶子节点,编码结束。因为haffman编码是从根到叶子节点依次编码,而我们的算法是从叶子节点遍历到根节点,所以我们在给pTempCode数组赋值的时候从后往前赋值。
// haffman树编码,将结果保存到vector<string>中返回 void Haffcode(const HaffTree* pHT, const int len, vector<string>* vCode) { int pChild = 0, pParent = 0; char* pTempCode = new char[len]; // 控制遍历haffman树叶子节点 for (int i = 0; i < len; ++i) { pChild = i; pParent = pHT[pChild].m_parent; int k = len - 2; // 之所以减2,是因为节点对应的01值反向保存,最后一位为'\0',数组下标往前一位 // 控制遍历次数,从叶子节点到根节点 while (pHT[pChild].m_parent != -1) { // 反向保存,左孩子取0,右孩子取1 if (pHT[pParent].m_lchild == pChild) { pTempCode[k--] = '0'; } if (pHT[pParent].m_rchild == pChild) { pTempCode[k--] = '1'; } pChild = pParent; pParent = pHT[pParent].m_parent; } pTempCode[len - 1] = '\0'; // 将临时变量的值赋给入参vCode (*vCode).push_back(string(pTempCode + k + 1)); } // 释放空间 delete[] pTempCode; } // 因为haffman创建有很多种可能形态,程序创建的有可能与你想的有出入,而且本程序没有将haffman树打印输出,所以,建议调试运行,找到各个节点的parent和child画出来,然后判断程序执行是否有误 int main(void) { int weight[] = { 1, 2, 4, 8, 10 }; int len = sizeof(weight) / sizeof(weight[0]); HaffTree* root = (HaffTree*)malloc(sizeof(HaffTree)*((2 * len) + 1)); CreateHaff(weight, len, root); vector<string> vCode; Haffcode(root, len, &vCode); // 输出haffman编码 for (auto p = vCode.begin(); p != vCode.end(); ++p) { cout << *p << endl; } system("pause"); return 0; }
尽量让代码符合编程规范,尽量提高可读性与健壮性,但是能力有限,有错误或者不规范的地方请提出来,大家一起交流。
9fba
相关文章推荐
- 数据结构笔记--通过与BF算法的比较理解KMP算法
- 数据结构笔记--循环队列分析
- 数据结构笔记--二叉树的非递归遍历与按层遍历分析
- php基本语法及基本数据结构(一)
- POJ 3450--->Corporate Identity(后缀数组求多个字符串的公共子串)
- SDUT 3346 数据结构实验之二叉树七:叶子问题
- HDU 1671 phone list 数据结构+Trie树(字典树、前缀树)
- SDUT 3344 数据结构实验之二叉树五:层序遍历
- 线性表——单链表
- 线性表——顺序表
- 《数据结构》2.6单链表应用举例
- POJ 2406 Power Strings 数据结构+KMP
- HDU 1867 A + B for you again 数据结构+KMP简单应用
- SDUT 2136 数据结构实验之二叉树的建立与遍历
- (模板题)sdut 2125 数据结构实验之串二:字符串匹配(KMP)
- HDU 3746 Cyclic Nacklace 数据结构+kmp周期类问题
- 二叉搜索树的插入和删除结点操作以及iterator的构造
- (模板题)sdut 3362 数据结构实验之图论六:村村通公路(prim求最小生成树)
- 复习(数据结构):字符串:c语言
- 数据结构-树-二叉树