左偏树 模板
左偏树(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;
}