C语言 数据结构与算法---哈夫曼树(编码)

文章目录

  • 一. 关于哈夫曼树
  • 二. 哈夫曼树的实现
  • 三. 哈夫曼编码
    • 1.哈夫曼编码的定义
    • 2. 哈夫曼编码的实现

一. 关于哈夫曼树

  • 路径:从树中一个结点到另一个结点之间的分支构成两个结点之间的路径。
  • 路径长度:路径上的分支数目
  • 树的路径长度:从树根到每一结点的路径之
  • 结点的权:树结点间的边相关数
  • 结点的带权路径长度:从根结点到该结点之间的路径长度与该结点的乘积
  • 树的带权路径长度(WPL):树中所有叶子结点的带权路径长度之和
  • 哈夫曼树:带权路径最短二叉树。(也称为最优二叉树)

实现思路:

  • 构造森林全是根:
    森林里的树只有一个带权的根结点

  • 选用两小造新树:

    1. 在森林中选取两颗结点的权值最小的树作为左右子树
    2. 设置新的二叉树的根结点的权值为其左右子树根结点的权值之和
  • 删除两小造新人:
    在森林中删除这两棵树,同时将得到的二叉树加入森林

  • 重复上述步骤,直到森林中只有一棵树为止,这棵树即为哈夫曼树

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

C语言 数据结构与算法---哈夫曼树(编码)_第1张图片C语言 数据结构与算法---哈夫曼树(编码)_第2张图片

二. 哈夫曼树的实现

注意:为了使得到的哈夫曼树的结构尽量唯一,通常规定生成的哈夫曼树中的每个结点左子树的根结点小于等于右子树根结点的权

1. 哈夫曼树的构造
哈夫曼树没有使用真正的树结构,而是用数组来控制相应的结构

typedef struct
{
     
	int weight; //权值
	int parent, lchild, rchild;//双亲,左右孩子的下标
}HTNode, *HuffmanTree;

哈夫曼树中共有2n-1个结点,不使用0为下标,数组大小为2n ----> 结点下标1~2n-1

2. 找出最小的两个值:

//HT数组中存放的哈夫曼树
//end表示 HT 数组中存放结点的最终位置
//s1和s2传递的是 HT 数组中权重值最小的两个结点在数组中的下标
void Select(HuffmanTree HT, int end, int* s1, int* s2)
{
     
	//分别对应s1,s2的权值,保持 min1 < min2
	int min1, min2;
	//遍历数组初始下标为 1
	int i = 1;
	//找到一个最近的,还没有构建树的结点
	while (HT[i].parent != 0 && i <= end)
	{
     
		i++;
	}
	min1 = HT[i].weight;
	*s1 = i;
	i++;

	//再寻找一个最近的,
	while (HT[i].parent != 0 && i <= end)
	{
     
		i++;
	}

	//对比找到的两个结点的大小,min2为大的,min1为小的
	if (HT[i].weight < min1)
	{
     
		min2 = min1;
		*s2 = *s1;
		min1 = HT[i].weight;
		*s1 = i;
	}
	else
	{
     
		min2 = HT[i].weight;
		*s2 = i;
	}

	//两个结点和后续的所有未构建成树的结点做比较
	for (int j = i + 1; j <= end; j++)
	{
     
		//如果有父结点,直接跳过,进行下一个
		if (HT[j].parent != 0)
		{
     
			continue;
		}
		//如果比最小的还小,将min2=min1,min1赋值新的结点的下标
		if (HT[j].weight < min1)
		{
     
			min2 = min1;
			min1 = HT[j].weight;
			*s2 = *s1;
			*s1 = j;
		}
		//如果介于两者之间,min2赋值为新的结点的位置下标
		else if (HT[j].weight >= min1 && HT[j].weight < min2)
		{
     
			min2 = HT[j].weight;
			*s2 = j;
		}
	}
}

3. 构建哈夫曼树

