数据结构与算法_【7】哈夫曼树(C++实现)

参考:数据结构与算法基础(青岛大学-王卓)
传送门:
数据结构与算法_【1】概念引入(C++实现)
数据结构与算法_【2】线性表(顺序表链表)(C++实现)
数据结构与算法_【3】栈和队列(C++实现)
数据结构与算法_【4】串数组广义表(C++实现)
数据结构与算法_【5】树和二叉树(C++实现)
数据结构与算法_【6】树和森林(C++实现)
数据结构与算法_【7】哈夫曼树(C++实现)
数据结构与算法_【8】图(C++实现)
数据结构与算法_【9】查找(C++实现)
数据结构与算法_【10】排序(C++实现)

哈夫曼树

判断树:用于描述分类过程的二叉树
不同判断树的判断效率不同—>哈夫曼树(最优二叉树)

1 哈夫曼树基本概念

路径:从树中一个结点到另一个结点之间的分支构成这两个结点间的路径
结点路径的长度:两结点间路径上的分支数
树的路径长度:从树根到每一个结点的路径长度之和,记作:TL
权(weight):将树中结点赋给一个有着某种含义的数值,则这个数值称为该结点的权
结点的带权路径长度:从根结点到该结点之间的路径长度与该结点的权的乘积
树的带权路径长度:树中所有叶子结点的带权路径长度之和,记作WPL (注意只需要乘叶子结点一个权)

哈夫曼树:最优树!带权路径长度(WPL)最短的树

注意:“带权路径长度最短”是在“度”相同的树中比较而得的结果,因此有最优二叉树,最优三叉树之称等。

满二叉树不一定是哈夫曼树,具有相同带权结点的哈夫曼树不唯一。

2 哈夫曼树的构造算法

哈夫曼树中权越大的叶子离根越近
贪心算法:构造哈夫曼树时首先选择权值小的叶子结点

构造哈夫曼树算法步骤:
(1)构造森林全是根,(2)选用两小造新树,(3)删除两小添新人,(4)重复2、3剩单根

数据结构与算法_【7】哈夫曼树(C++实现)_第1张图片

哈夫曼树的结点的度数为0或2,没有度为1的结点!
包含n各叶子结点的哈夫曼树中一定有2n-1个结点!
包含n棵树的森林要经过n-1次合并才能形成哈夫曼树,共产生n-1个新结点

3 哈夫曼树算法的实现

采用顺序存储结构->一维结构数组
结点类型定义:包含weigth,parent,lch,rch

数据结构与算法_【7】哈夫曼树(C++实现)_第2张图片

代码:

void HuffmanTree::CreatHuffmanTree(float *weigth, int n)
{
	if (n <= 1)
	{
		return;
	}
	//计算最终存储哈夫曼树所需的存储空间
	int m = 2 * n - 1;
	//创建存储空间
	this->HT = new H_Node[m + 1];//从序号1开始储存,方便计算
	//将原始结点输入到哈夫曼树存储空间内
	for (int i = 1; i <= n; i++)
	{
		this->HT[i].weight = weigth[i-1];
	}
	//this->ShowHuffmanTree(n);

	//构造哈夫曼树,
	H_Node* p = HT;
	for (int j = n + 1; j <= m; j++)
	{
		//提取未使用的,最小的两个结点
		int S1 = 0, S2 = 0;
		int count1 = 0, count2 = 0;
		//提取	S1
		for (int k = 1; k <= j - 1; k++)
		{
			if(p[k].partent == 0)
			{ 
				if (count1 == 0)//第一次循环时给S1赋初始值
				{
					S1 = k;
					count1++;
				}
				if (p[S1].weight >= p[k].weight)
				{
					S1 = k;
				}
			}
		}
		//提取	S2
		for (int h = 1; h <= j - 1; h++)
		{
			if(p[h].partent == 0 && h != S1)//如果双亲为0,才进行比较
			{
				if (count2 == 0)//第一次循环时给S2赋初始值
				{
					S2 = h;
					count2++;
				}
				if (p[S2].weight >= p[h].weight)
				{
					S2 = h;
				}
			}
		}
		//cout << S1 << " " << S2 << endl;
		HT[S1].partent = j;
		HT[S2].partent = j;
		HT[j].lch = S1;
		HT[j].rch = S2;
		HT[j].weight = HT[S1].weight + HT[S2].weight;
	}

	this->ShowHuffmanTree(m);
}

