哈夫曼树的建立——从一个数组中得到最小数和次小数谈起

笑着哭i 提交于 2020-02-01 06:15:47

👴周末两天也算就看了哈夫曼树和编码问题了,进度巨慢,还有一个骑士周游算法跑到现在还没出结果。。。。。
趁热打铁,把自己的一些心得分享给大家。
重点在后面。。。

话不多说 都在代码注释里了
这个是只操作整数数组

#include <iostream>
#define N 10
using namespace std;
void Find_Min_SubMin_Nums(int data[],int n)
{
	int min1,min2;//min1 是最小数  min2 是次小数d
	min1 = min2 = 30000;//先让这两个数取得尽量大
	for(int i = 0;i<n;i++)
	{
		if(data[i]<min1)//数组中某一数据比最小值还小?  那么最小值应该是它  
			//之前的最小值暂时算是第二小的数  所以可以把之前的最小值给次小值
		{
			min2 = min1;
			min1 = data[i];
		}
		//找到了最小数 就没必要关心次小数了  因为当前的min1 min2 都取到了最小
		else if(data[i]<min2)//此时的情况是说,扫描到的数字 比最小值大  但是比次小值小  
			//这好办  直接把次小值替换
			min2 = data[i];
	}
	//如此循环下去 得到的就是最小值和次小值了
	cout<<"最小值:"<<min1<<endl;
	cout<<"次小值:"<<min2<<endl;
}
int main()
{
	int a[N] = {-1,-9,10,-127,1999,1,-126,2,3,4};
	Find_Min_SubMin_Nums(a,N);
	return 0;
}

如果用在哈夫曼树的生成中应该是这样的
嘿嘿 开始之前我先复习一下哈夫曼树的生成过程
首先 先定义一个带有权重的树结点的结构体

typedef char ElemType;
struct  HfTreeNode
{
	ElemType data;
	int parent;      //双亲结点
	int lchild,rchild;//双亲结点
	int weight;//权重
};

接下来 简要描述一下哈夫曼树的生成过程
它的思想就是找一种最优的方法 使得WPL取得最小 有点类似于最优方法求解的味道 下面我尽量用简单直白的语言来描述这个过程
以下的过程基于以下假设:
1.已知某一顺序表的元素个数
2.元素的数据类型是char
3.生成的哈夫曼序列最大值不超过256

假设HfTreeNode possible[256] 结构体数组的元素个数为N(N也是叶子结点数)
那么有这个数组生成的哈夫曼树的总结点数为 2N - 1
我们使用possible的0~N-1位置来存放叶子结点
N~2
N-2位置存放非叶子结点
类似于下图
在这里插入图片描述
了解了这个 就成功了一半了
由于树结点的结构中有双亲结点、兄弟结点的值 我们可以在函数刚开始时对整个结构体数组进行初始化 将双亲结点、兄弟节点的值都指向-1.在后面操作时在对其进行赋值 有人可能会问了 这么做的目的是什么?emmmm 下面我用一个例子来说明一下
在这里插入图片描述
这是第一步走完了 结果 非叶子结点4时叶子结点0和1的双亲结点 那么结点0 和 结点 1的双亲结点值就被我给定了 那么 下一步 在序号0——4中寻找最小权和次小权元素时 结点0和结点1就不会参加了 所以第二步应该为:
在这里插入图片描述
这步完成后 同样 序号2和4作为序号5的子节点 下一次寻找权值时它们也不会参与
依次类推。。。。

好了,下面回到正题 其实上面的讲解基本已经把这个算法给讲完了
下面总结一下:
step1: 将 possible中每个成员的双亲结点、兄弟结点全部给-1.
step2: 由于叶子结点已经在队列中,从0——N-1.下面我们使用for循环结构对剩余的N-1个非叶子结点进行操作,即从N——2*N-2.

for(int i = n;i<2*n-1;i++)

【咦?N的大小写貌似搞错了,不影响大局 : ) 】
假设 i = N;此时应该从它前面的所有元素中找到 最小 和 次小 。
具体操作可以参看前面的找最小和次小的方法。
找到之后 ,并进行一系列的数据更新操作,哈夫曼树基本就建立起来了。不过这期间可能会遇到一些其他的问题,比如代码段第69行的那个“=”是否需要。李春葆的《数据结构》中是没有的,正确与否。嘿嘿嘿,这就需要你自己来判断了。我就再次不表了。(打字太累了)
其实我们可以通过一个友好的界面来获取possible数组的值,这个实现起来应该不难。
真是托了陶老师的福,👴要自学这门课。
有精力了 我再上传 哈夫曼编码的一些问题。。。
希望有大佬带带小弟。
附录代码:
(友好界面+建立哈夫曼树+哈夫曼编码)

#include <iostream>
#include <malloc.h>
#define MAXSIZE 256
#define N 4
#define M 2*N-1
typedef char ElemType;
struct  HfTreeNode
{
	ElemType data;
	int parent;      //双亲结点
	int lchild,rchild;//双亲结点
	int weight;//权重
};

struct HfCode
{
	ElemType data;
	ElemType Code[N];
	int length;
};
using namespace std;

