一.堆
【分析】
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-- ),再通过向下比较交换直到满足堆的性质。
【分析】
这不是简单的快排,而是一个通过手写堆来实现的排序 。
【代码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】
#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;
}