www.pudn.com > Huffman.rar > huffman_g.cpp


#include "huffman_g.h"

/*
A. Moffat和J. Katajainen设计的使用最少内存空间计算最小冗余编码(Huffman编码)
的算法源代码。在元素权值已从小到大排序的前提下,此算法通过对数组进行3次
遍历,不使用额外数组空间,即可得到所有元素的Huffman码长(bit length)。
经此函数计算码长后,利用Canonical Huffman编码方法,可以得到所有元素的编码。
参数
	A	包含各元素的权值的数组,调用前,应将数组A中的权值按从小到大排序
	n	数组A中元素的个数
*/
void huffman_g::calculate_minimum_redundancy(unsigned long A[], int n) 
{
	int root; /* 在第1次遍历中是当前子树的根结点,在第3次遍历中是当前深度中的分支结点 */
	int leaf; /* 只用于第1次遍历,是下一个要考察的叶子结点 */
	int next; /* 在第1次遍历中,是下一个合并用的结点(子树),该位置将存储该子树
					的两个分支合并后的权值总和;
					在第2次遍历中,是简单循环变量;
					在第3次遍历中,是将要填入码长值的元素位置 */
	int avbl; /* 用于第3次遍历,是当前深度中可用结点数量。
					最初等于该层结点总数,由上一层分支结点总数乘2得来。
					然后逐一排除分支结点,剩下的就是该层叶子结点总数 */
	int used; /* 用于第3次遍历,是当前深度中分支结点总数 */
	int dpth; /* 用于第3次遍历,当前深度,如果当前层有叶子结点,即为该叶子结点的码长 */

	/* 边界条件检查	*/
	if (n==0) {	return;	}
	if (n==1) {	A[0] = 0; return; }

	/* 
		第1次遍历,从左至右。
		第1次遍历的结果是,A[0]..A[n-3]对应于所有分支结点(不包含根结点),其中的数值为该
		分支结点的双亲结点索引;A[n-2]对应于根结点,其中的数值是所有权值的总和。所有分支
		结点的排列顺序:从左到右是二叉树中从下向上的顺序(假定树根在上),深度相同的分支
		结点排列在一起。
		参考:在Huffman树中,所有分支结点都有左、右两个子树,这样的二叉树中,当叶子结点数
		量为n时,分支结点(含根结点)数量为n-1
	*/

	/*
		首先将最小的两个元素(叶子结点)合并为子树,其权值之和存入A[0]中。这时,当前子树
		根为数组中第0个元素,因此root=0,下一个要考察的叶子结点是第3个元素,因此leaf=2
	*/
	A[0] +=	A[1]; root = 0;	leaf = 2;

	/* 从1到n-1循环,生成所有分支结点,并将它们连成完整的二叉树	*/
	for	(next=1; next <	n-1; next++) {
		/* 在root所指的当前子树和leaf所指的叶子结点中选一个最小的,作为下一个子树的一个分支	*/
		if (leaf>=n	|| A[root]=n	|| (root=0;	next--)
		/* 将每个分支结点的深度设置为其双亲结点深度加1 */ 
		A[next]	= A[A[next]]+1;

	/* 
		第3次遍历,从右至左,设置所有叶子结点的深度(码长) 
		第3次遍历的结果是,A[0]..A[n-1]顺序保存了每个元素的码长(即元素在二叉树中的深度)
	*/
	/* 
		从根出发,因此root为n-2,当前深度dpth为0;
		used用于统计当前深度中分支结点数目,其初始值为0;
		avbl为根据上一层分支结点算出的当前层结点数目,因初始时是根结点,所有结点数目为1;
		next指向下一个要设置的元素位置,从最后一个元素开始;
		说明:从右向左看,传入此函数的元素权值最初是从大到小排列的,其深度值或码长必然是
		从小到大排列的,而第2次遍历后得到的分支结点深度是从0逐渐增大的,因此,只要从根开
		始,知道每一层叶子结点数目,在数组中填相应数目的当前深度值就可以了。
	*/
	avbl = 1; used = dpth =	0; root	= n-2; next	= n-1;

	while (avbl>0) {
		/* 对当前层的所有分支结点循环 */
		while (root>=0 && A[root]==(unsigned int)dpth) { 
			/* 分支结点计数used加1 */
			used++;	root--;
		}
		/* 这时,avbl和used的差值就是当前层的叶子结点数目 */
		/* 对当前层所有叶子结点循环 */ 
		while (avbl>used) { 
			/* 从右至左设置元素的深度值(码长) */
			A[next--] =	dpth; avbl--;
		}
		/* 下一层的结点总数等于当前层分支结点乘2;深度值加1;used清0 */
		avbl = 2*used; dpth++; used	= 0;
	}
}

void huffman_g::generate_codes(int num, const unsigned long* weights)
{
	if (num <= 1 || weights == NULL)
		throw new huffman_exception("参数非法");

	unsigned long* A = new unsigned long[num];	
	memcpy(A, weights, num * sizeof(unsigned long));

	// 只计算权值非0的元素(因为传入的数据已从小到大排序,只简单忽略前面的0即可)
	int i = 0;
	while (A[i] == 0) i++;
	calculate_minimum_redundancy(A + i, num - i);

	code_lens.clear();
	for (int i = 0; i < num; i++)
		code_lens.push_back(A[i]);
	generate_canonical_codes();
	
	delete[] A;
}