2020-10-22

上文分析了哈夫曼树的构造方式:

机器学习入坑者:一文搞懂如何构造哈夫曼树?
下面从C++实现的角度对构建哈夫曼树的过程进行分析。首先给出创建哈夫曼树的完整步骤如下,每个步骤的具体分析见下文:

创建容量为2n-1的静态数组,元素为节点
对数组所有2n-1个元素(节点)进行初始化,并对前n个叶子节点赋予权值,后n-1个节点为空
查找数组中权值不为零且无双亲节点,选择其中权值最小的两个节点
对上一步获得的两个节点进行合并,生成新的节点
上一步参与合并的两个节点不再参与后续合并(设置parent指向新节点),新生成的节点存到数组下一个空位置,空位置索引向后移动一位
重复3、4、5共n-1次,最后一次获得的节点即为哈夫曼树的根节点

如何存储哈夫曼树?
当集合T元素(叶子节点)个数确定以后,可以使用数组容量的计算法则确定数组大小,所以可采用静态数组实现哈夫曼树的构建。哈夫曼树节点包括:

权值(用户指定);
左右孩子;
parent(标志其是否为根节点);

如何确定静态数组长度?
从前面的分析可知,可采用静态数组存储哈夫曼树,下面是确定静态数组长度的流程:

(1)在构建哈夫曼树之前,共有n个树(叶子节点),构成集合T,需要数组程度为n;

(2)每次进行合并时只有两个树参与,生成一个新树(需要给数组增加一个元素),此时需要数组长度为n+1;

(3)每次合并后,集合T元素个数减少一个(参与合并的两棵树从T中去除,新生成的树加入T);

(4)构造完成过后只有1个树,即T只剩一个元素,所以一共进行了n-1次构造,生成了n-1个新树,即数组元素个数从原来的n变为n+(n-1) = 2n-1个。

备注:上述的每个“树”只需要一个节点存储即可,也就是只需要使用数组中的一个元素进行存储,即结合T中的每棵树只需要存储其根节点。

如何设计节点类并初始化?
根据前面的分析可知,节点类需包含左右孩子、权值和parent节点位置,定义如下:

class Node {
public:
int left, right;
// -1表示无父节点
int parent = -1;
int weight = 0;
};
对数组中前n个树的根节点进行权值初始化的函数如下:

void initLeafs(Node *p, int numLeafs) {
// 对p指向的数组中前numLeafs个元素进行初始化
for (int i = 0; i < numLeafs; i++) {
int ch;
cout << "input : " << endl;
cin >> ch;
// 设置Node的权重
p[i].weight = ch;
}
cout << “---------------” << endl;
}

如何查找数组中权值最小且无parent的两个节点?
查找权值最小的两个根节点时,需要保证该根节点无parent。合并操作是针对根节点进行的,有parent的节点不会是树的根节点。搜索所有已经创建的根节点中权值最小的两个节点代码如下,可以认为是一种按序搜索算法:

void searchMin(const Node *p, int nums, int &min1, int &min2) {
// 在p指向的nums个元素的数组中
// 搜索权值最小的两个节点
// 并保证其parent为空,即根节点

// 首先找到数组中第一个根节点,即parent为空的节点
// 将此根节点作为weight最小节点,即采用排序算法
for (int i = 0; i < nums; i++) {
	if (p[i].parent == -1) { 
		min1 = i;
		break;
	}
}
// 寻找数组中权值最小的根节点作为min1
for (int i = 0; i < nums; i++) {
	if (p[i].parent == -1 && p[i].weight < p[min1].weight) {
		min1 = i;
	}
}

// 寻找到所有根节点中权值最小的weight1以后,继续寻找第二个
// 找到数组中第一个根节点,作为初始权值最小的位置min2
// 必须保证第min2不等于min1
for (int i = 0; i < nums; i++) {
	if (p[i].parent == -1 && i != min1) {
		min2 = i;
		break;
	}
}

// 寻找数组中除去min1以外权值最小的根节点
for (int i = 0; i < nums; i++) {
	if (p[i].parent == -1 && p[i].weight < p[min2].weight && i != min1) {
		min2 = i;
	}
}

}

如何进行合并操作?
进行两颗树合并时,首先要保证两棵树的根节点权值是所有根节点中最小的,然后将参与合并的两个根节点权值相加,形成新的根节点(新的树)。最后,将新的根节点插入到数组中,并将合并的两个根节点的parent设置为1(标志这两个根节点不再是根节点,而是树的子树了,以后不会再参与合并过程)。

合并过程共n-1次,对应的创建哈夫曼树的总体代码如下:

void createHuffman(Node *p, int numLeafs) {
// 1、初始化前numLeafs个根节点,指定其权值
initLeafs(p, numLeafs);

// 创建用于存储权值最小两个节点的位置
int minPos1, minPos2;

// 2、numLeafs个根节点需要进行numLeafs-1次合并
for (int i = 0; i < numLeafs - 1; i++) {
	// 3、寻找权值最小的前两个节点
	// 寻找的范围是前numLeafs+i个节点
	// 即所有已经创建的节点
	searchMin(p, numLeafs + i, minPos1, minPos2);
	// 4、在numLeafs+1处创建新根节点
	p[numLeafs + i].weight = p[minPos1].weight + p[minPos2].weight;
	p[numLeafs + i].left = minPos1;
	p[numLeafs + i].right = minPos2;
	// 将当前权值最小的两个节点的parent指向新创建的根节点
	// 表示其不再是根节点,不再参与合并
	p[minPos1].parent = numLeafs + i;
	p[minPos2].parent = numLeafs + i;
}

}

如何验证哈夫曼树创建成功?
通过输出哈夫曼树所有节点的全部信息,可以查看节点权值、左右孩子位置、父节点位置,进而确保代码无误:

void printHuffman(Node *p, int numNodes) {
// 输出所有的节点,及节点信息
for (int i = 0; i < numNodes; i++) {
cout << setw(8) << p[i].weight <<
setw(8) << p[i].parent <<
setw(8) << p[i].left <<
setw(8) << p[i].right << endl;
}
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值