编辑代码

/***********************************************************/
/****************   第6.7节 哈夫曼树及其应用 (146页——152页)**********
		 算法6.16【哈夫曼树的构造算法】(148页)
		 算法6.17【哈夫曼编码算法】(151页)
		 算法6.18【哈夫曼译码算法】(151页)(书中算法6.18有点问题,
		 看所列的DeCode函数)
************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define n 4                //叶结点数,也作为哈夫曼编码位数
#define m 2*n-1           //树的结点总数
#define MAXSIZE 128      //要译码的字符串或哈夫曼编码串长度

/*********哈夫曼树结点的存储结构(147页)********************/
typedef char datatype;

typedef struct { // 结点结构体
	datatype data;                  //结点值
	int weight;                 //权值,不妨设均大于零
	int parent;                 //双亲结点
	int lchild;                 //左孩子结点
	int rchild;                 //右孩子结点
} hufmtree;

/*********哈夫曼编码的数组结构描述(150页)********************/
typedef struct { //编码结构体
	char bits[n];  //存储编码位串,存放哈夫曼编码
	int start;     //指示位串在bits中的起始位置,从start开始读bits中的哈夫曼编码
	datatype data;
} codetype;


/***************算法6.16【哈夫曼树的构造算法】(148页)*************
 ************************************************/
void CreateHUFMT(hufmtree tree[]) {
	int i, j, p1, p2;     //p1和p2记录最小权值和次最小权值的结点位置
	int small1, small2;  //small1和small2分别用于存放最小权值和次最小权值
	char ch;
	int f;
	printf(" 数组下标  lchild  data  weight  rchild  parent \n");
	for (i = 0; i < m; i++) { //将数组tree初始化
		tree[i].parent = tree[i].lchild = tree[i].rchild = 0; //置初值
		// tree[i].weight = 0;
		// tree[i].data = '0';
	}

	/* //n个叶子结点的权值和字符也可由main函数里给出
	for (i = 0; i < n; i++) { //输入n个叶子结点的权值和字符至tree[0]~tree[n-1]
		scanf("%d", &f);
		tree[i].weight = f;
		scanf("%c", &ch);
		tree[i].data = ch;
	}
	*/

	//当n个叶子结点的权值和字符由main函数给出时,tree[n]~tree[m-1]的初始化
	for (i = n; i < m; i++) {
		tree[i].weight = 0;
		tree[i].data = '0';
	}


	//构造哈夫曼树
	for (i = n; i < m; i++) { //进行n-1次合并,将产生的n-1个新结点依次存入 tree[n]~tree[m-1]
		p1 = p2 = 0; //p1和p2记录最小权值的两个结点位置
		small1 = small2 = 32767;   //int型范围是-32768-32767

		for (j = 0; j <= i - 1; j++) {
			if (tree[j].parent == 0) { //只在尚未构造二叉树的结点中查找
				if (tree[j].weight < small1) { //若权值小于最小的左结点的权值
					small2 = small1;  //改变最小权和次最小权及位置
					small1 = tree[j].weight;
					p2 = p1;
					p1 = j;
				} else if (tree[j].weight < small2) {//改变次最小权及位置
					small2 = tree[j].weight;
					p2 = j;
				}
			}
		}
		tree[p1].parent = i;  //给合并的两个结点的双亲域赋值
		tree[p2].parent = i;   //两个最小结点的父结点是i
		tree[i].weight = tree[p1].weight + tree[p2].weight; //两个最小结点的父结点权值为两个最小结点权值之和
		tree[i].lchild = p1; //最小权的根结点是新结点的左孩子,父结点的左结点
		tree[i].rchild = p2; //次最小权的根结点是新结点的右孩子,父结点的右结点
	}
	for (i = 0; i < m; i++) {
		printf("%6d %6d %8c %6d %6d %6d\n", i, tree[i].lchild,
		       tree[i].data, tree[i].weight, tree[i].rchild, tree[i].parent);
	}
}

/**************************************************************
***************算法6.17【哈夫曼编码算法】(151页)*************
 **************************************************************/
