您的位置:首页 > 理论基础 > 数据结构算法

数据结构笔记--haffman树与haffman编码分析

2016-07-23 19:14 375 查看
构建haffman树思想是把所有的字符挂到一颗完全二叉树的叶子节点,任何一个非页子节点的左节点出现频率不大于右节点。算法为把统计信息转为Node存放到一个数组里面,每一次从数组里面删选两个最小的节点,构建一个新的父Node(非叶子节点), 字符内容刚弹出来的两个节点字符内容之和,最开始的弹出来的作为左子节点,后面一个作为右子节点,并且把刚构建的父节点放到数组里面。重复以上的动作N-1次,N为不同字符的个数(每一次数组里面个数减1)。结束以上步骤,数组里面剩一个节点,弹出作为树的根节点。代码重点在与从数组中找出最小与次小值。

定义一颗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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: