哈夫曼树详解

一:什么是哈夫曼树呢?

来看一下百度给的定义吧:给定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;
	}
运行截图:

哈夫曼树详解_第1张图片

好了,先介绍到最小堆的实现,剩下的的在哈夫曼树详解续中介绍。


你可能感兴趣的:(C++,C++,语言)