数据结构——堆的实现

堆的本质是一棵完全二叉树,在计算机体系中占据非常重要的地位,面试也经常会遇到。在C++中,priority_queue就是堆,在算法竞赛中,常常用堆优化最短路(迪杰斯特拉)算法。

堆的基本知识
  • 堆分为大根堆和小根堆,分别是指root在当前树结构中值最大(最小)。下述讲解均以小根堆为例。
  • 堆是完全二叉树,完全二叉树满足的性质它都满足:i节点的左子节点为2i,右子节点为2i+1
堆的基本功能
  1. 插入一个数
  2. 求集合当中的最小值
  3. 删除最小值
  4. 删除任意一个元素
  5. 删除任意一个元素
堆的实现

堆中的核心操作只有两个:down(),up()。分别表示将某个数向下/向上调整归位。上述5个基本操作都可以用down和up组合完成。

//向下调整归位
void down(int u){
    int t = u;
    if(2*u <= si && h[2*u]<h[t]) t = 2*u;
    if(2*u+1 <= si && h[2*u+1]<h[t]) t = 2*u+1;
    if(t != u) {
        swap(h[t],h[u]);
        down(t);
    }    
}
//向上调整归位
void up(int u) {
    while(u/2 && h[u]<h[u/2]){
        swap(h[u],h[u/2]);
        u /= 2;
    }
}
  • 插入一个数
heap[++ size] = x;
up[(size);
  • 求集合当中的最小值
heap[1]
  • 删除最小值:原理:先用最末尾的节点覆盖root节点,让size–,相当于去掉末尾节点,再从头至尾把heap[1]操作down一遍。
heap[1]  = heap[size --];
down(1);
  • 删除任意一个元素:原理:类似于删除最小值,先用末尾节点覆盖该节点,让size–相当于去掉末尾节点。不同的是,由于不知道修改后是更大了还是更小了,为写代码方便起见,直接一遍down,一遍up。
heap[k]  = heap[size --];
down(k);up(k);
  • 修改任意一个元素:原理:类似于删除任意一个元素,一遍down,一遍up。不同的是,不用覆盖,不用size–。直接修改heap[k]即可。
heap[k] = x;
down(k);up(k);
堆的另外一些操作
  1. 建堆(依次插入O(nlogn),O(n))
  2. 输出前k小(大)值
//1.建堆:
//依次插入O(nlogn)
for(int i = 1;i <= n;i++) {
   int x;
   scanf("%d",&x);
   h[++si] = x;
   up(si);
}
//O(n)从n/2开始down,相当于从完全二叉树的倒数第二层开始down。
for (int i = n / 2; i; i -- ) down(i);
//2.由删除最小值操作得到
for(int i = 1;i<=m;i++) {
   printf("%d ",h[1]);
   h[1] = h[si --];
   down(1);
}
堆的模板:
// h[N]存储堆中的值, h[1]是堆顶,x的左儿子是2x, 右儿子是2x + 1
// ph[k]存储第k个插入的点在堆中的位置
// hp[k]存储堆中下标是k的点是第几个插入的
int h[N], ph[N], hp[N], size;

// 交换两个点,及其映射关系
void heap_swap(int a, int b)
{
    swap(ph[hp[a]],ph[hp[b]]);
    swap(hp[a], hp[b]);
    swap(h[a], h[b]);
}

void down(int u)
{
    int t = u;
    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)
    {
        heap_swap(u, t);
        down(t);
    }
}

void up(int u)
{
    while (u / 2 && h[u] < h[u / 2])
    {
        heap_swap(u, u / 2);
        u >>= 1;
    }
}

// O(n)建堆
for (int i = n / 2; i; i -- ) down(i);

作者:yxc
链接:https://www.acwing.com/blog/content/404/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
练习题

堆排序

#include<iostream>

using namespace std;

int n,m,si,h[100010];

void down(int u){
    int t = u;
    if(2*u <= si && h[2*u]<h[t]) t = 2*u;
    if(2*u+1 <= si && h[2*u+1]<h[t]) t = 2*u+1;
    if(t != u) {
        swap(h[t],h[u]);
        down(t);
    }    
}

void up(int u) {
    while(u/2 && h[u]<h[u/2]){
        swap(h[u],h[u/2]);
        u /= 2;
    }
}

int main()
{
    scanf("%d %d",&n,&m);
    
    for(int i = 1;i <= n;i++) {
        int x;
        scanf("%d",&x);
        h[++si] = x;
        up(si);
    }
    
    for(int i = 1;i<=m;i++) {
        printf("%d ",h[1]);
        h[1] = h[si --];
        down(1);
    }
    
    return 0;
}

模拟堆

#include<iostream>

using namespace std;

int n,m,si,h[100010],ph[100010],hp[100010],idx ;
char op[5];

void heap_swap(int a,int b) {
	swap(ph[hp[a]],ph[hp[b]]);
	swap(hp[a],hp[b]);
	swap(h[a],h[b]);
}

void down(int u){
    int t = u;
    if(2*u <= si && h[2*u]<h[t]) t = 2*u;
    if(2*u+1 <= si && h[2*u+1]<h[t]) t = 2*u+1;
    if(t != u) {
        heap_swap(t,u);
        down(t);
    }    
}

void up(int u) {
    while(u/2 && h[u]<h[u/2]){
        heap_swap(u,u/2);
        u /= 2;
    }
}

int main()
{
    scanf("%d",&n);
    m = n;
    while(n --) {
    	scanf("%s",op);
    	if(op[0] == 'P') {
    		printf("%d\n",h[1]);
		}
		else if(op[0] == 'D' && op[1] == 'M') {
		//注意不能按原来的末尾直接覆盖的方法做,那样会在ph,hp的维护上存在缺陷,直接利用heap_swap交换末尾即可。
			heap_swap(1,si--);
			down(1);
		}else if(op[0] == 'I') {
			int x;
			scanf("%d",&x);
			idx ++;
			h[++ si] = x;
			ph[idx] = si;
			hp[si] = idx;
			up(si);
		}else if(op[0] == 'D' && !op[1]) {
			int k;
			scanf("%d",&k);
			int index = ph[k];
			//注意不能按原来的末尾直接覆盖的方法做,那样会在ph,hp的维护上存在缺陷,直接利用heap_swap交换末尾即可。
			heap_swap(index,si--);
			down(index);
			up(index);
		}else {
			int k,x;
			scanf("%d %d",&k,&x);
			int index = ph[k];
			h[index] = x;
			down(index);
			up(index);
		}	
	}
    
    return 0;
}
priority_queue的用法
  1. 定义:默认定义为大根堆,怎么定义小根堆?
//压入操作时,直接压入-x,而非x。利用大根堆的性质,处理逻辑上的小根堆。
//直接定义小根堆
priority_queue<int,vector<int>,greater<int>> heap;
  1. 其他操作与queue相同
用于优化迪杰斯特拉算法的改进部分:

需要维护两个新的数组,分别表示ph[k]存储第k个插入的点在堆中的位置
,hp[k]存储堆中下标是k的点是第几个插入的.

// ph[k]存储第k个插入的点在堆中的位置
// hp[k]存储堆中下标是k的点是第几个插入的
// 交换两个点,及其映射关系
void heap_swap(int a, int b)
{
    swap(ph[hp[a]],ph[hp[b]]);
    swap(hp[a], hp[b]);
    swap(h[a], h[b]);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值