左偏树 学习笔记

左偏树(Leftist Tree)是一种可并堆的实现。左偏树是一棵二叉树,它的节点除了和二叉树的节点一样具有左右子树指针( left, right)外,还有两个属性,键值和距离(dist)。

键值:是用于比较节点的大小。

距离:节点i称为外节点(external node),当且仅当节点i的左子树或右子树为空 ( left(i) = NULL或right(i) = NULL );节点i称为外节点(external node),当且仅当节点i的左子树或右子树为空 ( left(i) = NULL或right(i) = NULL );特别的,如果节点i本身是外节点,则它的距离为0;而空节点的距离规定为-1 (dist(NULL) = -1)。

//以上来自 黄源河神犇2005国家集训队论文%%%

可并堆(Mergeable Heap)也是一种抽象数据类型,它除了支持优先队列的三个基本操作(Insert, Minimum,Delete-Min),还支持一个额外的操作--合并操作;

部分摘自:

基本性质

[性质1] 节点的键值小于或等于它的左右子节点的键值。

即key(i)≤key(parent(i)) 这条性质又叫堆性质。符合该性质的树是堆有序的(Heap-Ordered)。有了性质1,我们可以知道左偏树的根节点是整棵树的最小节点,于是我们可以在O(1) 的时间内完成取最小节点操作。

[性质2] 节点的左子节点的距离不小于右子节点的距离。

即dist(left(i))≥dist(right(i)) 这条性质称为左偏性质。性质2是为了使我们可以以更小的代价在优先队列的其它两个基本操作(插入节点、删除最小节点)进行后维持堆性质。在后面我们就会看到它的作用。

这两条性质是对每一个节点而言的,因此可以简单地从中得出,左偏树的左右子树都是左偏树。

由这两条性质,我们可以得出左偏树的定义:左偏树是具有左偏性质的堆有序二叉树。

我们知道,一个节点必须经由它的子节点才能到达外节点。由于性质2,一个节点的距离实际上就是这个节点一直沿它的右边到达一个外节点所经过的边数,也就是说,我们有

[性质3] 节点的距离等于它的右子节点的距离加1。

即dist( i ) = dist( right( i ) ) + 1 外节点的距离为0,由于性质2,它的右子节点必为空节点。为了满足性质3,故前面规定空节点的距离为-1。

我们的印象中,平衡树是具有非常小的深度的,这也意味着到达任何一个节点所经过的边数很少。左偏树并不是为了快速访问所有的节点而设计的,它的目的是快速访问最小节点以及在对树修改后快速的恢复堆性质。从图中我们可以看到它并不平衡,由于性质2的缘故,它的结构偏向左侧,不过距离的概念和树的深度并不同,左偏树并不意味着左子树的节点数或是深度一定大于右子树。

//以上来自百度百科

左偏树的左右子树都是左偏树;

我们的印象中,平衡树是具有非常小的深度的,这也意味着到达任何一个节点所经过的边数很少。左偏树并不是为了快速访问所有的节点而设计的,它的目的是快速访问最小节点以及在对树修改后快速的恢复堆性质。

Merge( ) 把A,B两棵左偏树合并,返回一棵新的左偏树C,包含A和B中的所有元素。在本文中,一棵左偏树用它的根节点的指针表示。

在合并操作中,最简单的情况是其中一棵树为空(也就是,该树根节点指针为NULL)。这时我们只须要返回另一棵树。

若A和B都非空,我们假设A的根节点小于等于B的根节点(否则交换A,B),把A的根节点作为新树C的根节点,剩下的事就是合并A的右子树right(A) 和B了。

合并了right(A) 和B之后,right(A) 的距离可能会变大,当right(A) 的距离大于left(A) 的距离时, 只需交换right(A)于left(A);

插入节点 :直接当做插入两个左偏树处理;

删除最小(大)值, 直接删除根节点, 然后Merge(left, right);

剩下的操作有点麻烦以后更新!

留下模板:

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
#define maxn 100010

int n, m;

int ch[maxn][2], val[maxn], dis[maxn], fa[maxn];

int Merge(int x, int y)
{
    if(x * y == 0) return x + y;
    if(val[x] > val[y] || (val[x] == val[y] && x > y)) swap(x, y);
    ch[x][1] = Merge(ch[x][1], y);
    fa[ch[x][1]] = x;
    if(dis[ch[x][0]] < dis[ch[x][1]]) swap(ch[x][0], ch[x][1]);
    dis[x] = dis[ch[x][1]] + 1;
    return x;
}

int getf(int x)
{
    while(fa[x]) x = fa[x];
    return x;
}

void del(int x)
{
    val[x] = -1;
    fa[ch[x][0]] = fa[ch[x][1]] = 0;
    Merge(ch[x][0], ch[x][1]);
}

int main()
{
    cin >> n >> m;

    dis[0] = -1;

    for(register int i = 1 ; i <= n ; i ++) scanf("%d", &val[i]);

    while(m--)
    {
        int opt;
        scanf("%d", &opt);
        int x, y;
        if(opt == 1)
        {
            scanf("%d%d", &x, &y);
            if(val[x] == -1 || val[y] == -1) continue;

            if(x == y) continue;

            Merge(getf(x), getf(y));
        }
        else if(opt == 2)
        {
            scanf("%d", &x);
            if(val[x] == -1)
            {
                printf("-1\n");
                continue;
            }
            int fx = getf(x);
            printf("%d\n", val[fx]);
            del(fx);
        }
    }
    return 0;

}

zZhBr

