哈夫曼树-创建,编码,解码,带权路径长度(含全部代码)

目录

主要函数

所选实例

创建哈夫曼树

步骤

【分析】

哈夫曼树结构

注意项

代码

创建哈夫曼树结果截图

编码

【分析】

代码

哈夫曼树编码结果截图

解码

【分析】

代码

哈夫曼树解码结果截图

计算带权路径长度

【分析】

代码

计算带权路径长度结果截图

全部代码


主要函数

void CreateHT()创建哈夫曼树
void Code() 哈夫曼树编码
void Encode() 哈夫曼树解码
void WPL() 计算带权路径长度

所选实例

所选实例

 

创建哈夫曼树

步骤

假设有n个权值,则构造出的哈夫曼树有n个叶子结点。 n个权值分别设为 w1、w2、…、wn,则哈夫曼树的构造规则为:

(1) 将w1、w2、…,wn看成是有n 棵树的森林(每棵树仅有一个结点);

(2) 在森林中选出两个根结点的权值最小的树合并,作为一棵新树的左、右子树,且新树的根结点权值为其左、右子树根结点权值之和;

(3)从森林中删除选取的两棵树,并将新树加入森林;

(4)重复(2)、(3)步,直到森林中只剩一棵树为止,该树即为所求得的哈夫曼树。

【分析】

采用双亲孩子表示法,存在数组之中,记录下标。辅助函数进行两棵根权值最小的树的选择,用标志数组标志是否已加入哈夫曼树。

哈夫曼树结构

//哈夫曼树结点结构
typedef struct HNode 
{
	char data; //数据,非叶节点为NULL
	double weight;//权重
	int parent;//双亲,-1表示没有双亲,即根节点
	int lchild;//左孩子,数组下标,-1表示无左孩子,即叶节点
	int rchild;//右孩子
}Hnode;

注意项

辅助函数进行两棵最小权值树的挑选时,可能只剩下最后一棵树,注意进行判断。

代码

//创建哈夫曼树
void CreateHT()
{
	int n;//字符个数,即哈夫曼树叶节点个数
	minnodes mins;
	cout << "请输入字符个数:" << endl;
	cin >> n;
	cout << "请输入字符及权值:" << endl;
	for (int i=0;i<n;i++)
	{
		cin >> HT[i].data >> HT[i].weight;
		HT[i].lchild = -1;HT[i].rchild = -1;
	}
	/*
	HT[0].data = 'a';HT[0].weight = 45;HT[0].lchild = -1;HT[0].rchild = -1;
	HT[1].data = 'b';HT[1].weight = 13;HT[1].lchild = -1;HT[1].rchild = -1;
	HT[2].data = 'c';HT[2].weight = 12;HT[2].lchild = -1;HT[2].rchild = -1;
	HT[3].data = 'd';HT[3].weight = 16;HT[3].lchild = -1;HT[3].rchild = -1;
	HT[4].data = 'e';HT[4].weight = 9; HT[4].lchild = -1;HT[4].rchild = -1;
	HT[5].data = 'f';HT[5].weight = 5; HT[5].lchild = -1;HT[5].rchild = -1;*/
	int i = n;
	for (;;i++)
	{
		mins = Select(i);//找到两棵根权值最小的树
		if (mins.flag == false)//仅剩余一棵树时跳出
		{
			HT[mins.m1].parent = -1;
			break;
		}			
		HT[i].weight = HT[mins.m1].weight + HT[mins.m2].weight;//新加入哈夫曼树结点为两个结点权值之和
		HT[i].data = ' ';
		HT[mins.m1].parent = i;                                //两个权值最小结点双亲为新加入结点
		HT[mins.m2].parent = i;
		HT[i].lchild = mins.m1;//左小又大
		HT[i].rchild = mins.m2;
	} 
	PrintHT(i);//打印哈夫曼树
}

创建哈夫曼树结果截图

创建结果截图

编码

【分析】

从叶子结点出发,向根前进,进一步进行判断,左孩子为0,右孩子为1。最后逆序保存即可。这里用了reverse函数,HC就是哈夫曼树叶节点的备份,加了编码,也可直接将编码作为哈夫曼树结点的属性,那样不太好,不需要编码时冗余,所以又弄了一个数组。

