堆(手写堆模拟)

文章介绍了堆数据结构的基本概念,包括小根堆的定义、操作(插入、删除、修改及down/up调整),以及在一维数组中的存储方式。以一个实际问题为例,展示了如何使用手动方法构建和维护堆,以处理频繁的增删操作。
摘要由CSDN通过智能技术生成

1.堆的定义

堆(heap):一种有特殊用途的数据结构——用来在一组变化频繁(发生增删查改的频率较高)的数据集中查找最值

堆是一颗完全二叉树(除了最后一层节点外,树上面的节点都是满的,不存在空节点,最后一层从左到右排列

 堆:满足一个性质,这里以小根堆为例,它的每一个节点都是小于对于它的左右儿子的。依次类推,就会发现每个点都会小于等于对于分支两边的所有点,所以根节点就是最小值

2.如何手写一个堆?(//后面的看不懂可先跳过,继续往下看)

①.插入一个数      // 每次在堆的最后面插入,然后再up操作

②.求集合当中的最小值  //heap[1]

③.删除最小值     //删除就用最后一个元素覆盖到堆顶元素,size--,然后再down操作

④.删除任意一个元素  //假如是k,heap[k] = heap[size] ; size --; down(k) ;/ up(k) ;

⑤.修改任意一个元素   //heap[k]=x; down(k);/ up(k);

这里不同于C++中stl里的堆(优先队列),stl里的堆不支持后面两个操作

3.堆的存储

不同于链表的存储,堆是一种全新的存储方式:

用一维数组存,初始位置存根节点,每个节点x的左儿子是2x,右儿子2x+1. (这里的是指的数组下标)

 4.堆有两个基本操作(这里都以小根堆为例)

①.down(x)向下调整,把节点向下移(每次操作时就把要移动的节点和其子节点的最小值交换)

②.up(x)向上调整,把节点向上移(每次操作只需要交换父节点和要移动的节点)

 下面将以具体题为例实战一下

这道题我们直接把数据存放在一维数组h中,看成一个完全二叉树,每个节点的左儿子是其2倍,右儿子是2倍+1;然后开始建堆,从倒数第二层最后一个元素n/2(向下取整)开始向上遍历,不断的down。

为什么从倒数第二层的最后一个元素遍历呢? 

因为最后一层往下就没有元素了,而且还会浪费大量时间。

在每次取出堆顶元素后,用最后一个元素填充堆顶,然后再进行一次 down(1) 操作,即可保持堆的性质。

#include<iostream>
using namespace std;

const int N=100010;

int n,m;
int h[N],size_;

void down(int u)
{
	int t=u; //我们用t来表示每一个down的三个元素中的最小值下标
	if(u*2<=size_&&h[u*2]<h[t]) t=u*2; //成立就更新下标
	if(u*2+1<=size_&&h[u*2+1]<h[t]) t=u*2+1;
	
	if(u!=t) //说明有比传入的这个节点还小的点
	{
		swap(h[u],h[t]);
		down(t);
	}
	
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) scanf("%d",&h[i]);
	size_=n;

//这里不一个一个遍历了(时间复杂度O(nlog(n))),下面这样插入可以将时间复杂度降到O(n).
	for(int i=n/2;i;i--) down(i); //这里的n/2是向下取整,i=n/2,即完全二叉树的倒数第二层的最后一个元素
	
	while(m--)
	{
		printf("%d ",h[1]); //输出堆顶元素
		h[1]=h[size_]; //删除堆顶元素,即用最后一个元素覆盖堆顶元素
		size_--; //size减减
		down(1); //然后再从堆顶开始往下down
	}
	return 0;
}

这里补一下up的操作

void up(int u)
{
    while(u/2&&h[u/2]>h[u]) //这里的u/2代表u的父节点下标,u/2不为0,就一直往上up
    {
        swap(h[u/2],h[u]);
        u/=2;
    }
}

ps:完全二叉树:

完全二叉树是一种特殊的二叉树,其中除了最后一层可能不满外,其余各层的节点都达到了最大个数。

具体地说,对于一个完全二叉树:

  1. 最后一层或者是满的,或者是从左边开始连续缺少一些节点。
  2. 如果某个节点有右子节点,那么它一定有左子节点。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值