最大堆、最小堆(优先队列、哈夫曼树完整版)
最大堆(优先队列)
最大堆:根结点的键值是所有堆结点键值中最大者,且每个结点的值都比其孩子的值大。
最小堆:根结点的键值是所有堆结点键值中最小者,且每个结点的值都比其孩子的值小。
最大堆(优先队列)的生成
生成最大堆:最大堆通常都是一棵完全二叉树,因此我们使用数组的形式来存储最大堆的值,从1号单元开始存储,因此父结点跟子结点的关系就是两倍的关系。
因此创建一个最大堆只需要在数组位置为1的地方插入第一个元素。
数组从第一个开始存放的原因有两个:
1.从元素1开始存储数据可以保证结点的子树的下标为父亲的2倍
2.在起点放置哨兵在接下来最大堆的插入删除有一定帮助
该最大堆在数组中的存储顺序为
[Flag]-[100]-[19]-[36]-[17]-[3]-[25]-[1]-[2]-[7]
最大堆(优先队列)的删除
最大堆的操作先从简单的来,最大堆的设计就是为了方便将最大元素或最小元素以最小的代价给出。
从上面的树中,删除一个最大元素将使得根结点被弹出。需要对根结点经行补全,此时要寻找最大堆中剩余节点中的最大元素补到根结点的位置,也就是数组中下标为1的位置。
很显然,下面的元素中,最大元素必然是根节点的左右子树中较大的那一个。
我们将根节点补充为其较大的那一个儿子。而这个被拿去补充根节点的元素所占的位置仍然需要其较大的子结点去补充,这样不断向下,直到被拿去补充的元素不再拥有左右子树,退出循环。
程序:
//最大堆的删除,刚写完没写注释,后续有空会加上
struct tree *Delete(void) {
struct tree *T;
T = malloc(sizeof(LEN_TREE));
int change;
int i;
T->data = MaxHeap[1].data;
T->right = MaxHeap[1].right;
T->left = MaxHeap[1].left;
MaxHeap[1].data = MaxHeap[length].data;
MaxHeap[length].data = 0;
length--;
for (i = 1; i < length; i *= 2) {
if (MaxHeap[2 * i].data>MaxHeap[2 * i + 1].data) {
if (MaxHeap[i].data < MaxHeap[2 * i].data) {
change = MaxHeap[i].data;
MaxHeap[i].data = MaxHeap[2 * i].data;
MaxHeap[2 * i].data = change;
}
else {
break;
}
}
else {
if (MaxHeap[i].data < MaxHeap[2 * i+1].data) {
change = MaxHeap[i].data;
MaxHeap[i].data = MaxHeap[2 * i+1].data;
MaxHeap[2 * i+1].data = change;
}
else {
break;
}
}
}
return T;
}
最大堆(优先队列)的插入
最大的的插入与删除类似于反向操作。
我们首先将要插入的数据放在最大堆的尾部,然后比较其父亲子树与该结点的参数大小,若插入的参数大于其父亲结点的参数,则二者做交换。在不断向上交换的过程中,循环什么时候结束呢?
没错,当比较的元素N/2为0,我们先前定的Flag时,则退出循环,若是最大堆可以将Flag定位无穷大,反之可以定为0或无穷小。
程序:
//最大堆的插入,刚写完没写注释,后续有空会加上
int Insert(struct tree T) {
int len;
length++;
len = length;
MaxHeap[length] = T;
if ((len / 2) == 0) {
return 0;
}
while (len) {
if (MaxHeap[len].data > MaxHeap[len / 2].data && len / 2!=0) {
MaxHeap[len].data = MaxHeap[len / 2].data;
len /= 2;
}
else {
break;
}
MaxHeap[len] = T;
}
MaxHeap[len] = T;
return 0;
}
哈夫曼树
给定N个权值作为N个叶子结点,构造一棵二叉树,若该树的带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree)。哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近。
以上为哈夫曼树的定义,哈夫曼树的创建其实是一个比较复杂的过程。但是在最大堆的配合下,可以让哈夫曼树的创建变得十分简单。
举个栗子。
很多时候我们需要为一些代号进行编码,例如A,B,C,D,我们需要对其进行编码时,我们可以通过00,01,10,11,这样的方式来进行编码。此时,我们只需要一棵高度为2的二叉树就可以对A,B,C,D进行编码。
但是当我们在处理数据的同时,数据之间存在权重的关系,A,B出现的次数远远大于C,D出现的次数,此时这种编码方式就有一点浪费空间的意思。
在其他很多实际问题中也一样,所以有了哈夫曼树,当带权路径最短时,我们可以得到一颗综合搜索成本最低的树。
如何通过最大堆来生成哈夫曼树:
程序设计思路。
例子:通过最小堆来生成哈夫曼树(最大最小堆取决于需要取的数据权重)
1.需要从最小堆中去取出两个最小的结点,将他们挂在一个新的结点下。
2.将该结点的值附为两个结点的和。
3.将这个新生成的结点插入最小堆。
循环进行以上操作,我们最终可以得到一颗哈夫曼树。
//创建哈夫曼树
void makehuffman(void) {
struct tree *t;
struct tree *left;
struct tree *right;
while(length!=1){
t = malloc(sizeof(LEN_TREE));
left = Delete();
//printf("left:%d\n", left->data);
right = Delete();
//printf("right:%d\n", right->data);
t->left = left;
t->right = right;
t->data = left->data + right->data;
Insert(*t);
}
}