数据结构与算法详解——堆篇(附c++实现代码)

  堆其实可以看成是完全二叉树,即除了最后一层其他层全满,最后一层的叶子结点靠左边的二叉树。
  其次,堆的每个结点的值必须大于等于(或者小于等于)其子树的中每个结点的值,根据这个性质,我们可以知道堆的根结点的值是整个堆中最大(或者最小的),也称为大顶堆(小顶堆)。
在这里插入图片描述

堆的实现

存储

  之前在二叉树那一篇中提到过,完全二叉树非常适合用数组存储(以上图大顶堆为例):
  第一种:下标从1开始存储,在不超边界的情况下,假设当前结点的下标为i,左子树的下标为i*2,右子树的下标为i*2+1,父节点的下标为i/2。

90 70 80 60 10 40 50 30 20
下标 0 1 2 3 4 5 6 7 8 9

  第二种:下标从0开始存储,在不超边界的情况下,假设当前结点的下标为i,左子树的下标为i*2+1,右子树的下标为i*2+2,父节点的下标为(i-1)/2。

90 70 80 60 10 40 50 30 20
下标 0 1 2 3 4 5 6 7 8

插入

  往堆插入一个元素,为了继续满足完全二叉树的特点,所以我们将这个元素加在堆的末尾或者说数组的末尾。
  其次,还需要满足大顶堆(小顶堆)的特点,本文以大顶堆为例。要保持大顶堆的特点,需要进行堆化,堆化又分为自上而下和自下而上。
  自上而下的堆化:拿当前结点n和左右孩子比较,如果左右孩子得值有比n大的,那么把大的那个结点的值与n进行交换,从大的孩子结点继续往下进行上面的比较,直到叶子结点;如果左右孩子没有比n大的就直接结束。因为是一直和孩子结点进行比较,所以是从上往下的。
  自下而上的堆化:拿当前结点n和父节点比较,如果比父节点大,那么就交换值,从父节点开始,直到根节点。如果没有比父节点大就直接结束。和上面差不多,不过不是和孩子结点比较,而是和父节点进行比较,所以是从下往上的。
  这里我们将一个结点插入到末尾,所以是从下往上进行堆化,来看下面这个例子:
在这里插入图片描述
  我们插入21,加到堆的末尾,然后开始自下而上的堆化:21>父节点2所以交换值,然后接着在父节点的位置比较21>根节点20所以交换值,到根节点了结束,可以看到堆化后继续保持了大顶堆的特点。
  插入的代码如下:

void insert(T data) {
   
		//size是已经装了的元素个数,capacity是数组申请的空间大小
		if (size >= capacity)return;	//堆满了,这里没有写扩容的逻辑
		++size;	
		arr[size] = data;
		int i = size;
		while ((i - 1) / 2 > 0 && arr[(i - 1) / 2] < arr[i]) {
   	//(i - 1) / 2是父节点的下标,自下而上堆化
			std::swap(arr[(i - 1) / 2], arr[i]);
			i = (i - 1) / 2;
		}

删除堆顶元素

  删除堆顶元素我们需要用到自上而下的堆化,这里我们将最后一个元素与堆顶元素交换,然后再从根节点自上而下堆化:
在这里插入图片描述

void removeMax() {
   
		if (size == 0)return;
		arr[0] = arr[size];		//将最后一个元素换到根节点的位置
		--size;
		heapify(size, 1);//然后自上而下堆化
	}

	void heapify(int n, int i) {
   	//自上而下堆,n表示数组最末尾的下标,从下标i开始进行堆化
		while (1) {
   
			int maxPos = i;
			if (i * 2 + 1 >= n && arr[i] < arr[i * 2 + 1])maxPos = i * 2 + 1;
			if (i * 2 + 2 >= n && arr[maxPos] < arr[i * 2 + 2])maxPos = i * 2 + 2;
			if (maxPos == i)break;
			std::swap(arr[i], arr[maxPos]);
			i = maxPos;
		
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值