原文地址:https://www.cnblogs.com/zZh-Brim/p/8969482.html

时间: 2024-11-07 22:04:10

左偏树 学习笔记的相关文章

左偏树学习

左偏树(Leftist Tree)树这个数据结构内容真的很多,二叉堆,其实就是一颗二叉树,这次讲的左偏树(又叫“左翼堆”),也是树.二叉堆是个很不错的数据结构,因为它非常便于理解,而且仅仅用了一个数组,不会造成额外空间的浪费,但它有个缺点,那就是很难合并两个二叉堆,对于“合并”,“拆分”这种操作,我觉得最方面的还是依靠指针,改变一下指针的值就可以实现,要是涉及到元素的移动,那就复杂一些了.左偏树跟二叉堆比起来,就是一棵真正意义上的树了,具有左右指针,所以空间开销上稍微大一点,但却带来了便于合并的

学习笔记——左偏树

左偏树是一个堆,为了实现快速合并的操作,我们可以构造一颗二叉树,并且使右子树尽量简短 什么是左偏呢? 定义:一个左偏树的外节点是一个左子树为空或者右子树为空的节点,对于每一个点定义一个距离dist它为到它子树内外节点的最短距离. 一个合法的左偏树节点需要满足堆性以及它的右子树的dist比左子树的dist小. 为什么要这样呢? 这样右子树的dist是严格控制在logn以内的. 于是我们合并的时候,将另一个左偏树与当前左偏树的右子树合并,这样递归下去,则时间复杂度是O(logn)的. 这就是一颗左偏

浅析左偏树的性质及其应用

本文是看了黄源河的论文后才写的 如果本人有哪些地方写得不对的,希望各位大佬改正ORZ 学习C++的大佬应该都会优先队列(原谅我的菜,我连priority_queue都不会拼) 左偏树说到底就是一个升级版的堆 因为左偏树拥有所有堆拥有的功能比如说插入一个节点,取出堆顶和删除堆顶 我们的左偏树的优秀到底体现在哪呢? 左偏树可以合并两个堆!!! 如果我们用普通的做法合并两个堆是需要O(N)的时间 那么如果合并操作非常多 那么堆就不在实用了 先来规定左偏树的一些概念 外节点:一个没有右儿子的节点成为外节

左偏树初步 bzoj2809 &amp; bzoj4003

看着百度文库学习了一个. 总的来说,左偏树这个可并堆满足 堆的性质 和 左偏 性质. bzoj2809: [Apio2012]dispatching 把每个忍者先放到节点上,然后从下往上合并,假设到了这个点 总值 大于 预算,那么我们把这个 大根堆 的堆顶弹掉就好了,剩下的就是可合并堆. 感谢prey :) 1 #include <bits/stdc++.h> 2 #define rep(i, a, b) for (int i = a; i <= b; i++) 3 #define dr

hdu 1512 Monkey King 左偏树

题目链接:HDU - 1512 Once in a forest, there lived N aggressive monkeys. At the beginning, they each does things in its own way and none of them knows each other. But monkeys can't avoid quarrelling, and it only happens between two monkeys who does not kn

POJ3016-K-Monotonic(左偏树+DP)

我觉得我要改一下签名了……怎么会有窝这么啰嗦的人呢? 做这题需要先学习左偏树<左偏树的特点及其应用> 然后做一下POJ3666,这题的简单版. 思路: 考虑一下维护中位数的过程原数组为A,找到的不降数列为B当对于A的前n个数已经找好了最优解B[1…n],可知此时A被分成很多块,并被一些大顶堆记录,假设第i块有num个数,那么第i个堆维护这一块的最小的(num+1)/2个数,堆顶即为中位数.假设已经处理好前7个数,被分为两块 ([a,b],c,d) ([h,e],f) (每一块按升序排列,[]中

模板:左偏树

如果你知道priority_queue的话,那自然就知道左偏树的目的了. 左偏树的目的和优先队列一致,就是求出当前所在堆中的最大(小)值. 但是我们作为高贵的C++选手,我们为什么还要学习左偏树呢. 当然是因为priority_queue太!慢!了! ———————————————————————————————————— 概念引入: 对于左偏树,我们引入两个概念: 外节点:如果该节点的左子树或右子树为空,那么该节点为外节点. 距离(dis):该节点到达最近的外节点经过的边的个数. 我们同时将优

浅谈左偏树在OI中的应用

Preface 可并堆,一个听起来很NB的数据结构,实际上比一般的堆就多了一个合并的操作. 考虑一般的堆合并时,当我们合并时只能暴力把一个堆里的元素一个一个插入另一个堆里,这样复杂度将达到\(\log(|A|)+\log(|B|)\),极限数据下显然是要T爆的. 所以我们考虑使用一种性价比最高的可并堆--左偏树,它的思想以及代码都挺简单而且效率也不错. 学习和参考自这里 What is Leftist Tree 左偏树,顾名思义就是像左偏的树,但是这样抽象的表述肯定是不符合我们学OI的人的背板子

关于左偏树的一些东东

大概所有的预备知识这里都有https://baike.baidu.com/item/%E5%B7%A6%E5%81%8F%E6%A0%91/2181887?fr=aladdin 例题1:洛谷 P3377 [模板]左偏树(可并堆) 383通过 1.2K提交 题目提供者HansBug 站长团 标签 难度提高+/省选- 时空限制1s / 128MB 提交 讨论 题解 最新讨论更多讨论 加了路径压缩就WA,路过dal… 左偏树用指针写会MLE吗..… m,n写反了也可以过,数据有… 哪位大神有pbds库