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

数据结构::如何实现哈夫曼树

2017-01-01 20:13 423 查看

【哈夫曼树的定义】:


哈夫曼树又称为最优二叉树,它是加权路径最短的二叉树。

不同于普通的二叉树,它的每个节点都有相应的权值,当我们构建的时候最终离根节点远的节点权重小,反之就比较大。



【哈夫曼树的实现】:

1、利用的核心思想:贪心算法


贪心算法:是指在问题进行求解的时候,总是做出当前看起来最好的选择。即就是说贪心算法做出不是整体的最优解,而是某种意义上的局部最优解。它不是对所有的问题都能得到整体最优解。

2、如何构建:

1)节点的设置:

我们了解哈夫曼树的定义后,我们知道就比普通的二叉树多了一个权重,因此,我们可以这样进行设计。(注意:我在这里给出了父节点可以不给出父节点,因为此时的构建两个孩子节点就足够了,我这里加上也就是为了稍稍的复习下三叉链)

template<class T>
struct HuffmanTreeNode
{
T _weight;
HuffmanTreeNode<T>* _left;
HuffmanTreeNode<T>* _right;
HuffmanTreeNode<T>* _parent;
HuffmanTreeNode(const T& x)
:_left(NULL)
,_right(NULL)
,_parent(NULL)
,_weight(x)
{}
//此处的重载是后面的构建要用到,读者先不用看,到后面知道如何构建的时候,再回过头来串一遍
HuffmanTreeNode operator+(const HuffmanTreeNode& x)
{
return _weight+x._weight;
}
};


2)哈夫曼树构造函数的实现

//构造函数
HuffmanTree()
:_root(NULL)
{}
HuffmanTree(T*a, size_t n,const T& invalue)
{
//因为在构建哈夫曼数的时候,比较的是两个权重的大小
//而权重的类型是Node*,所以要进行重载
struct com
{
bool operator()(Node* l,Node* r)
{
return l->_weight < r->_weight;
}

};
//利用堆来实现数据的存储
Heap<Node* ,com> hp;
for(size_t i = 0; i<n; i++)
{
if(a[i] != invalue)
hp.Push(new Node(a[i]));
}
//然后再进行构建哈夫曼树
//构建的最后,数组里会只剩下一个元素
while(hp.Size()>1)
{
//先取出堆顶的元素给左孩子
Node* left = hp.Top();
//出堆
hp.Pop();
//再取出堆顶的元素给右孩子
Node* right = hp.Top();
//出堆
hp.Pop();
//然后左右构建出一个父节点,即两者的权重之和
//(注意:此处需要进行重载+)
Node* parent = new Node(left->_weight+right->_weight);
//将节点要进行连接
parent->_left = left;
parent->_right = right;
//下面的这两条语句用于构建三叉链
right->_parent = parent;
left->_parent = parent;
//要将构建完后的节点扔回来,因为它可能不是最小的
hp.Push(parent);
}
//出循环后,数组中的存储就剩最后的一个节点,此节点就是根
_root = hp.Top();
}


【说明】:

*这里我写的很详细,我写了两个构造函数,一个是无参的,一个是通过数组来进行构建。

*我们在构建哈夫曼树的时候,是从数组中选取两个权重小的数,因此我们这里就会出现各种各样的办法

方法一:首先想到的就是排序,如果我们利用排序的话,每次取出数之后,前面我说到每次取出两个数进行处理后要将它们的父节点再扔回去,此时,当扔回去的时候,又要进行重新排序,所以利用数组的话,代价会很大

方法二:利用遍历,每次遍历找出最小的一个,重复,它的时间复杂度是O(n^2),效率比较低,所以不建议采取

方法三:利用堆采用这种方法的时间复杂度是O(nlg(n)),效率会比较高,所以这里我们采用这种方法

【说明】:此处我们要注意的一个点是,用堆的话,我们在堆里要用什么,怎么放的问题?

我们如果在堆里放节点的话就太大了(因为此处是结构体),应放节点的指针(来根据节点的权值 选择。):即就是这里的Heap<Node*>hp

*我这里是用我自己实现的堆来进行实现的,读者也可以用库里的STL 算法中的几个有关接口来进行实现

3)析构函数的实现

因为哈夫曼树也是二叉树,所以在析构的时候也要用到递归,和普通的二叉树的析构一样。

//析构函数
~HuffmanTree()
{
if(_root)
Destory(_root);
_root = NULL;
}
void Destory(Node* root)
{
if(root == NULL)
return;
Destory(root->_left);
Destory(root->_right);
delete root;
}


【哈夫曼树的应用】:

1、哈夫曼编码:

关于哈夫曼的应用,在哈夫曼树构建好后,它的节点向左我们标记为0,向右我们标记为1,这样每个节点到根节点都会有自己的一系列关于0,1的序列,次序列就是哈夫曼编码。

2、经典应用:文件压缩

此处我就先不展开了,具体的我在后面开一篇博客来详细的进行讲述,如何进行文件压缩
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: