赞
踩
堆我们认为他是一种完全二叉树的数据结构,我们在使用这种数据结构的时候会使用一些二叉树种的性质,这些性质需要我们着重的理解和应用
二叉树的父子关系
设一个结点为u : 2 * u 为他的左子节点 , 2 * u+ 1 为他的右子结点
设一个结点为u : u / 2 为他的父亲结点
虽然数据结构中堆是一种非线性的数据结构,但是他的存储结构是一种绝对的线性存储结构,我们它通过一个指针cnt来完成这个堆结构的数据输入和输出操作.
堆是的h[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;
}
}
我们使用up和down来完成初始化操作,首先我们知道堆是一个完全二叉树,使用这个完全二叉树的性质可以知道,堆的最小值是在最上面的,当我们用down完成这个操作的时候,我们是从下向上的完成这个操作,并且最后一行不用进行这次的操作
当我们使用up操作的时候是不好控制的,一般情况下我们不使用up操作进行初始化
堆每次都遍历一次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这个函数就可以了。
在堆的进阶操作中,由于我们要进行堆的第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 ; }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。