输出:

数据结构与算法_【7】哈夫曼树(C++实现)_第3张图片

4 哈夫曼编码

设计一种编码方式:

数据结构与算法_【7】哈夫曼树(C++实现)_第4张图片

什么样的前缀码能够使得电文总长最短? —>哈夫曼编码
编码步骤:

数据结构与算法_【7】哈夫曼树(C++实现)_第5张图片

案例:

数据结构与算法_【7】哈夫曼树(C++实现)_第6张图片

两个问题:

数据结构与算法_【7】哈夫曼树(C++实现)_第7张图片

4 哈夫曼编码算法的实现

实现步骤:
先将数据存储到哈夫曼树中,然后遍历n个子结点,左子树路径赋值0,右子树路径赋值1,使用一个字符数组(大小为n-1,并且最后一位赋值‘\0’,因为n个数据,最多产生n-2个路径)存储遍历过程中的路径值,最后将路径值反转存储到编码数组中即可。

数据结构与算法_【7】哈夫曼树(C++实现)_第8张图片

代码:

//哈夫曼树已经在构造函数中直接创建了,这里直接使用即可!!!
void HuffmanTree::CreatHuffmanCode(float* weight, int n)
{
	H_Node* HT = CreatHuffmanTree(weight, n);
	for (int i = 1; i <= n; i++)
	{
		char* cd = new char[n];//建立存放临时编码的字符串,反向存储,正向读取
		cd[n - 1] = '\0';
		int start = n - 1;
		int c = i;//临时存放结点的索引
		int f = HT[i].partent;//保存结点的双亲索引
		while (f != 0)//双亲不为0,说明没有编码结束,一直执行
		{
			--start;//临时字符串数组的指针先指向倒数第二个位置
			if (HT[f].lch == c)//左节点给0
			{
				cd[start] = '0';
			}
			else//右结点给1
			{
				cd[start] = '1';
			}
			c = f;
			f = HT[f].partent;
		}

		//将临时存储的数组,正向复制到code属性中
		HT[i].code = new char[n - start];
		for (int j = start; j <= n - 1; j++)
		{
			HT[i].code[j-start] = cd[j];
		}
		//delete cd;
	}
	this->ShowHuffmanCode(n);
	
}

输出:

数据结构与算法_【7】哈夫曼树(C++实现)_第9张图片

5 解码

数据结构与算法_【7】哈夫曼树(C++实现)_第10张图片数据结构与算法_【7】哈夫曼树(C++实现)_第11张图片

6 完整代码(可直接运行)

#pragma once
#include
using namespace std;
#include"seqList.hpp"
//#include"string.h"

class HuffmanTree;

class H_Node {

private:
	float weight;
	int partent, lch, rch;
	//string ch;
	char *code;//定义存储哈夫曼编码的字符串指针备用
public:

	friend class HuffmanTree;//友元类,能够访问私有属性
	H_Node()//构造函数
	{
		this->weight = 0;
		this->partent = 0;
		this->lch = 0;
		this->rch = 0;
		this->code = NULL;
		
	}
};


class HuffmanTree {

private:
	H_Node *HT;
public:
	HuffmanTree();//默认构造
	H_Node* CreatHuffmanTree(float *weight,int n);//有参构造
	void ShowHuffmanTree(int lenth);//打印哈夫曼树
	void CreatHuffmanCode(float* weight,int n);//通过HC返回
	void ShowHuffmanCode(int n);

	~HuffmanTree();//析构函数

};

HuffmanTree::HuffmanTree()
{

}