代码

//哈夫曼树编码
void Code()
{
	int i = 0;
	for (;;i++)//给所有叶子结点编码
	{
		int j = i;
		string str="";
		HC[i].data = HT[i].data;//复制数据
		while (-1!=HT[j].parent)//从叶节点找到根
		{
			if (HT[HT[j].parent].lchild == j)//左0右1
			{
				str += '0';
			}
			else
			{
				str += '1';
			}
			j = HT[j].parent;
		}
		reverse(str.begin(),str.end());//逆序
		HC[i].code = str;              //保存至编码
		if (HT[i].lchild == -1 && HT[i].rchild == -1)continue;//非叶子不编码
		else break;
	}
	PrintHC(i);
}

哈夫曼树编码结果截图

哈夫曼树编码截图

解码

【分析】

解码就是编码的逆过程,从根开始,左0右1,到叶节点输出。再回到根节点重复即可。

代码

//哈夫曼树解码 从根开始,左0右1,直至叶节点
void Encode() 
{
	string s;
	int root=0;//记录根节点的下标
	cout << "请输入01字符串:" << endl;
	cin >> s;
	while (HT[root].parent != -1) root++;
	int j = root;
	for (int i=0;i<s.length();i++)//遍历输入的01串
	{
		if ('0' == s[i])
		{
			j = HT[j].lchild;
		}
		else
		{
			j = HT[j].rchild;
		}
		if (HT[j].lchild == -1 && HT[j].rchild == -1)//到达叶节点
		{
			cout << HT[j].data;
			j = root;//返回根节点继续
		}
	}
	cout << endl;
}

哈夫曼树解码结果截图

哈夫曼树解码结果截图

计算带权路径长度

【分析】

树的带权路径长度为所有叶节点的带权路径长度之和。叶节点的带权路径长度即叶节点的权值与从根节点到叶节点的路径长度之积。前面已经算过编码,用编码长度代替路径长度即可。本篇文章不针对某题,如果有题仅需计算带权路径长度,就仿照编码那段代码,从根出发计算路径长度,不用记录01即可。

代码

//计算WPL
void WPL() 
{
	double WPL=0;
	for (int i = 0;;i++)
	{
		if (HT[i].lchild != -1 || HT[i].rchild != -1)break;
		WPL += HT[i].weight*HC[i].code.length();//权值×路径长度(编码长度)	
	}
	cout << "WPL:" << WPL << endl;
}

计算带权路径长度结果截图

计算带权路径长度结果截图

全部代码

/*
Project:哈夫曼树 构造 编码 译码 计算wpl
Date:    2019/02/04
Author:  Frank Yu
void CreateHT()创建哈夫曼树
void Code() 哈夫曼树编码
void Encode() 哈夫曼树解码
void WPL() 计算带权路径长度
*/
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<string>
#include<set>
#include<list>
#include<vector>
#include<map>
#include<iterator>
#include<algorithm>
#include<iostream>
#define MAX 1000 //哈夫曼树最大结点个数
#define MAXW 1000 //权值最大
using namespace std;
//哈夫曼树结点结构
typedef struct HNode
{
	char data; //数据,非叶节点为NULL
	double weight;//权重
	int parent;//双亲,-1表示没有双亲,即根节点
	int lchild;//左孩子,数组下标,-1表示无左孩子,即叶节点
	int rchild;//右孩子
}Hnode;
//编码结构
typedef struct HCNode
{
	char data; //数据
	string code;//该字符编码
}Hcnode;
//两个最小结点下标
struct minnodes
{
	int m1;//两者更小权值结点下标
	int m2;
	bool flag;//若找到则为true,否则为false,false说明仅有一个结点
};
//辅助标志数组 标记该结点为根的树是否已加入哈夫曼树
bool flag[MAX] = { false };
Hnode HT[MAX];//哈夫曼树
Hcnode HC[MAX];//哈夫曼编码数组			   
			   //选择两棵最小权值的树 参数max,当前有权值结点下标+1
