哈夫曼树的构造及C++代码实现

哈夫曼树的构造过程:

(1) 以权值分别为W1,W2...Wn的n各结点,构成n棵二叉树T1,T2,...Tn并组成森林F={T1,T2,...Tn},其中每棵二叉树 Ti仅有一个权值为 Wi的根结点;

(2) 在F中选取两棵根结点权值最小的树作为左右子树构造一棵新二叉树,并且置新二叉树根结点权值为左右子树上根结点的权值之和(根结点的权值=左右孩子权值之和,叶结点的权值= Wi)

(3) 从F中删除这两棵二叉树,同时将新二叉树加入到F中;

(4) 重复(2)、(3)直到F中只含一棵二叉树为止,这棵二叉树就是Huffman树。

参考《数据结构(C语言版)(第二版)》(严蔚敏编)第138页)

哈夫曼树存储结构:

一棵有n个叶子结点的哈夫曼树共有2n-1个结点,可以将其存于一个大小为2n-1的数组中,为了方便(第i个结点用下标为i表示),所以将数组0号单元置空,故数组的大小为2n。哈夫曼树对于一个结点来说需要存储其权值、双亲及左右孩子,因此哈夫曼树的存储表示如下:

typedef struct {
    //定义一个结构体数组
    int weight;//权重
    int p, lc, rc;//结点双亲及左右孩子下标
}*HuffmanTree,HTNode;

将叶子结点集中存储在1~n的位置,后面的n-1个位置存储非叶子结点。

哈夫曼树构造的算法实现:

构造huffmantree大致分为两大步骤:

1.初始化

2.创建树

严蔚敏版的《数据结构(c语言版)(第二版)》对其算法描述如下:

void CreateHuffmanTree(HuffmanTree &T, int n){
//初始化


    if(n <= 1) return;
    m = 2*n-1;
    T = new HTNode[m+1];//0号单元未用,动态分配m+1个单元
    for(1 = 1;i <= m;i++){
        T[i].p = 0;
        T[i].lc = 0;
        T[i].rc = 0;
    }
    for(i = 1;i <= n;i++){
        cin>>T[i].weight  //依次输入前n个叶子结点的权值
    }


/*----------------初始化结束,下面开始创建哈夫曼树--------------------*/


    for(i = n+1;i <= m;i++){
    //n个叶子结点,2n-1个结点,共要n-1次合并产生n-1个新的结点


    Select(T,i-1,s1,s2);
    //在T[k]中 (k范围为[1,i-1])找到两个权值最小且双亲域为0的结点,返回它们在T中的序号s1,s2
    T[s1].p = i; T[s2].p = i; //得到新结点i,从森林中删除s1、s2,将s1、s2的双亲域改为i
    T[i].lc = s1; T[i].rc = s2; //s1、s2分别作为i的左右孩子
    T[i].weight = T[s1].weight + T[s2].weight; //i的权值为左右孩子之和

    }

}

具体实现的时候,除了一些初始化和语法规范,主要的是写出select函数,由于刚刚开始学,自身代码水平不行笔者在写select函数的时候遇到了不少麻烦。

要找权值最小的两个下标可以如下实现:

int min1 = 100;
int min2 = 100;

 //定义min1,min2分别为最小值和次小值,赋一个很大的值(至少比叶子结点最大的权重要大)这里以100为例子
for (int j = 1;j <= i - 1;j++) {
        if (T[j].p == 0) {
        //条件为双亲域为0
            if (T[j].weight < min1) {
                min2 = min1;   
                min1 = T[j].weight;
               //如果第j个结点的权重比最小值小,min2更新为min1,min1更新为第j个结点的权重

            }
            else if (T[j].weight <= min2) {
                min2 = T[j].weight;
                //如果第j个结点的权重比次小值min2还小,那min2更新为该值
            }
            
        }
    }
   

