堆12321

一.堆

例题 模拟堆

【分析】

 

1.因为数字过大,需要用到离散化,映射关系如下。(可以将ph和hp理解成方向)

 交换之后就会成为 这样

 

2.关于up和down 顾名思义

down(x) 往下移: x 本身与 x 的两个儿子中更小的交换,直到无法交换。
up(x) 往上移: x 本身与 x 的父节点更小的交换,直到无法交换。
堆有个性质,即堆性质:一个点 x 上的数,要比它的儿子上的书要大,其实 up 和 down 都是用来维护堆性质的。

【代码】

#include <bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int h[N],hp[N],ph[N];
int n,s;
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 x){
    int t=x;
    if(x*2<=s && h[x*2]<h[t]) t=x*2;
    if(x*2+1<=s && h[x*2+1]<h[t]) t=x*2+1;
    if(t!=x){
        heap_swap(t,x);
        down(t);
    }
}
void up(int x){
    while(x/2 && h[x/2]>h[x]){
        heap_swap(x/2,x);
        x/=2;
    }
}
int main(){
    int m=0;
    scanf("%d",&n);
    while(n--){
        char op[5];
        int k,x;
        cin>>op;
        if(!strcmp(op,"I")){
            cin>>x;
            s++;
            m++;//中间值(个人理解)
            hp[s]=m,ph[m]=s;
            h[s]=x;
            up(s);
        }
        else if(!strcmp(op,"PM")) printf("%d\n",h[1]);
        else if(!strcmp(op,"DM")){
            heap_swap(1,s);
            s--;
            down(1);
        }
        else if(!strcmp(op,"D")){
            cin>>k;
            k=ph[k];
            heap_swap(k,s);
            s--;
            down(k);
            up(k);
        }
        else{
            cin>>k>>x;
            k=ph[k];
            h[k]=x;//记得赋值
            down(k);
            up(k);
        }
    }
    return 0;
}

二.二叉堆

【定义】

二叉堆是一种支持插入、删除、查询最值的数据结构。本质上是一种满足“堆性质”的完全二叉 树,树上的每个节点带有一个权值。

根据完全二叉树的性质,我们采用层次序列存储方式,直接用一个数组来保存二叉堆。 层次序列存储方式,就是逐层从左到右为树中的节点依次编号,把此编号作为节点在数组中存储 的位置(下表)。在这种存储方式中,父节点编号等于子节点编号除以2,左节点编号等于父节点编号乘2,右节点编号等于父亲节点乘2加1。

【分类】

1.若树中的任意一个节点的权值都小于等于其父亲的权值,则称该二叉树满足“大根堆性质”。

2.若树中的任意一个节点的权值都大于等于其父亲的权值,则称该二叉树满足“小根堆性质”。

【操作】

1.push

插入一个数,将需要插入的数值放到堆的末尾,通过向上比较交换直到满足当前堆的性质。其时间复杂度就是堆的深度,即O(logN)。

2.gettop

获取最值,即获取堆顶的权值。

int gettop(){
    return heap[1];
}

3.pop

删除堆顶元素,先将堆顶元素与堆底元素交换位置,然后直接移除堆底节点 ( size-- ),再通过向下比较交换直到满足堆的性质。

例题 洛谷1177 快速排序

【分析】

这不是简单的快排,而是一个通过手写堆来实现的排序 。

【代码1】

#include <bits/stdc++.h>
using namespace std;
const int N=100005;
int heap[N];//堆数组,本题为小根堆
int size;//堆的大小
void push(int x){
	heap[++size]=x;//size++后插入x
	int now,next;//now指向当前节点(左儿子),next指向父亲节点
	now=size;//从堆底开始
	while(now!=1){//没有到堆顶
		next=now/2;//父亲节点会等于当前节点除以二
		if(heap[now]<heap[next]){
			swap(heap[now],heap[next]);//交换
			now=next;//指针也要更新
		}
		else break;//如果当前节点最小,说明到达堆顶,跳出。
	}
	return ;
}
int getop(){
	return heap[1];
}//没什么好说的,,
void pop(){
	heap[1]=heap[size];//交换堆顶和堆底元素
	size--;//大小--
	int now=1;//从堆顶开始向下交换比较
	while(now*2<=size){//当前元素的左儿子的下标不超过堆的大小
		int next=now*2;//此时next为当前元素左儿子
		if(next+1<=size&&heap[next+1]<heap[next]) next++;//如果右儿子比左儿子大且不超过边界,令当前元素为右儿子(因为要和更大的交换实现小根堆性质)
		if(heap[next]<heap[now]){
			swap(heap[next],heap[now]);
			now=next;//交换,更新指针
		}
		else break;
	}
	return ;
}
int main(){
	int n;
	cin>>n;
	int k;
	for(int i=1;i<=n;i++){
		scanf("%d",&k);
		push(k);//插入
	} 
	for(int i=1;i<=n;i++){
		printf("%d ",getop());//每次获取最值,就能实现从小到大排序
		pop();//然后删除
	}
	return 0;
}

【代码2】 

Acwing 堆排序

#include <iostream>
#include <algorithm>
using namespace std;
const int N=1e5+10;
int h[N];
int n,m,s;
void down(int x){
    int t=x;
    if(x*2<=s && h[x*2]<h[t]) t=x*2;//和左儿子比
    if(x*2+1<=s && h[x*2+1]<h[t]) t=x*2+1;//和右儿子比
    if(t!=x){//现在t是最小的了
        swap(h[t],h[x]);
        down(t);//交换之后递归down一遍
    }
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        scanf("%d",&h[i]);
    }
    s=n;//堆的长度
    for(int i=n/2;i;i--) down(i);//从中间开始遍历,可以将时间复杂度优化为O(n)
    while(m--){
        printf("%d ",h[1]);
        h[1]=h[s];
        s--;
        down(1);//删除堆首元素
    }
    return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值