//输入权值和结点数
H_Node* HuffmanTree::CreatHuffmanTree(float*weigth, int n)
{
	if (n <= 1)
	{
		return NULL;
	}
	//计算最终存储哈夫曼树所需的存储空间
	int m = 2 * n - 1;
	//创建存储空间
	this->HT = new H_Node[m + 1];//从序号1开始储存,方便计算
	//将原始结点输入到哈夫曼树存储空间内
	for (int i = 1; i <= n; i++)
	{
		this->HT[i].weight = weigth[i-1];
	}
	//this->ShowHuffmanTree(n);

	//构造哈夫曼树,
	H_Node* p = HT;
	for (int j = n + 1; j <= m; j++)
	{
		//提取未使用的,最小的两个结点
		int S1 = 0, S2 = 0;
		int count1 = 0, count2 = 0;
		//提取	S1
		for (int k = 1; k <= j - 1; k++)
		{
			if(p[k].partent == 0)
			{ 
				if (count1 == 0)//第一次循环时给S1赋初始值
				{
					S1 = k;
					count1++;
				}
				if (p[S1].weight >= p[k].weight)
				{
					S1 = k;
				}
			}
		}
		//提取	S2
		for (int h = 1; h <= j - 1; h++)
		{
			if(p[h].partent == 0 && h != S1)//如果双亲为0,才进行比较
			{
				if (count2 == 0)//第一次循环时给S2赋初始值
				{
					S2 = h;
					count2++;
				}
				if (p[S2].weight >= p[h].weight)
				{
					S2 = h;
				}
			}
		}
		//cout << S1 << " " << S2 << endl;
		//存储双亲结点、孩子结点的index,构造新的结点
		HT[S1].partent = j;
		HT[S2].partent = j;
		HT[j].lch = S1;
		HT[j].rch = S2;
		HT[j].weight = HT[S1].weight + HT[S2].weight;
	}

	this->ShowHuffmanTree(m);
	return HT;
}

void HuffmanTree::ShowHuffmanTree(int lenth)
{
	cout << "index" << "\t" << "weigth" << "\t" << "parent" << "\t" << "lch" << "\t" << "rch" << endl;
	for (int i = 0; i <= lenth; i++)
	{
		cout << i << "\t" << HT[i].weight << "\t" << HT[i].partent << "\t" << HT[i].lch << "\t" << HT[i].rch << endl;
	}


}


//哈夫曼树已经在构造函数中直接创建了,这里直接使用即可!!!
void HuffmanTree::CreatHuffmanCode(float* weight, int n)
{
	H_Node* HT = CreatHuffmanTree(weight, n);
	for (int i = 1; i <= n; i++)
	{
		char* cd = new char[n];//建立存放临时编码的字符串,反向存储,正向读取
		cd[n - 1] = '\0';
		int start = n - 1;
		int c = i;//临时存放结点的索引
		int f = HT[i].partent;//保存结点的双亲索引
		while (f != 0)//双亲不为0,说明没有编码结束,一直执行
		{
			--start;//临时字符串数组的指针先指向倒数第二个位置
			if (HT[f].lch == c)//左节点给0
			{
				cd[start] = '0';
			}
			else//右结点给1
			{
				cd[start] = '1';
			}
			c = f;
			f = HT[f].partent;
		}

		//将临时存储的数组,正向复制到code属性中
		HT[i].code = new char[n - start];
		for (int j = start; j <= n - 1; j++)
		{
			HT[i].code[j-start] = cd[j];
		}
		//delete cd;
	}
	this->ShowHuffmanCode(n);
	
}

void HuffmanTree::ShowHuffmanCode(int n)
{
	cout << "index" << "\t" << "weigth" << "\t" << "code" << endl;
	for (int i = 1; i <= n; i++)
	{
		cout << i << "\t" << HT[i].weight << "\t";
		//遍历打印字符串数组
		char* p = HT[i].code;
		while(*p)//判断是否到截止符号"\0"
		{
			cout << *(p++);
		}
		cout<<endl;
	}
}

HuffmanTree::~HuffmanTree()
{

}

测试:

void test08()
{
	HuffmanTree HT;
	float weigth[7] = {0.4,0.3,0.15,0.05,0.04,0.03,0.03 };
	int n = 7;
	//HT.CreatHuffmanTree(weigth,n);
	HT.CreatHuffmanCode(weigth, n);


}

数据结构与算法_【7】哈夫曼树(C++实现)_第12张图片

你可能感兴趣的:(数据结构与算法学习笔记,数据结构,算法,二叉树,c++)