最大(小)堆 --- 相关性质 与 代码实现

堆的特性

在这里插入图片描述

最大堆代码实现

插入时放在最后一个位置  ---  再进行上滤
删除时把最后一个结点放树根   ---  再进行下滤
建立堆的结构
  1. 用数组保存各个值,因为堆为完全二叉树,所以满足完全二叉树的相关性质 — i i i的左子结点下标 i ∗ 2 i*2 i2,右子结点下标 i ∗ 2 + 1 i*2+1 i2+1
    (结点存在且数组有效起始下标为1)
  2. 数组下标为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;
}
往堆中插入元素
  1. 注意在建立堆的结构中岗哨的作用
  2. 逻辑过程:
    • 插入到元素放到完全二叉树的最后一个结点
    • 从这个结点开始一步一步的与父结点比较
      • 当大于时,交换两个结点的位置
      • 当小于时,该位置则为插入结点位置
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;
}
删除堆顶元素并调整建立新堆

当左右子结点都为堆,但加入一个根时如何调整:

  1. 删除堆顶元素后,让完全二叉树的最后一个结点当作堆顶元素(树根),然后通过其与子节点的关系不断调整这个结点。
  2. 调整过程:
    对应结点的左右子结点中,找一个值最大的结点与父结点比较
    • 当父结点值小的时候,让父结点与最大值的子结点换位置
    • 当父结点值大的时候,此位置为正确位置
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;
}

结果

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值