//根据用户输入得到数据
int Get_Weight(HfTreeNode possible[],char *str)
{
	int i = 0,k = 0;
	int temp[MAXSIZE] = {0};
	while (str[i] != 0)
	{
		temp[(int)str[i]]++;
		i++;
	}
	for(int i = 0;i<MAXSIZE;i++)
	{
		if(temp[i] != 0)
		{
				possible[k].weight = temp[i];
				possible[k].data = (char)i;
				k++;
		}
	}
	return k;
}
//创建一个哈夫曼树
void Creat_HfTree(HfTreeNode possible[],int n)
{
	int min1,min2;//min1 最小数  min2 次小数
	int lnode,rnode;
	//1. 先将2*n-1的空间都给初始化
	for(int i = 0;i<2*n - 1;i++)
	{
		possible[i].parent = -1;
		possible[i].lchild = possible[i].rchild = -1;
	}

	//2. 叶子结点已经确定下来了  下面就确定非叶子结点就可以了
	//叶子结点有n个,数组下标到n-1;所以非叶子结点应从n开始
	//找到了最小结点后  我们应当这个非叶子结点的左孩子---->最小结点  右孩子---->次小结点
	//更新双亲节点的下标   
	for(int i = n;i<2*n-1;i++)
	{
		//2.1 从0——i中搜寻到两个权值最小的结点是我们的目标
		min1 = min2 = 100000;//这两个数的初始值应尽量给小
		lnode = rnode = -1;
		for(int j = 0;j<i;j++)
		{
			if(possible[j].parent == -1)//不等于-1说明该结点已经被处理过了  我们只处理未处理过的结点
			{
					if(possible[j].weight <= min1)//数组中某一数据比最小值还小?  那么最小值应该是它  
				//之前的最小值暂时算是第二小的数  所以可以把之前的最小值给次小值
				{
					min2 = min1;rnode = lnode;
					min1 = possible[j].weight;
					 lnode = j;
				}//找到了最小数 就没必要关心次小数了  因为当前的min1 min2 都取到了最小
				else if(possible[j].weight < min2)//此时的情况是说,扫描到的数字 比最小值大  但是比次小值小  
				//这好办  直接把次小值替换
				{
					min2 = possible[j].weight;
					rnode = j;
				}
				//如此循环下去 得到的就是最小值和次小值了
			}
		}
		possible[i].weight = possible[lnode].weight + possible[rnode].weight;
		possible[i].lchild = lnode;
		possible[i].rchild = rnode;
		possible[lnode].parent = i; possible[rnode].parent = i;

	}
}

//处理编码顺序
void Deal_Order_Code(HfCode code[],int n)
{
	int Flag = 0;//用于标记第一次非0时的情况
	for(int i = 0;i<n;i++)
	{
		for(int j = 0;j<=code[i].length;j++)
		{
			if(code[i].Code[j] != 0)
			{
				Flag = j;break;
			}
		}
		for(int k = Flag,l = 0;k<=code[i].length;k++,l++)
		{
				 code[i].Code[l] = code[i].Code[k];
				 code[i].Code[k] = 0;
		}
	}
}

//根据哈夫曼树得到哈夫曼编码
void Get_HfCode(HfTreeNode possible[],int n,HfCode code[])
{
	int f,temp,Length;
	for(int i = 0;i<n;i++)//获取n个叶子结点的哈夫曼编码
	{
		temp = i;//temp 表示当前结点
		f = possible[i].parent;
		code[i].length = n;
		code[i].data = possible[i].data;
		Length = code[i].length;
		while (f != -1)//只要双亲结点不等于-1  就说明还没有到根节点
		{
			if(possible[f].lchild == temp)//双亲结点的左孩子是当前结点 说明双亲结点是从该结点过来的
			{
				code[i].Code[Length] = '0';
				Length--;
			}
			else
			{
				code[i].Code[Length] = '1';
				Length--;
			}
			//执行完该结点的操作后 由于还没有到达根节点 要往上继续走
			//更新
			temp = f;//temp 现在是父亲了   
			f = possible[f].parent;//f 是父亲的父亲  也就是爷爷了
		}
	}
	Deal_Order_Code(code,n);
}
//显示哈夫曼编码
void Display(HfCode code[],int n)
{
	for(int i = 0;i<n;i++)
	{
		cout<<code[i].data<<"的哈夫曼编码是:"<<code[i].Code<<endl;
	}
}

int main()
{
	int k;
	char choice;
	char str[MAXSIZE];
	cout<<"请输入字符串"<<endl;
	//cin.get(str,MAXSIZE);
	//cin.get();
	while (cin.get(str,MAXSIZE))
	{
		HfTreeNode nums[MAXSIZE] = {0};
        HfCode MyCode[MAXSIZE] = {};
		k = Get_Weight(nums,str);
		Creat_HfTree(nums,k);
		Get_HfCode(nums,k,MyCode);
		cout<<"下面是输入字符串的哈夫曼编码:"<<endl;
		Display(MyCode,k);
		cout<<"请问还要输入吗?(Y/N)"<<endl;
		cin>>choice;
		cin.get();
		if(choice == 'N')
			break;
		cout<<"请继续输入"<<endl;
		//cin.get(str,MAXSIZE);
		//cin.get();
	}
	cout<<"再见!"<<endl;
	cout<<endl;
	return 0;
}
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!