zoukankan      html  css  js  c++  java
  • 最优二叉树(赫夫曼树、赫夫曼树和赫夫曼编码的存储结构)

    最优二叉树是带权路径长度最短的二叉树。根据结点的个数、权值的不同,最优二叉
    树的形状也各不相同。图634 是3 棵最优二叉树的例子。它们的共同特点是:带权值的
    结点都是叶子结点。权值越小的结点,其到根结点的路径越长。构造最优二叉树的方法
    如下:
    (1) 将每个带有权值的结点作为一棵仅有根结点的二叉树,树的权值为结点的权值;
    (2) 将其中两棵权值最小的树组成一棵新二叉树,新树的权值为两棵树的权值之和;

    (3) 重复(2),直到所有结点都在一棵二叉树上。这棵二叉树就是最优二叉树。


    最优二叉树的左右子树是可以互换的,因为这不影响树的带权路径长度。当结点的权
    值差别大到一定程度,最优二叉树就形成了如图634(b)所示的“一边倒”的形状。有些
    书称最优二叉树都是这种“一边倒”的形状是不对的。这通过计算二叉树的带权路径长度
    是否最短就可看出。当所有结点的权值一样,或其权值差别很小,最优二叉树就形成了如
    图634(c)所示的完全二叉树的形状。叶子结点的路径长度近似相等。
    最优二叉树除了叶子结点就是度为2 的结点,没有度为1 的结点。这样才使得树的带
    权路径长度最短。根据二叉树的性质3,最优二叉树的结点数为叶子数的2 倍减1。

    // c6-7.h 赫夫曼树和赫夫曼编码的存储结构(见图6.35)
    typedef struct
    {
    	unsigned int weight;
    	unsigned int parent,lchild,rchild;
    }HTNode,*HuffmanTree; // 动态分配数组存储赫夫曼树
    typedef char **HuffmanCode; // 动态分配数组存储赫夫曼编码表


    c6-7.h 定义的二叉树结构是我们在前边没有讨论过的,但它特别适合建立赫夫曼树。
    赫夫曼树是由多棵二叉树(森林)组合成而的一棵树。这种二叉树结构既适合表示树,也适
    合表示森林。赫夫曼树结点的结构包括权值、双亲及左右孩子,双亲值为0 的是根结点,
    左右孩子值均为0 的是叶子结点。这种二叉树结构是动态生成的顺序结构。当叶子结点数
    确定,赫夫曼树的结点数也确定。由图636(d)可见,建成的赫夫曼树除0 号结点空间不
    用外,每个结点空间都没空置。

    // func6-1.cpp 程序 algo6-1.cpp和algo6-2.cpp要调用
    int min(HuffmanTree t,int i)
    { // 返回i个结点中权值最小的树的根结点序号,函数select()调用
    	int j,flag;
    	unsigned int k=UINT_MAX; // 取k为不小于可能的值(无符号整型最大值)
    	for(j=1;j<=i;j++)
    		if(t[j].weight<k&&t[j].parent==0) // t[j]是树的根结点
    			k=t[j].weight,flag=j;
    		t[flag].parent=1; // 给选中的根结点的双亲赋1,避免第2次查找该结点
    		return flag;
    }
    void select(HuffmanTree t,int i,int &s1,int &s2)
    { // 在i个结点中选择2个权值最小的树的根结点序号,s1为其中序号小的那个
    	int j;
    	s1=min(t,i);
    	s2=min(t,i);
    	if(s1>s2)
    	{
    		j=s1;
    		s1=s2;
    		s2=j;
    	}
    }

    // algo6-1.cpp 求赫夫曼编码。实现算法6.12的程序
    #include"c1.h"
    #include"c6-7.h"
    #include"func6-1.cpp"
    void HuffmanCoding(HuffmanTree &HT,HuffmanCode &HC,int *w,int n) // 算法6.12
    { // w存放n个字符的权值(均>0),构造赫夫曼树HT,并求出n个字符的赫夫曼编码HC
    	int m,i,s1,s2,start;
    	unsigned c,f;
    	HuffmanTree p;
    	char *cd;
    	if(n<=1)
    		return;
    	m=2*n-1;
    	HT=(HuffmanTree)malloc((m+1)*sizeof(HTNode)); // 0号单元未用
    	for(p=HT+1,i=1;i<=n;++i,++p,++w)
    	{
    		(*p).weight=*w;
    		(*p).parent=0;
    		(*p).lchild=0;
    		(*p).rchild=0;
    	}
    	for(;i<=m;++i,++p)
    		(*p).parent=0;
    	for(i=n+1;i<=m;++i) // 建赫夫曼树
    	{ // 在HT[1~i-1]中选择parent为0且weight最小的两个结点,其序号分别为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;
    	}
    	// 从叶子到根逆向求每个字符的赫夫曼编码
    	HC=(HuffmanCode)malloc((n+1)*sizeof(char*));
    	// 分配n个字符编码的头指针向量([0]不用)
    	cd=(char*)malloc(n*sizeof(char)); // 分配求编码的工作空间
    	cd[n-1]=''; // 编码结束符
    	for(i=1;i<=n;i++)
    	{ // 逐个字符求赫夫曼编码
    		start=n-1; // 编码结束符位置
    		for(c=i,f=HT[i].parent;f!=0;c=f,f=HT[f].parent)
    			// 从叶子到根逆向求编码
    			if(HT[f].lchild==c)
    				cd[--start]='0';
    			else
    				cd[--start]='1';
    			HC[i]=(char*)malloc((n-start)*sizeof(char));
    			// 为第i个字符编码分配空间
    			strcpy(HC[i],&cd[start]); // 从cd复制编码(串)到HC
    	}
    	free(cd); // 释放工作空间
    }
    void main()
    {
    	HuffmanTree HT;
    	HuffmanCode HC;
    	int *w,n,i;
    	printf("请输入权值的个数(>1): ");
    	scanf("%d",&n);
    	w=(int*)malloc(n*sizeof(int));
    	printf("请依次输入%d个权值(整型):
    ",n);
    	for(i=0;i<=n-1;i++)
    		scanf("%d",w+i);
    	HuffmanCoding(HT,HC,w,n);
    	for(i=1;i<=n;i++)
    		puts(HC[i]);
    }
    

    代码的运行结果(以教科书图6.24 为例,如图636 所示):

    请输入权值的个数(>1): 4
    请依次输入4个权值(整型):
    7 5 2 4
    0
    10
    110
    111

    图636 是运行过程的图解。初始状态下(见图636(b)),权值分别为7、5、2、4
    的4 个结点是4 棵独立的树(根结点)。它们没有双亲,也没有左右孩子。反复查找权值最
    小的两棵树,并把它们合并成一棵树,其权值为两树的权值之和。最后,所有结点合并成
    一棵赫夫曼树(见图636(d))。
    算法6.12(在algo6-1.cpp 中)在找到两个无双亲且权值最小的结点后,将序号小的结
    点作为左子树,序号大的结点作为右子树,如果不按这个规则,赫夫曼编码的形式会改
    变。但码长不会改变,仍然是赫夫曼编码。


    // algo6-2.cpp 实现算法6.13的程序
    #include"c1.h"
    #include"c6-7.h"
    #include"func6-1.cpp"
    void HuffmanCoding(HuffmanTree &HT,HuffmanCode &HC,int *w,int n) // 前半部分为算法6.12
    { // w存放n个字符的权值(均>0),构造赫夫曼树HT,并求出n个字符的赫夫曼编码HC
    	int m,i,s1,s2; // 此句与algo6-1.cpp不同
    	unsigned c,cdlen; // 此句与algo6-1.cpp不同
    	HuffmanTree p;
    	char *cd;
    	if(n<=1)
    		return;
    	m=2*n-1;
    	HT=(HuffmanTree)malloc((m+1)*sizeof(HTNode)); // 0号单元未用
    	for(p=HT+1,i=1;i<=n;++i,++p,++w)
    	{
    		(*p).weight=*w;
    		(*p).parent=0;
    		(*p).lchild=0;
    		(*p).rchild=0;
    	}
    	for(;i<=m;++i,++p)
    		(*p).parent=0;
    	for(i=n+1;i<=m;++i) // 建赫夫曼树
    	{ // 在HT[1~i-1]中选择parent为0且weight最小的两个结点,其序号分别为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;
    	}
    	// 以下为算法6.13,无栈非递归遍历赫夫曼树,求赫夫曼编码,以上同算法6.12
    	HC=(HuffmanCode)malloc((n+1)*sizeof(char*));
    	// 分配n个字符编码的头指针向量([0]不用)
    	cd=(char*)malloc(n*sizeof(char)); // 分配求编码的工作空间
    	c=m;
    	cdlen=0;
    	for(i=1;i<=m;++i)
    		HT[i].weight=0; // 遍历赫夫曼树时用作结点状态标志
    	while(c)
    	{
    		if(HT[c].weight==0)
    		{ // 向左
    			HT[c].weight=1;
    			if(HT[c].lchild!=0)
    			{
    				c=HT[c].lchild;
    				cd[cdlen++]='0';
    			}
    			else if(HT[c].rchild==0)
    			{ // 登记叶子结点的字符的编码
    				HC[c]=(char *)malloc((cdlen+1)*sizeof(char));
    				cd[cdlen]='';
    				strcpy(HC[c],cd); // 复制编码(串)
    			}
    		}
    		else if(HT[c].weight==1)
    		{ // 向右
    			HT[c].weight=2;
    			if(HT[c].rchild!=0)
    			{
    				c=HT[c].rchild;
    				cd[cdlen++]='1';
    			}
    		}
    		else
    		{ // HT[c].weight==2,退回
    			HT[c].weight=0;
    			c=HT[c].parent;
    			--cdlen; // 退到父结点,编码长度减1
    		}
    	}
    	free(cd);
    }
    void main()
    { // 主程序同algo6-1.cpp
    	HuffmanTree HT;
    	HuffmanCode HC;
    	int *w,n,i;
    	printf("请输入权值的个数(>1): ");
    	scanf("%d",&n);
    	w=(int *)malloc(n*sizeof(int));
    	printf("请依次输入%d个权值(整型):
    ",n);
    	for(i=0;i<=n-1;i++)
    		scanf("%d",w+i);
    	HuffmanCoding(HT,HC,w,n);
    	for(i=1;i<=n;i++)
    		puts(HC[i]);
    }

    代码的运行结果(以教科书例62 为例):

    请输入权值的个数(>1): 8
    请依次输入8个权值(整型):
    5 29 7 8 14 23 3 11
    0110
    10
    1110
    1111
    110
    00
    0111
    010

  • 相关阅读:
    18.3.2从Class上获取信息(属性)
    18.3.2从Class上获取信息(方法)
    18.3.2从Class上获取信息(构造器)
    18.3.1获得Class对象
    ClassLoader.loadClass和Class.forName的区别
    java线程池原理
    如何理解「不要用战术上的勤奋掩盖战略上的懒惰」?
    (转)生产者/消费者问题的多种Java实现方式
    Machine learning system design---Error analysis
    Machine learning system design---prioritizing what to work on
  • 原文地址:https://www.cnblogs.com/KongkOngL/p/3945942.html
Copyright © 2011-2022 走看看