//HT为地址传递的存储哈夫曼树的数组
//w为存储结点权重值的数组
//n为结点个数
void CreateHuffmanTree(HuffmanTree* HT, int* w, int n)
{
     
	if (n <= 1)
	{
     
		return;   //如果只有一个结点无意义	
	}
	//哈夫曼树总节点数,n是叶子结点
	int m = 2 * n - 1;  
	//数组下标为 0 不用,使用1~2n-1
	*HT = (HuffmanTree)malloc((m + 1) * sizeof(HTNode));
	HuffmanTree p = *HT;

	// 初始化哈夫曼树中的所有叶结点
	for (int i = 1; i <= n; i++)
	{
     
		(p + i)->weight = *(w + i - 1);//w数组下标是从0开始的
		(p + i)->parent = 0;
		(p + i)->lchild = 0;
		(p + i)->rchild = 0;
	}

	//从树组的下标 n+1 开始初始化哈夫曼树中除叶结点外的结点
	for (int i = n + 1; i <= m; i++)
	{
     
		(p + i)->weight = 0;
		(p + i)->parent = 0;
		(p + i)->lchild = 0;
		(p + i)->rchild = 0;
	}

	//构建哈夫曼树
	for (int i = n + 1; i <= m; i++)
	{
     
		//保存最小值的下标,且s1
		int s1, s2;
		Select(*HT, i - 1, &s1, &s2);
		(*HT)[s1].parent = (*HT)[s2].parent = i;
		(*HT)[i].lchild = s1;
		(*HT)[i].rchild = s2;
		(*HT)[i].weight = (*HT)[s1].weight + (*HT)[s2].weight;
	}
}

三. 哈夫曼编码

1.哈夫曼编码的定义

关键:要设计长度不等的编码,则必须使得任一字符的编码都不是另一个字符编码的前缀------前缀编码

哈夫曼编码:使得电文总长最短的前缀编码

  1. 统计字符集中每个字符在电文中出现的平均概率(概率越大,要求编码越短)
  2. 利用哈夫曼树的特点:权越大的叶子离根更近;将每个字符的概率作为权值,构造哈夫曼树。则概率越大的结点,路径越短
  3. 在哈夫曼树的每个分支上表上0或1:
    结点左分支标为 0,右分支标为 1
    ② 把从根到每个叶子的路径上的标号连接起来,作为该叶子代表字符的编码

C语言 数据结构与算法---哈夫曼树(编码)_第3张图片
为什么哈夫曼编码能够保证是前缀编码?
答:因为没有一片树叶是另一片树叶的祖先,所以每个叶结点的编码就不可能是其他叶结点编码的前缀。

为什么哈夫曼编码能够保证字符编码总长最短?
答:因为哈夫曼树的带权路径长度最短

2. 哈夫曼编码的实现

从叶子结点出发更为简便:

typedef char** HuffmanCode;

//HC为存储结点哈夫曼编码的 二维 动态数组,n为结点的个数
//n 个元素,哈夫曼树最高为 n-1,即编码最多 n-1 位,加上‘\0’,共 n 位
void  HuffmanCoding(HuffmanTree HT, HuffmanCode* HC, int n)
{
     
	//分配了 n+1 行,下标使用1~n
	*HC = (HuffmanCode)malloc((n + 1) * sizeof(char*));
	//存放哈夫曼编码的字符串数组
	char* cd = (char*)malloc(n * sizeof(char)); 
	cd[n - 1] = '\0';
	//从叶子结点出发,得到的哈夫曼编码是逆序的
	for (int i = 1; i <= n; i++)
	{
     
		int start = n - 1; //当前结点在数组中的位置
		int c = i;//当前结点的双亲结点在数组中的位置
		int j = HT[i].parent;
		while (j != 0)
		{
     
			//该结点是双亲结点的左孩子,对应路径编码为0
			if (HT[j].lchild == c)
			{
     
				cd[--start] = '0';
			}
			//该结点是双亲结点的右孩子,对应路径编码为1
			else
			{
     
				cd[--start] = '1';
			}
			c = j; //以双亲结点为孩子结点,继续朝树根的方向遍历
			j = HT[j].parent;
		}
		//跳出循环后,cd数组中从下标 start 开始,存放的就是该结点的哈夫曼编码
		(*HC)[i] = (char*)malloc((n - start) * sizeof(char));//给每一行分配列
		strcpy((*HC)[i], &cd[start]);
	}
	free(cd);
}

你可能感兴趣的:(数据结构与算法(C语言),数据结构,算法,二叉树)