minnodes Select(int max)
{
	double min = MAXW;
	minnodes mins;
	mins.m2 = -1;
	//查找第一个最小权值的结点下标
	for (int i = 0; i < max; i++)
	{
		if (!flag[i] && HT[i].weight < min)//未加入哈夫曼树,权值更小
		{
			min = HT[i].weight;//更新最小权值
			mins.m1 = i;
		}
	}
	flag[mins.m1] = true;//将结点加入哈夫曼树
	min = MAXW;
	//查找第二个最小权值结点下标,可能不存在
	for (int i = 0; i < max; i++)
	{
		if (!flag[i] && HT[i].weight < min)//未加入哈夫曼树,权值更小
		{
			min = HT[i].weight;//更新最小权值
			mins.m2 = i;
		}
	}
	flag[mins.m2] = true;//将结点加入哈夫曼树
	if (-1 == mins.m2)//仅剩余一个结点未加入哈夫曼树
	{
		mins.flag = false;//未找到两棵最小权值树
	}
	else
	{
		mins.flag = true;
	}
	return mins;
}
//打印哈夫曼树
void PrintHT(int max)
{
	cout << "下标\t" << "数据\t" << "权重\t" << "双亲\t" << "左孩子\t" << "右孩子" << endl;
	for (int i = 0; i<max; i++)
	{
		cout << i << "\t" << HT[i].data << "\t" << HT[i].weight << "\t" << HT[i].parent << "\t" << HT[i].lchild << "\t" << HT[i].rchild << endl;
	}
}
//打印编码
void PrintHC(int n)
{
	for (int i = 0; i < n; i++)
	{
		cout << HC[i].data << ":" << HC[i].code << endl;;
	}
}
//创建哈夫曼树
void CreateHT()
{
	int n;//字符个数,即哈夫曼树叶节点个数
	minnodes mins;
	cout << "请输入字符个数:" << endl;
	cin >> n;
	cout << "请输入字符及权值:" << endl;
	for (int i = 0; i<n; i++)
	{
		cin >> HT[i].data >> HT[i].weight;
		HT[i].lchild = -1; HT[i].rchild = -1;
	}
	/*
	HT[0].data = 'a';HT[0].weight = 45;HT[0].lchild = -1;HT[0].rchild = -1;
	HT[1].data = 'b';HT[1].weight = 13;HT[1].lchild = -1;HT[1].rchild = -1;
	HT[2].data = 'c';HT[2].weight = 12;HT[2].lchild = -1;HT[2].rchild = -1;
	HT[3].data = 'd';HT[3].weight = 16;HT[3].lchild = -1;HT[3].rchild = -1;
	HT[4].data = 'e';HT[4].weight = 9; HT[4].lchild = -1;HT[4].rchild = -1;
	HT[5].data = 'f';HT[5].weight = 5; HT[5].lchild = -1;HT[5].rchild = -1;*/
	int i = n;
	for (;; i++)
	{
		mins = Select(i);//找到两棵根权值最小的树
		if (mins.flag == false)//仅剩余一棵树时跳出
		{
			HT[mins.m1].parent = -1;
			break;
		}
		HT[i].weight = HT[mins.m1].weight + HT[mins.m2].weight;//新加入哈夫曼树结点为两个结点权值之和
		HT[i].data = ' ';
		HT[mins.m1].parent = i;                                //两个权值最小结点双亲为新加入结点
		HT[mins.m2].parent = i;
		HT[i].lchild = mins.m1;//左小又大
		HT[i].rchild = mins.m2;
	}
	PrintHT(i);//打印哈夫曼树
}
//哈夫曼树编码
void Code()
{
	int i = 0;
	for (;; i++)//给所有叶子结点编码
	{
		int j = i;
		string str = "";
		HC[i].data = HT[i].data;//复制数据
		while (-1 != HT[j].parent)//从叶节点找到根
		{
			if (HT[HT[j].parent].lchild == j)//左0右1
			{
				str += '0';
			}
			else
			{
				str += '1';
			}
			j = HT[j].parent;
		}
		reverse(str.begin(), str.end());//逆序
		HC[i].code = str;              //保存至编码
		if (HT[i].lchild == -1 && HT[i].rchild == -1)continue;//非叶子不编码
		else break;
	}
	PrintHC(i);
}
//哈夫曼树解码 从根开始,左0右1,直至叶节点
void Encode()
{
	string s;
	int root = 0;//记录根节点的下标
	cout << "请输入01字符串:" << endl;
	cin >> s;
	while (HT[root].parent != -1) root++;
	int j = root;
	for (int i = 0; i<s.length(); i++)//遍历输入的01串
	{
		if ('0' == s[i])
		{
			j = HT[j].lchild;
		}
		else
		{
			j = HT[j].rchild;
		}
		if (HT[j].lchild == -1 && HT[j].rchild == -1)//到达叶节点
		{
			cout << HT[j].data;
			j = root;//返回根节点继续
		}
	}
	cout << endl;
}
//计算WPL
void WPL()
{
	double WPL = 0;
	for (int i = 0;; i++)
	{
		if (HT[i].lchild != -1 || HT[i].rchild != -1)break;
		WPL += HT[i].weight*HC[i].code.length();//权值×路径长度(编码长度)	
	}
	cout << "WPL:" << WPL << endl;
}
//菜单
void menu()
{
	cout << "************1.创建哈夫曼树   2.编码************" << endl;
	cout << "************3.解码           4.计算wpl*********" << endl;
	cout << "************5.退出" << endl;
}
//主函数
int main()
{
	int choice = 0;
	while (1)
	{
		menu();
		printf("请输入菜单序号:\n");
		scanf("%d", &choice);
		if (5 == choice) break;
		switch (choice)
		{
		case 1:CreateHT(); break;
		case 2:Code(); break;
		case 3:Encode(); break;
		case 4:WPL(); break;
		default:printf("输入错误!!!\n"); break;
		}
	}
	return 0;
}