//上述循环中找到两个最小值,但要求的是两个下标,故再次循环找
    for (int j = 1;j <= i - 1;j++) {
        if (T[j].p == 0 && T[j].weight == min1) {
            s1 = j;
  //这里要注意不要忘记双亲域为0的条件,如果已经合并过的结点(双亲域不为0)的权值恰好等于这一次合并的最小值,就会出错
        }
    }
    for (int j = 1;j <= i - 1;j++){
        if (T[j].p == 0 && T[j].weight == min2 && j != s1) {
            s2 = j;
            //这里还得添加一个条件,也就是s1!=s2
            //如果min1 = min2,但此时j1!=j2,也就是下标不同,如果不加该条件的话s1和s2的值相同,也就是只找到了一个值,也会报错
        }

如果像算法描述的那样,s1、s2作为select的参数很难定义和初始化,因此不如将:

    T[s1].p = i;
    T[s2].p = i;
    T[i].lc = s1;
    T[i].rc = s2;
    T[i].weight = T[s1].weight + T[s2].weight;

这一段放到select函数里实现,再将i作为参数传到select函数中就会方便许多。

完整代码实现:

测试的以书上的例题w = {5,29,7,8,14,23,3,11} 为例,n = 8,m = 15

测试第14、15个结点的权值、双亲及左右孩子的结点(因为一开始逐步调试的时候这两个结点的错误最多)

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<Windows.h>
#include<malloc.h>

typedef struct {
	//定义一个结构体数组
	int weight;//权重
	int p, lc, rc;//结点双亲及左右孩子下标
}*HuffmanTree,HTNode;

void select(HuffmanTree& T, int n, int i) {
	//找T中下标范围1----i-1且双亲域为0的权重最小的两个结点下标为s1,s2
	int min1 = 100;
	int min2 = 100;
	int s1 = 0;
	int s2 = 0;
	for (int j = 1;j <= i - 1;j++) {
		if (T[j].p == 0) {
			if (T[j].weight < min1) {
				min2 = min1;
				min1 = T[j].weight;
			}
			else if (T[j].weight <= min2) {
				min2 = T[j].weight;
			}
			
		}
	}
	for (int j = 1;j <= i - 1;j++) {
		if (T[j].p == 0 && T[j].weight == min1) {
			s1 = j;
		}
	}
	for (int j = 1;j <= i - 1;j++){
		if (T[j].p == 0 && T[j].weight == min2 && j != s1) {
			s2 = j;
		}
		
	}
	T[s1].p = i;
	T[s2].p = i;
	T[i].lc = s1;
	T[i].rc = s2;
	
	T[i].weight = T[s1].weight + T[s2].weight;
	
}
void CreateHuffmanTree(HuffmanTree& T, int n) {
	if (n <= 1) {
		return;
	}
	int m = 2 * n - 1;
	
	//1.初始化数组
	T = new HTNode[m + 1];
	for (int i = 1;i <= m;i++) {
		T[i].p = 0;
		T[i].lc = 0;
		T[i].rc = 0;
	}
	for (int i = 1;i <= n;i++) {
		printf("第%d个结点权重为:\n", i);
		scanf("%d", &(T[i].weight));
	}
	//2.创建哈夫曼树
	for (int i = n + 1;i <= m;i++) {
		//n-1次合并,n-1次循环
		
		select(T, n, i);
		//找T中下标范围1----i-1且双亲域为0的权重最小的两个结点

	}


}


int main() {
	HuffmanTree T;
	//测试用w = {5,29,7,8,14,23,3,11}
	CreateHuffmanTree(T, 8);
	printf("%第十四个结点的权重为%d,双亲为%d结点,左孩子为%d结点,右孩子为%d结点\n", T[14].weight,T[14].p,T[14].lc,T[14].rc);//测试用数据
	printf("第十五个结点的权重为%d,双亲为%d结点,左孩子为%d结点,右孩子为%d结点\n", T[15].weight,T[15].p,T[15].lc,T[15].rc);
	system("pause");
}

测试结果如下:

 ps:笔者第一次写博客,若有代码错误和表述不清还请见谅,谢谢!!!!!!!!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值