堆的特性
最大堆代码实现
插入时放在最后一个位置 --- 再进行上滤
删除时把最后一个结点放树根 --- 再进行下滤
建立堆的结构
- 用数组保存各个值,因为堆为完全二叉树,所以满足完全二叉树的相关性质 —
i
i
i的左子结点下标
i
∗
2
i*2
i∗2,右子结点下标
i
∗
2
+
1
i*2+1
i∗2+1
(结点存在且数组有效起始下标为1) - 数组下标为0的位置保存的为堆中所有可能元素的最大值
当作岗哨,在插入操作中可以简化 for循环 判断条件- 没有岗哨的条件还要加上下标1 与 下标为0 的值比较
- 有岗哨的条件可以不用加这个条件,因为下标为0的位置保存的为最大值
#include <stdio.h>
#include <stdlib.h>
// 建立最大堆
typedef struct HeapStruct* MaxHeap;
struct HeapStruct {
int* Elements;
int Size; // 堆中当前的元素个数 不计算岗哨
int Capacity; //最大容量
};
#define MAXDATA 1000; //定义为堆中所有可能元素的最大值
/*作为数组中的0号位置上的元素,当作岗哨,在遍历的时候可以减少判断条件*/
创建堆
MaxHeap CreateHeap(int MaxSize) { // 创建容量为MaxSize大小的堆
MaxHeap H = (MaxHeap)malloc(sizeof(struct HeapStruct));
H->Elements = (int*)malloc(sizeof(int) * (MaxSize + 1));
H->Size = 0;
H->Capacity = MaxSize;
H->Elements[0] = MAXDATA; // 下标为0的元素为岗哨元素
return H;
}
往堆中插入元素
- 注意在建立堆的结构中岗哨的作用
- 逻辑过程:
- 插入到元素放到完全二叉树的最后一个结点
- 从这个结点开始一步一步的与父结点比较
- 当大于时,交换两个结点的位置
- 当小于时,该位置则为插入结点位置
int IsFull(MaxHeap H) {
if (H->Capacity == H->Size)
return 1;
return 0;
}
/*插入一个结点*/
int Insert(MaxHeap H, int x) {
if (IsFull(H)) {
printf("FULL\n");
return 0;
}
int i = ++H->Size; //i指向插入后堆中的最后一个元素 也即对应完全二叉树的最后一个结点
// 没用岗哨时就增加一个限制 i>1 以防止下标为0与下标为1的值进行比较
for (; x > H->Elements[i / 2]; i /= 2) { //当子结点的值大于父结点的值 交换两结点位置
H->Elements[i] = H->Elements[i / 2];
}
H->Elements[i] = x;
return 1;
}
删除堆顶元素并调整建立新堆
当左右子结点都为堆,但加入一个根时如何调整:
- 删除堆顶元素后,让完全二叉树的最后一个结点当作堆顶元素(树根),然后通过其与子节点的关系不断调整这个结点。
- 调整过程:
对应结点的左右子结点中,找一个值最大的结点与父结点比较- 当父结点值小的时候,让父结点与最大值的子结点换位置
- 当父结点值大的时候,此位置为正确位置
int IsEmpty(MaxHeap H) {
return H->Size == 0;
}
/*取出来最大值(堆顶) 并删除一个结点*/
int DeleteMax(MaxHeap H) { // 把最后一个结点放到堆顶然后调整
if (IsEmpty(H)) {
printf("EMOTY\n");
return -1;
}
int Parenet, Child;
int Maxitem, X;
Maxitem = H->Elements[1]; // 返回最大值
X = H->Elements[H->Size--]; // 保存最后一个结点的值并删除一个结点
// 开始调整堆 找左子树与右子树最大值 进行与X的比较
for (Parenet = 1; Parenet * 2 <= H->Size; Parenet = Child) {
Child = Parenet * 2;
if ((Child != H->Size) // 有右结点
&& (H->Elements[Child] < H->Elements[Child + 1])) {
Child++; //Child 指向左右结点最大的值
}
if (X >= H->Elements[Child]) // 元素比子结点最大值还大时则找到正确的位置
break;
else
H->Elements[Parenet] = H->Elements[Child];
}
H->Elements[Parenet] = X;
return Maxitem;
}
插入删除的图示
通过已给定元素建立一个堆
当给了一个序列时,我们把他保存到一个数组中
删除过程中,实质上是左右子结点都为堆,但加入一个根时如何调整的问题,也就是说我们现在已经可以解决这样一个问题了,那我们就把一个序列变成一个堆的问题转化成这个问题:
- 我们从最后一个父结点开始调整,因为最后一个父结点的左(右)子结点一定只有元素或者没有,这样的一定是一个堆。所以就成为左右子结点都为堆,但加入一个根时如何调整问题
/*调整下标为P对应的子树为一个堆*/
void PercDown(MaxHeap H, int P) {
int Parenet, Child;
int X;
X = H->Elements[P];
for (Parenet = P; Parenet * 2 <= H->Size; Parenet = Child) {
Child = Parenet * 2;
if (Child != H->Size && (H->Elements[Child] < H->Elements[Child + 1]))
Child++;
if (X >= H->Elements[Child])
break;
else
H->Elements[Parenet] = H->Elements[Child];
}
H->Elements[Parenet] = X;
}
/*建立最大堆*/ // 所有H->Size个元素已经存在H->Data[]中
void BuildHeap(MaxHeap H) { // 从最后一个父结点开始调整这些元素使之成为最大堆
for (int i = H->Size / 2; i > 0; i--) {
PercDown(H, i);
}
}
测试
int main() {
MaxHeap H = CreateHeap(20);
for (int i = 0; i < 10; i++) {
H->Elements[++H->Size] = i;
}
BuildHeap(H);
for (int i = 1; i <= 10; i++) {
printf("%d ", H->Elements[i]);
}
printf("\n");
Insert(H, 10);
for (int i = 1; i <= 11; i++) {
printf("%d ", H->Elements[i]);
}
printf("\n");
DeleteMax(H);
for (int i = 1; i <= 10; i++) {
printf("%d ", H->Elements[i]);
}
printf("\n");
return 0;
}