首页 > 编程语言 > 详细

[算法学习笔记]左偏树的基本操作及其应用

时间:2019-03-25 20:34:45      阅读:117      评论:0      收藏:0      [点我收藏+]

简介

左偏树是一种可以快速支持合并等操作的堆, 是可并堆中代码复杂度最低,也最容易理解的一种(注意左偏树的每一棵子树都为左偏树)

性质

左偏树是一种二叉树, 除了有二叉树的左右儿子,还有2个属性,键和距离。下面是左偏树的一些基本性质。

  • 节点的键值小于或等于左右子节点的键值。这是左偏树的堆性质。

  • 节点的左子节点的距离不小于右子节点的距离。 这是左偏树的左偏性质。

  • 节点的距离等于它的右子节点距离加一。 若无则为0.

  • 若左偏树的距离为一定值,则节点数最少的左偏树是完全二叉树。(显然啊)

基本操作

数组定义


struct Point{
    int val,ls,rs,dis,fa;
    //val 为 权值
    //ls,rs 为左右子树
    //dis为距离
    //fa 为 father!
}tree[maxn];

Merge

? 顾名思义, Merge操作就是把两个左偏树并起来, 注意并后的左偏树一定要满足上面的四条性质。现在我们令x, y为要合并的两个堆的根, 下面即是步骤。

  • 如果\(x\)\(y\) 为空(即等于0) 返回 x + y(就是\(x\)\(y\) 非零的那一个)
  • 为了保持一定是val大的的并到小的上面, 所以\(if(tree[x].val > tree[y].val) swap(x, y);\)(大根堆则反之)
  • 现在我们将\(y\)树并到\(x\)的右子树上.(因为\(x\)的val比\(y\)的小, 所以并到他的子树中, 又因为这棵树是左偏的,你就把它并到右子树中吧)
  • 为了维持上面的第二条性质, 若\(tree[tree[x].ls].dis > tree[tree[x].rs].dis\)\(swap(tree[x].ls, tree[x].rs)\)
  • 更新x的距离
int merge(int x, int y)
{
    if (x == 0 || y == 0)
       return x+y;
    if ((tree[x].val > tree[y].val) || (tree[x].val == tree[y].val && x > y)) swap(x,y);
    tree[x].rs = merge(tree[x].rs,y);
    tree[tree[x].rs].fa = x;
    if(tree[tree[x].ls].dis < tree[tree[x].ls].dis)
        swap(tree[x].ls,tree[x].rs);
    tree[x].dis = tree[tree[x].rs].dis + 1;
    return x;
}

Insert

? Insert操作为插入一个新的节点, 把这个节点看做一颗新的左偏树即可


    int New(int x, int y){
        tree[++tot].val = y;
        tree[tot].ls = tree[tot].rs = 0;
        return merge(x, y);
    }

Delete

? 把根提出来, 合并左右节点就好了

    root[x]= merge(tree[x].ls, tree[x].rs);

Top

? emmm, \(Top = tree[x].val\) (x 为 堆的根)

?

例题

? 以洛谷p3377为例, 因为此题需要维护联通性, 所以需要像并查集一样暴力找父亲(不能路径压缩)

? 代码丑, 建议不看, 以后会更新的

#include <bits/stdc++.h>
using namespace std;
int n,m;
struct Point{
    int val,ch[2],dis,fa;
}tree[1000010];
template<class T>
void read(T &a){
    T s = 0, w = 1;
    char c = getchar();
    while(c < '0' || c > '9'){
        if(c == '-') w = -1;
        c = getchar();
    } 
    while(c >= '0' && c <= '9'){
        s = (s << 1) + (s << 3) + (c ^ 48);
        c = getchar();
    }
    a = w*s;
}
int merge(int x, int y){
    if (x == 0 || y == 0)
       return x+y;
    if ((tree[x].val > tree[y].val) || (tree[x].val == tree[y].val && x > y)) swap(x,y);
    tree[x].rs = merge(tree[x].rs,y);
    tree[tree[x].ls].fa = x;
    if(tree[tree[x].ls].dis < tree[tree[x].rs].dis){
        swap(tree[x].ls,tree[x].rs);
    }
    tree[x].dis = tree[tree[x].rs].dis + 1;
    return x;
}
int find(int x){
    if(tree[x].fa == 0) return x;
    return find(tree[x].fa);
}
int main(){
    read(n); read(m);
    tree[0].dis = -1;
    for (int i = 1; i <= n; i++){
        int x;
        cin>>x;
        tree[i].val = x;
    }
    for (int i = 1; i <= m; i++){
        int opx;
        read(opx);
        if(opx == 1){
            int x,y;
            read(x); read(y);
            if(tree[x].val == -1 || tree[y].val == -1) continue;
            int xx = find(x), yy = find(y);
            if(xx == yy) continue;
            merge(xx,yy); 
        }
        if(opx == 2){
            int x;
            read(x);
            int xx = find(x);
            printf("%d\n",tree[xx].val);
            if(tree[xx].val == -1) continue;
            tree[xx].val = -1;
            int ls = tree[xx].ls;, rs = tree[xx].rs;
            tree[xx].ch[0] = tree[xx].ch[1] = 0;
            tree[ls].fa = 0,tree[rs].fa = 0;
            merge(ls,rs);
        }
    }
    return 0;
}

[算法学习笔记]左偏树的基本操作及其应用

原文:https://www.cnblogs.com/Ender-zzm/p/10595834.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!