zoukankan      html  css  js  c++  java
  • 堆小结

    每个节点权值大于(小根堆)父亲的树形数据结构

    以下均讨论小根堆的问题

    普通二叉堆

    用数组(a[1:n])构成一棵二叉树来维护堆操作,可以做到

    1.插入元素

    2.查询堆顶

    3.删除堆顶或者删除特定元素(需要记录权值位置)

    1.插入元素

    先放到(a[n+1])的位置,然后每次与父亲比较是否交换

    void push(int x){
        a[++n]=x;
        for(int p=n;p>1 && a[p]<a[p>>1];) swap(a[p],a[p>>1]),p>>=1;
    }
    

    2.删除特定元素

    删除后,把(a[n])元素放到空的位置,然后向下走,注意每次一定是把左右儿子中比较小的换上来

    void Delete(int x){
        swap(a[x],a[n--]);
        for(int p=x;(p<<1)<=n;){
    		int nxt=p<<1;
            if((p<<1|1)<=n && a[p<<1|1]<a[p<<1]) nxt=p<<1|1;
            if(a[nxt]<a[p]) swap(a[p],a[nxt]);
            else break;
        }
    }
    

    [ ]

    配对堆

    配对堆不是一个二叉树结构,所以在存储上,使用左儿子右兄弟来存储树形结构

    可以实现的操作有

    1.插入/删除元素,查询堆顶

    2.查询堆顶

    3.合并两个堆

    首先要维护最基本的两个操作

    1.合并两个堆

    直接按照堆顶权值大小合并,接上去即可

    int a[N],ch[N],br[N]; //权值,儿子,兄弟
    int Union(int x,int y){
        if(!x||!y) return x|y;
        if(a[x]>a[y]) swap(x,y);
        br[y]=ch[x],ch[x]=y;
        return x;
    }
    

    2.配对操作

    把一个点的所有儿子两两合并之后再依次合并到一起

    配对堆的所有操作都基于合并和配对实现

    合并操作是(O(1))

    配对操作单次最坏是(O(n)),但是和(Splay)类似的,配对可以让儿子中兄弟最多的个数减半,是一个均摊(O(log n))的操作,因此不可持久化,但是实际运行常数比较小

    操作实现:用一个函数给(x)(x)的右边的所有兄弟配对,递归实现

    每次让(x)和右边第一个兄弟配对(即先合并),再和右边剩下的节点合并

    int Pair(int x){
        if(!x || !br[x]) return x;
        int y=br[x],z=br[y];
        return Union(Union(x,y),Pair(z));
    }
    

    3.删除元素

    如果删除的不是堆顶元素,还需要额外存储每个点的父亲

    把被删除元素的儿子合并之后接到父亲上面

    4.查询堆顶

    如果是查询某个特定元素所在堆的堆顶,需要用并查集来维护

    [ ]

    左偏树

    左偏树是一个二叉堆结构,顾名思义,向左边偏的树

    左偏树判断左偏的方法是定义了一个(dis)数组,满足(forall dis_{lson}ge dis_{rson},dis_x=dis_{rson}+1)

    因此一直走右儿子的链长度就是(O(log n))

    利用这个性质完成操作,每次合并之后检查(dis_{lson},dis_{rson})是否满足条件

    可以完成的操作有

    1.插入节点/合并堆

    2.删除节点

    3.访问堆顶

    4.可持久化

    1.检查操作

    void Check(int x){
        if(dis[ls[x]]<dis[rs[x]]) swap(ls[x],rs[x]);
        dis[x]=dis[rs[x]]+1;
    }
    

    左偏树的合并操作就是

    让较大的堆顶 和 小的堆顶的右儿子合并成为 新的右儿子

    很显然合并次数$leq (两个堆的右儿子长度之和,这个操作是单次)O(log n)$

    int Union(int x,int y){
    	if(!x||!y) return x|y;
        if(a[x]>a[y]) swap(x,y);
        return rs[x]=Union(rs[x],y),;
    }
    

    2.删除节点

    合并左右儿子后接到父亲上

    3.访问堆顶

    左偏树的深度没有保证,访问特定节点所在堆的堆顶需要用并查集维护

    4.可持久化

    由于单次访问复杂度保证了是(O(log n)),因此可以对于每次合并得到的开一个新的节点存下来

    即完成了可持久化操作

  • 相关阅读:
    多线程中,上锁的理解
    sql server 2008 联机丛书
    序列化是线程安全的么
    对象化下的编程——字段
    Dic实现工厂模式
    design principle:java 回调与委派/委托机制(转)
    风筝数据结构学习笔记(2)后序遍历二叉树(非递归)
    风筝数据结构学习笔记(1)利用链式存储结构和递归构建二叉树
    吕震宇老师《设计模式系列》
    吕震宇老师《设计模式随笔系列》
  • 原文地址:https://www.cnblogs.com/chasedeath/p/13379146.html
Copyright © 2011-2022 走看看