void HUFMCode(codetype code[], hufmtree tree[]) {
//code: 存放求出的哈夫曼编码的数组。tree:已知的哈夫曼树。
	int i, c, p;
	codetype cd;               //缓冲变量,临时存放编码
	for (i = 0; i < n; i++) {  //根据哈夫曼树求哈夫曼编码.n为叶子结点数目,循环产生n个字符的哈夫曼编码
		cd.start = n;          //从叶子结点出发向上回溯,从bits的高位开始存储
		c = i;                //c指示第i个叶子结点
		p = tree[c].parent;    //p指示结点c的双亲结点
		cd.data = tree[c].data;   //对结点数据域赋值

		while (p != 0) {      //循序直到树根结点结束循环,c指示到根结点时,p为0
			cd.start--;
			if (tree[p].lchild == c)   //若结点c是双亲结点p的左孩子,置0;否则,置1;
				cd.bits[cd.start] = '0';
			else
				cd.bits[cd.start] = '1';
			c = p;                     //双亲结点p作为孩子结点,用c指示
			p = tree[c].parent;      //p指示结点c的双亲结点
		}
		code[i] = cd;      //一个字符的编码存入code[i]
	}
}

/**********************************************
函数功能:输出哈夫曼编码表
函数输入:哈夫曼树、哈夫曼码编码表
函数输出:(哈夫曼数组)
 ************************************************/

void ShowHUFMCode(codetype code[], hufmtree tree[]) {
	int i, k;
	printf("  输出哈夫曼编码:\n");
	for (i = 0; i < n; i++) {       //输出data中的所有数据
		printf("      %c:\t", tree[i].data);
		for (k = code[i].start; k < n; k++) { //输出所有data中数据的编码
			printf("%c", code[i].bits[k]);
		}
		printf("\n");
	}
}

/**************************************************************
***************算法6.18【哈夫曼译码算法】(151页)*************
	此为书中算法,该算法有问题,看后面的DeCode函数
 **************************************************************/
void HUFMDecode(codetype code[], hufmtree tree[]) {
	int i, c, p, b;
	int endflag = -1;  //电文结束标志取-1
	i = m - 1;        //从根结点开始向下搜索
	scanf("%d", &b);   //读入一个二进制代码
	while (b != endflag) {
		if (b == 0)
			i = tree[i].lchild;   //走向左孩子
		else
			i = tree[i].rchild;   //走向右孩子

		if (tree[i].lchild == 0) { //tree[i]是叶子结点
			putchar(code[i].data);
			i = m - 1;         //回到根结点
		}
		scanf("%d", &b);     //读入下一个二进制代码
	}
	if ((tree[i].lchild != 0) && (i != m - 1)) //电文读完尚未到叶子结点
		printf("\n ERROR \n");      //输入电文有错
}


//哈夫曼树译码
void DeCode(hufmtree tree[], char codestr[]) {  //codestr为电文序列
	int i = m - 1; //当前节点下标,从根结点开始向下搜索
	int j;
	for (j = 0; codestr[j] != '\0'; j++) { //遍历电文序列中的每个字符
		if (codestr[j] == '0') { 	//走向左孩子
			i = tree[i].lchild;
		} else if (codestr[j] == '1') {  //走向右孩子
			i = tree[i].rchild;
		} else { 			//非法字符
			printf("\n Invalid Code Error!\n");
			exit(1);
		}
		if (tree[i].lchild == 0 && tree[i].rchild == 0) { // 到达叶子节点
			printf("%c", tree[i].data); // 输出对应字符
			i = m - 1; // 返回根节点继续遍历
		}
	}
	printf("\n");
}


int main() {
	int i;
	char str[] = {'a', 'b', 'c', 'd'};
	int fnum[] = {7, 5, 2, 4};
	hufmtree tree[m];   //建立哈夫曼树结构体
	for (i = 0; i < n; i++) { //把初始化的数据存入hufmtree结构体中
		tree[i].data = str[i];
		tree[i].weight = fnum[i];
	}
	CreateHUFMT(tree);

	codetype code[n];  //建立编码表结构体
	HUFMCode(code, tree); //建立哈夫曼编码表
	ShowHUFMCode(code, tree);  //输出哈夫曼编码表

	//HUFMDecode(code, tree);
	char c[] = "0110010101110";
	printf("\n 电文 %s \n译码后为:", c);
	DeCode(tree, c);
	return 0;
}