一,堆的性质与特点
1.堆的数据结构
堆我们认为他是一种完全二叉树的数据结构,我们在使用这种数据结构的时候会使用一些二叉树种的性质,这些性质需要我们着重的理解和应用
二叉树的父子关系
设一个结点为u : 2 * u 为他的左子节点 , 2 * u+ 1 为他的右子结点
设一个结点为u : u / 2 为他的父亲结点
2.堆的存储结构
虽然数据结构中堆是一种非线性的数据结构,但是他的存储结构是一种绝对的线性存储结构,我们它通过一个指针cnt来完成这个堆结构的数据输入和输出操作.
3.堆的特点
堆是的h[1] 永远都是最小值,因为堆的压入压出操作
二,堆的操作和代码实现
1.堆的基础操作
堆两个基础操作组成,down ,up 操作,这两个操作涉及堆数据结构的 基本的构建和绝大多数操作。我们首先从down操作开始,down操作就是每次和他的儿子结点进行比较,如果他们比父亲大的话就进行交换操作,当这两个数值比他父亲结点小的时候,选用他们中比较小的一个进行交换.
void down (int u)
{
int t = u ;
if(2 * u <= cnt && h[2 * u] < h[t]) t = 2 * u;
if(2 * u + 1 <= cnt && h[2 * u + 1] < h [t]) t = 2 * u + 1 ;
}
当完成这一步交换判断之后,我们首先判断一下他是否要进行交换,如果要进行交换操作,我们就进行交换操作,并且在交换完成之后完成剩下的再次down操作。
if(u ! = t)
{
swap(h[t] , h[u] );
down(t) ;
}
总结一下这个操作
void down(int u)
{
int t = u ;
if( 2 * u <= cnt && h[ 2 * u] < h[t]) t = 2 * u;
if(2 * u + 1 <= cnt && h[2 * u + 1] < h[t] ) t = 2 * u + 1;
if(t != u )
{
swap(h[t],h[u]);
down(t) ;
}
}
当完成这个简单的down操作之后我们再来看看简单的up操作,up操作实际上就是看看父亲结点比他的大小关系,当父亲大的时候,进行交换并且再次up,下面我们看代码实现
void up (int u )
{
while(u / 2 > 0 && h[u / 2] > h[u])
{
swap(h[u / 2],h[u]);
u = u / 2;
}
}
2.堆的操作
2.1堆的初始化操作
我们使用up和down来完成初始化操作,首先我们知道堆是一个完全二叉树,使用这个完全二叉树的性质可以知道,堆的最小值是在最上面的,当我们用down完成这个操作的时候,我们是从下向上的完成这个操作,并且最后一行不用进行这次的操作
当我们使用up操作的时候是不好控制的,一般情况下我们不使用up操作进行初始化
2.2堆的排序(集成操作)
堆每次都遍历一次h[1] 这个数,并且把他弹出,弹出之后我们再进行把末尾的元素赋值给h[1] 再进行down(1) 操作.
#include <iostream>
using namespace std ;
const int N = 100010 ;
int h[N] , cnt ;
void down (int u )
{
int t = u ;
if(2 * u <= cnt && h[2 * u] < h[t]) t = 2 * u;
if(2 * u + 1 <= cnt && h[2 * u + 1 ] < h[t]) t = 2 * u + 1;
if(t != u)
{
swap(h[u], h [t]);
down(t);
}
}
int main ()
{
int n ,m;
cin >> n >> m;
cnt = n ;
for(int i = 1 ; i <= n ; i ++)
cin >> h[i] ;
for(int i = n / 2 ; i ; i --)
down(i) ;
for(int i = 0 ; i < m ; i ++ )
{
int x = h[1];
h[1] = h[ cnt -- ];
down(1) ;
cout << x << " ";
}
return 0 ;
}
上述操作已经是比较完美了,但是,还是有一些能改进的地方,这个堆虽然能快速的反应最小值的情况但是,对于第k个插入的值我们还是没有办法,当然我们可以统过后面的堆模拟来改造一下swap_1这个函数就可以了。
3.堆的进阶模拟
在堆的进阶操作中,由于我们要进行堆的第k次运算的相关的操作,所以我们开了两个指针——相互指针,ph[N],hp[N]。
首先我们在up,down中要为了,查找这个相关值得操作更改一下swap函数要完成一些相关的更改
void swap_1(int u ,int v)
{
swap(h[u],h[v]);
swap(hp[u],hp[v]);
swap(ph[hp[u]],ph[hp[v]]);
}
剩下操作的更改就是在初始化赋值的时候,我们要把两个指针都完成他们的赋值操作,ph[N]第几个插入的指向的位置,m++,cnt++很好的就能完成这个赋值操作,同样的hp[N]就是一个完整的取反操作
#include <iostream>
using namespace std ;
const int N = 100010 ;
int h[N] , cnt ;
int hp[N], ph[N];
void swap_1(int u ,int v)
{
swap(h[u],h[v]);
swap(hp[u],hp[v]);
swap(ph[hp[u]],ph[hp[v]]);
}
void down (int u )
{
int t = u ;
if(2 * u <= cnt && h[2 * u] < h[t]) t = 2 * u;
if(2 * u + 1 <= cnt && h[2 * u + 1 ] < h[t]) t = 2 * u + 1;
if(t != u)
{
swap_1(u, t);
down(t);
}
}
void up (int u)
{
while (u / 2 and h[u / 2 ] > h[u])
{
swap_1(u,u / 2);
u = u / 2;
}
}
int main ()
{
int n ;
cin >> n ;
int m = 1 ;
while ( n -- )
{
string str ;
cin >> str ;
if(str == "I")
//I 操作是一个标准的插入操作;
{
int x ;
cin >> x ;
h[++ cnt ] = x ;
//基础的的赋值操作
ph[m] = cnt ; hp[cnt] = m ++ ;
//常规的:ph[N].hp[N[赋值操作;
up(cnt);
}
else if(str == "PM")
//返回最小值
cout << h[1] <<endl;
else if(str == "DM")
//删除最小值
{
swap_1(1,cnt);
cnt -- ;
//删除
down(1) ;
//规整操作;
}
else if(str == "D")
//删除第k个插入的数
{
int k ;
cin >> k;
k = ph[k] ;
swap_1(cnt -- , k);
down (k) ;
up(k) ;
}
else
{
//更换第k个插入的数值为x
int k , x;
cin >> k >> x;
k = ph[k];
h[k] = x;
up(k);
down(k);
}
}
return 0 ;
}