更多数据结构与算法实现:数据结构(严蔚敏版)与算法的实现(含全部代码)

有问题请下方评论,转载请注明出处,并附有原文链接,谢谢!如有侵权,请及时联系。

  • 56
    点赞
  • 260
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 25
    评论
在信号处理领域,DOA(Direction of Arrival)估计是一项关键技术,主要用于确定多个信号源到达接收阵列的方向。本文将详细探讨三种ESPRIT(Estimation of Signal Parameters via Rotational Invariance Techniques)算法在DOA估计中的实现,以及它们在MATLAB环境中的具体应用。 ESPRIT算法是由Paul Kailath等人于1986年提出的,其核心思想是利用阵列数据的旋转不变性来估计信号源的角度。这种算法相比传统的 MUSIC(Multiple Signal Classification)算法具有较低的计算复杂度,且无需进行特征值分解,因此在实际应用中颇具优势。 1. 普通ESPRIT算法 普通ESPRIT算法分为两个主要步骤:构造等效旋转不变系统和估计角度。通过空间平移(如延时)构建两个子阵列,使得它们之间的关系具有旋转不变性。然后,通过对子阵列数据进行最小二乘拟合,可以得到信号源的角频率估计,进一步转换为DOA估计。 2. 常规ESPRIT算法实现 在描述中提到的`common_esprit_method1.m`和`common_esprit_method2.m`是两种不同的普通ESPRIT算法实现。它们可能在实现细节上略有差异,比如选择子阵列的方式、参数估计的策略等。MATLAB代码通常会包预处理步骤(如数据归一化)、子阵列构造、旋转不变性矩阵的建立、最小二乘估计等部分。通过运行这两个文件,可以比较它们在估计精度和计算效率上的异同。 3. TLS_ESPRIT算法 TLS(Total Least Squares)ESPRIT是对普通ESPRIT的优化,它考虑了数据噪声的影响,提高了估计的稳健性。在TLS_ESPRIT算法中,不假设数据噪声是高斯白噪声,而是采用总最小二乘准则来拟合数据。这使得算法在噪声环境下表现更优。`TLS_esprit.m`文件应该包了TLS_ESPRIT算法的完整实现,包括TLS估计的步骤和旋转不变性矩阵的改进处理。 在实际应用中,选择合适的ESPRIT变体取决于系统条件,例如噪声水平、信号质量以及计算资源。通过MATLAB实现,研究者和工程师可以方便地比较不同算法的效果,并根据需要进行调整和优化。同时,这些代码也为教学和学习DOA估计提供了一个直观的平台,有助于深入理解ESPRIT算法的工作原理。
评论 25
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lady_killer9

感谢您的打赏,我会加倍努力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值