一:什么是哈夫曼树呢?
来看一下百度给的定义吧:给定n个权值作为n个叶子结点,构造一棵二叉树,若带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman tree)。PS:哈夫曼树是在二叉树的基础上定义的。
自己的描述:在树的路径长度的基础上引入了带权路径长度(Weighted Path Length,WPL)。一棵叶子结点带权值(分支节点不带权值)的二叉树叫做扩充二叉树。带权值的结点均为叶子结点,不带权值的均为分支节点(包括根结点),一棵扩充二叉树(带权二叉树)的WPL最小,这么一棵扩充二叉树即为哈夫曼树。下面说明什么是WPL以及如何计算一棵扩充二叉树的WPL:
Wk是每一个结点所带的权值,Lk是每一个结点的路径长度。某一个外结点的带权路径长度为:这两个值的乘积,一棵扩充二叉树的带权路径长度则是所有乘积的和了。
二:如何构造一棵哈夫曼树呢?
哈夫曼树构造韩的核心思想就是:要把权值较大的放在离根结点较近的地方,采用自底向上的构造方式。在这里我利用的是最小堆辅助实现的。
1:先来介绍一下我的最小堆吧:
#include<iostream>
#include<vector>
using namespace std;
const int DefaultSize = 10; //默认最小堆数组元素的个数
template<class T>
class MinHeap
{
protected:
T *heap; //指向一个数组的指针
int currentSize; //最小堆当前的元素个数
int maxHeapSize; //最小堆最大能容纳的元素个数
void SiftDown(int begPos, int endPos); //自上往下调整顺序函数 为对外接口服务 所以置为保护
void SiftUp(int begPos); //自下往上的调整顺序函数 仍为保护
void Display();
public:
MinHeap(int size = DefaultSize); //默认大小的构造函数 构造一个空的最小堆
MinHeap(T arr[], int elemnum); //接受一个T类型数组地址和元素个数,构造一个最小堆
~MinHeap(); //析构函数
bool Insert(const T &elem); //往最小堆里插入一个任意的T类型元素
bool RemoveMinelem(T &relem); //删除最小堆中的优先级最高的元素即为堆顶元素
bool IsFull() const; //判最小堆满 定义为常函数
bool IsEmpty() const; //判最小堆空
void MakeEmpty(); //置最小堆为空
template<class T> friend ostream& operator<< (ostream &output, MinHeap<T> &mh); //友元输出一个最小堆
};
template<class T>
void MinHeap<T>::SiftDown(int begPos, int endPos)
{
int parent_i = begPos;
int child_j = (2 * parent_i) + 1;
T temp = heap[parent_i];
while(child_j <= endPos)
{
if((child_j < endPos) && (heap[child_j] > heap[child_j + 1])) //如果当前parent_i结点有分支并且左分支key大于右分支
{
child_j++; //则把child_j变量转向右分支 总之child_j记录的是parent_i的左右分支中key较小的那个分支
}
if(temp <= heap[child_j])
{
break; //在某次比较中,符合最小堆序关系 则跳出循环
}
else
{
heap[parent_i] = heap[child_j]; //如果需要进一步的交换,则把当前parent_i,child_j两个变量分别下移一层
parent_i = child_j;
child_j = (2 * parent_i) + 1;
}
}//end of while
heap[parent_i] = temp; //跳出循环后的parent_i就指向当前空缺的位置
}
template<class T>
void MinHeap<T>::SiftUp(int begPos)
{
int child_j = begPos; //开始位置一定是一个孩子节点 具体地说是最后一个节点
int parent_i = (child_j - 1) / 2; //找到当前孩子节点的双亲
T temp = heap[child_j];
while(child_j > 0)
{
if(temp < heap[parent_i])
{
heap[child_j] = heap[parent_i];
child_j = parent_i;
parent_i = (child_j - 1) / 2;
}
else
{
break;
}
}//end of while
heap[child_j] = temp;
}
template<class T>
void MinHeap<T>::Display()
{
for(int idx = 0; idx < currentSize; idx++)
{
cout<<"#"<<idx + 1<<": "<<heap[idx]<<endl;
}
}
template<class T>
MinHeap<T>::MinHeap(int size)
{
maxHeapSize = ((size >= DefaultSize) ? size : DefaultSize);
heap = new T[maxHeapSize]; //根据最大尺寸开辟元素空间
if(heap == NULL)
{
cerr<<"开辟空间失败!"<<endl;
exit(1); //强制终止程序
}
currentSize = 0; //置当前堆元素大小为0
}
template<class T>
MinHeap<T>::MinHeap(T arr[], int elemnum)
{
maxHeapSize = ((elemnum >= DefaultSize) ? elemnum : DefaultSize); //确保最小堆的元素空间值最大的
heap = new T[maxHeapSize];
if(heap == NULL)
{
cerr<<"开辟空间失败!"<<endl;
exit(1);
}
for(int idx = 0; idx < elemnum; idx++)
{
heap[idx] = arr[idx]; //从一个数组中把元素依依复制到最小堆空间中
}
currentSize = elemnum; //当前最小堆元素个数为源数组元素个数
int CurrentPos = (currentSize - 2) / 2; //定起始调整位置,即为最小的分支节点,即为最后一个元素的双亲
while(CurrentPos >= 0) //循环调整 直至到最小堆顶
{
SiftDown(CurrentPos, currentSize - 1); //调用下调函数
CurrentPos--; //起始调整位置转到下一个分支节点
}
}
template<class T>
MinHeap<T>::~MinHeap()
{
delete []heap; //一定要加上方括号,因为要删除的是一个连续的数组空间,如果不加括号则删除的仅仅是数组的第一个元素,剩下的都将成为"内存黑洞"(被占用的不能被利用的内存空间)
}
template<class T>
bool MinHeap<T>::Insert(const T &elem)
{
if(currentSize == maxHeapSize)
{
cout<<"最小堆满,不能插入!"<<endl;
return false;
}
heap[currentSize] = elem; //放置在堆数组的最后一个
SiftUp(currentSize); //最后一个叶子节点开始上浮调整
currentSize++; //当前元素个数加1
return true;
}
template<class T>
bool MinHeap<T>::RemoveMinelem(T &relem)
{
if(currentSize == 0)
{
cout<<"最小堆为空,无法执行删除操作!"<<endl;
return false;
}
relem = heap[0]; //把最小堆顶元素赋给引用参数relem
heap[0] = heap[currentSize - 1]; //将最小堆的最后一个元素赋给最小堆顶
currentSize--; //最小堆元素个数减1
SiftDown(0,currentSize - 1);
return true;
}
template<class T>
bool MinHeap<T>::IsEmpty() const
{
return ((currentSize == 0) ? true : false);
}
template<class T>
bool MinHeap<T>::IsFull() const
{
return ((currentSize == maxHeapSize - 1) ? true : false);
}
template<class T>
void MinHeap<T>::MakeEmpty()
{
currentSize = 0; //置最小堆为空
}
template<class T>
ostream& operator<< (ostream &output, MinHeap<T> &mh)
{
for(int idx = 0; idx < mh.currentSize; idx++)
{
output<<"第"<<idx + 1<<"个元素: "<<(mh.heap)[idx]<<endl;
}
return output;
}
代码中的注释已经很全了,所以在这里不再赘述,上述代码利用的是模板,所以下面贴的是自定义的类以及主函数中的调用,运行截图等
#pragma once
#include<fstream>
#include<string>
using namespace std;
class City
{
private:
string m_name;
int m_key;
public:
City();
City(string name, int key);
~City();
friend ostream& operator<< (ostream &output, City &C);
friend ofstream& operator<< (ofstream &foutput, City &C);
friend istream& operator>> (istream &input, City &C);
friend ifstream& operator>> (ifstream &finput, City &C);
void operator= (City &C);
bool operator<= (City &C);
bool operator> (City &C);
};
#include<fstream>
#include"City.h"
using namespace std;
City::City()
{
}
City::City(string name, int key)
{
m_name = name;
m_key = key;
}
City::~City()
{
}
ostream& operator<< (ostream &output, City &C)
{
output<<C.m_name<<" "<<C.m_key ;
return output;
}
ofstream& operator<< (ofstream &foutput, City &C)
{
foutput<<C.m_name<<" "<<C.m_key;
return foutput;
}
istream& operator>> (istream &input, City &C)
{
input>>C.m_name>>C.m_key;
return input;
}
ifstream& operator>> (ifstream &finput, City &C)
{
finput>>C.m_name>>C.m_key;
return finput;
}
void City::operator= (City &C)
{
this->m_name = C.m_name;
this->m_key = C.m_key;
}
bool City::operator<= (City &C)
{
return ((this->m_key <= C.m_key) ? true : false);
}
bool City::operator> (City &C)
{
return ((this->m_key > C.m_key) ? true : false);
}
上述自定义类更多的是重载输入输出以及运算符等。
下面是主函数调用:
City city[8]; //自定义类对象数组
ifstream in("CITY.txt"); //对象都在文件中
for(int i = 0; i < 8; i++)
{
in>>city[i]; //这里用到文件输入流重载
}
MinHeap<City> mheap(city, 8);
cout<<mheap<<endl; //上述代码是用来显示最小堆中的各个元素 没有顺序的
//下面这段代码是每一次取出当前优先级最高的
City elem;
for(int i = 0; i < 8; i++)
{
mheap.RemoveMinelem(elem);
cout<<elem<<endl;
}
运行截图:
好了,先介绍到最小堆的实现,剩下的的在哈夫曼树详解续中介绍。