数据结构——树与二叉树-哈夫曼树

介绍

又称最优树,是一类带权路径长度最短的树,在实际生活有着广泛的应用。

在这里插入图片描述

构造

  • 构造过程

    1. 给定n个权值{w1,w2,…,wn},构造n棵只有根节点的二叉树,这n棵二叉树构成森林F。
    2. 在森林F中选取两棵根节点的权值最小的树(构造的新树也算,若权值最小是新树之外的两颗,那就重新再组有课树)作为左右子树构造新的二叉树,且其根节点权值为左右孩子权值和。
    3. 在森林F中删去这两棵树,同时加入新的树。
    4. 重复2、3直到F只含一棵树为止。

    在这里插入图片描述

  • 构造算法

    #include<iostream>
    using namespace std;
    /*
    	树节点结构:权值weight+父母parent+左孩子lchild+右孩子rchild
    	存储在大小为2n-1(n个节点构成的哈夫曼树有2n-1个节点)的数组内,但是为了方便,0不使用,从1开始,所以数组大小要定义为2n,通过下标去调用
    */
    typedef struct
    {
    	int weight;//权值
    	int parent, lchild, rchild;//对应下标
    }HTNode, * HuffmanTree;
    
    //HT数组中存放的哈夫曼树,end表示HT数组中存放结点的最终位置,s1和s2传递的是HT数组中权重值最小的两个结点在数组中的位置
    void Select(HuffmanTree HT, int end, int& s1, int& s2)
    {
        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;
            }
        }
    }
    
    /*
    	算法步骤
    	1.初始化
    		·申请大小2n的动态数组,从1开始循环到2n-1,把所有节点的父母、孩子都设为0
    		·循环n次(1-n),输入n个叶子节点的权值
    	2.创建树
    		·循环n-1次(n+1~2n)的选择、删除与合并来创建哈夫曼树
    		·选择:从当前森林选出双亲为0且权值最小的节点的下标s1和s2
    		·删除:把s1和s2的父母改为非0,即当前的下标
    		·合并:把s1和s2的权值相加,成为当前节点的权值,同时记录左右孩子下标,即s1和s2
    */
    
    void CreateHuffmanTree(HuffmanTree& HT, int n)
    {
    	if (n <= 1) return;//检测输入是否正确
    /*--------------------------------------------初始化--------------------------------------------------------*/
    	int m = 2 * n - 1;//n个叶子节点构建的树有2n-1个节点
    	HT = new HTNode[m + 1];//从1开始,故加一
    	for (int i = 1; i <= m; i++)
    	{//全部置0
    		HT[i].parent = 0;
    		HT[i].lchild = 0;
    		HT[i].rchild = 0;
    	}
    	for (int i = 1; i < n; i++)
    	{//输入权值
    		cin >> HT[i].weight;
    	}
    /*-------------------------------------------构造哈夫曼树----------------------------------------------------*/
    	for (int i = n + 1; i <= m; i++)
    	{
    		int s1, s2;
    		Select(HT, i - 1, s1, s2);//选择函数
    		HT[i].lchild = s1;//当前节点左孩子设置为s1
    		HT[i].rchild = s2;//当前节点右孩子设置为s2
    		HT[s1].parent = HT[s2].parent = i;//s1、s2父母设置为当前节点下标
    		HT[i].weight = HT[s1].weight + HT[s2].weight;//当前节点权值为s1和s2总和
    	}
    }
    

编码

进行数据压缩时,为了使压缩后的数据文件尽可能短,可采用不定长编码。为了正确编码,用哈夫曼树来实现。

关于编码,有两个概念:

  1. 前缀编码:在一个编码方案里,任一个编码都不是其他任何编码的前缀(最左子串),则为前缀编码。(不懂百度一下)
  2. 哈夫曼编码:约定左支为0,右支为1,则根节点到每个叶子节点路径上的0、1序列就是对应编码。以下是哈夫曼编码的性质
    • 哈夫曼编码是前缀编码
    • 哈夫曼编码是最优前缀编码
/*
	哈夫曼编码表存储结构
	因为每个编码不等长,所以定义一个二维字符数组来存储
	为了方便,数组下标从1开始使用,故为n+1行
	因为事先不能确定编码长度,为了不浪费内存空间,定义一个一维字符数组(长度为n,编码一定不会大于n)来存储后再移入编码表里。值得注意的是,因为哈夫曼编码是从根到叶,而记入cd时是从叶到根,所以需要我们记入cd时要反过来记入
*/

typedef char **HuffmanCode;//动态分配数组存储哈夫曼编码表

/*
	算法步骤
	1.分配存储n个字符编码的编码表空间HC,长度为n+1
	2.分配临时编码存储数组cd,长度为n,cd[n-1]置为‘\0’
	3.逐个字符求编码,循环n次,执行以下操作
		·设置start用于记录编码在cd中存放的位置,初始化指向最后
		·设置变量c用于记录从叶子向上回溯到根节点所经过的节点下标,c初始化为等待编码字符的下标i
		·设置变量f用于记录i双亲节点的下标
	4.从叶子向上回溯到根节点,当没有回溯到根节点,循环执行以下操作
		·回溯一次start向前指一个位置,即--start
		·若节点c是f的左孩子,生成0;否则生成1,保存在cd中
		·继续回溯,改变c和f的值
	5.分配HC空间,记入编码
*/

void CreateHuffmanCode(HuffmanTree HT, HuffmanCode& HC, int n)
{
	HC = new char* [n + 1];
	char* cd = new char[n];
	cd[n - 1] = '\0';
	for (int i = 1; i <= n; i++)
	{
		//前期准备
		int start = n - 1;
		int c = i;
		int f = HT[i].parent;
		//开始编码
		while (f != 0)//没有到根节点,哈夫曼树只有根节点是无双亲的
		{
			--start;
			if (HT[f].lchild = c)cd[start] = '0';
			else cd[start] = '1';
			c = f;
			f = HT[f].parent;
		}
		//记入HC
		HC[i] = new char[n - start];
		strcpy(HC[i], &cd[start]);//记得include<string>
	}
    delete cd;//释放临时空间
}
上一篇:第17章 子查询


下一篇:快速了解RDD的创建与处理过程