左偏树基础教学

感觉这两天数据结构没啥可说的

但是有个左偏树 听起来很高级但是特别简单

那我就说一下吧

 

左偏树教学


左偏树 顾名思义 往左偏的树

它肯定不是BST 因为我们的终极目标是把他转成一条链什么的

左偏树并不是为了快速访问所有的节点而设计的,它的目的是快速访问最小节点 以及在对树修改后快速的恢复堆性质。

恰好走了相反的路的左偏树 依旧靠着自己的特点完成了自己的职能

事实上往右偏是一样的因为二叉树可以转

你只要知道以下几点:

(以下均以小根堆为例)

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

堆性质。便于我们更高效的查找最值

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

这里我们定义一个距离数组dist[]

它表示的就是节点到叶子的距离

同时 如果一个节点只有左儿子(不可能只有右儿子)它的dist值是0

所以举个例子 一条链每个节点dist都是0

 

好 研究完两个性质 我们考虑怎么实现操作

1.插入 查询

同二叉堆

2.合并两颗左偏树

重头戏来了!

让我们先看代码

1 int Merge(int x,int y) {
2     if(!x || !y) return x | y;
3     if(v[x] > v[y] || (v[x] == v[y] && x > y)) swap(x,y);
4     rs[x] = Merge(rs[x],y);
5     pa[rs[x]] = x;
6     if(dist[ls[x]] < dist[rs[x]]) swap(ls[x],rs[x]);
7     dist[x] = dist[rs[x]] + 1;
8     return x;
9 }

是不是特别短?

(啪!)

我们考虑暴力合并启发式合并  拆除较小的堆中的每一个节点加进较大的节点中 复杂度O(nlgn*单步复杂度) = O(nlg2n)或者O(nlgn)什么的

但是 优秀的左偏树可以做到O(n)完成n个节点的合并 O(lgn)完成两颗n个节点子树的合并

可能会有dalao觉得直接讲第二个就能显然第一个 但是我还是suo一下

其实我们的复杂度瓶颈就在于合并 因为查询直接O(1) 二叉堆都能做到

所以左偏树可以理解为 事先留出右边给要合并的另一个树

树是递归定义的 所以每次合并的时候只需要递归处理就OK

所以合并的时候:

1.找到两颗树中树根值较小的一棵树做新树根

2.把右子树和另一棵树合并直到一棵树为空为止

关于子问题的信息 我们只需要子树树根的编号和dist

然后考虑可能放下去合并的树比较大 导致右边比左边多

判断一下直接交换就可以了

然后左偏树左儿子dist一定大于右儿子 所以整棵树的dist就是右儿子dist+1

如果没有右儿子dist是0所以一开始规定dist[0] = -1

这里就可以发现一个事情 由于左儿子较大 而且求根节点dist的时候我们不去管他

所以 若一棵左偏树的dist为 k,则这棵左偏树至少有2k+1-1个节点。

所以 复杂度得到了保证,即:

一棵 N 个节点的左偏树距离最多为[log2(n+1)-1](下取整)

由于分解的次数不会超过 log2(n)+log2(m) 其中 n 和 m 分别为左偏树 A 和 B 的节点个数。因此合并操作最坏情况下的时间复杂度为O(lg(nm)) 也就是O(lgn)

同时 插入操作也可以看成是一个只有一个点的左偏树和一颗大树合并 所以是O(lgn)

3.删除某个特定元素

这个做不到

但是我们能做到删除堆顶最值 而大部分左偏树的题目只有这个要求

删完之后把左右子树合并就OK了

--------------------更新分割线----------------

然后这里填一下坑 如何O(n)建一棵左偏树

其实特别简单 考虑合并果子 每次贪心找最小的合并

那么需要:

合并n/2次1个节点

合并n/4次2个节点

合并n/8次4个节点

...

合并复杂度是严格O(log2n)

所以总复杂度就是O(n*Σi/2^i) = O(n*(2 - (k+2)/2^k)) = O(n)

(常数忽略,k是总深度)

-----------------------------------------------------

Code:(luoguP3377)

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 #include<cmath>
 5 #include<queue>
 6 #include<vector>
 7 #define ms(a,b) memset(a,b,sizeof a)
 8 #define rep(i,a,n) for(int i = a;i <= n;i++)
 9 #define per(i,n,a) for(int i = n;i >= a;i--)
10 #define inf 2147483647
11 using namespace std;
12 typedef long long ll;
13 ll read() {
14     ll as = 0,fu = 1;
15     char c = getchar();
16     while(c < '0' || c > '9') {
17         if(c == '-') fu = -1;
18         c = getchar();
19     }
20     while(c >= '0' && c <= '9') {
21         as = as * 10 + c - '0';
22         c = getchar();
23     }
24     return as * fu;
25 }
26 const int N = 500005;
27 //head
28 int n,m;
29 int v[N],pa[N];
30 int ls[N],rs[N],dist[N];
31 int Merge(int x,int y) {
32     if(!x || !y) return x | y;
33     if(v[x] > v[y] || v[x] == v[y] && x > y) swap(x,y);
34     rs[x] = Merge(rs[x],y);
35     pa[rs[x]] = x;
36     if(dist[ls[x]] < dist[rs[x]]) swap(ls[x],rs[x]);
37     dist[x] = dist[rs[x]] + 1;
38     return x;
39 }
40 
41 int Del(int x) {
42     int tmp = v[x];
43     v[x] = -1;
44     pa[ls[x]] = pa[rs[x]] = 0;
45     Merge(ls[x],rs[x]);
46     return tmp;
47 }
48 
49 int gpa(int x) {
50     while(pa[x]) x = pa[x];
51     return x;
52 }
53 
54 int main() {
55     n = read(),m = read();
56     rep(i,1,n) v[i] = read();
57     while(m--) {
58     int op = read();
59     if(op == 1) {
60         int x = read();
61         int y = read();
62         if(~v[x] && ~v[y] && x ^ y) Merge(gpa(x),gpa(y));
63     } else {
64         int x = read();
65         if(v[x] == -1) {puts("-1");continue;}
66         int y = gpa(x);
67         printf("%d\n",Del(y));
68     }
69     }
70     return 0;
71 }
View Code

 

posted @ 2018-10-17 16:15  白怀潇  阅读(141)  评论(0编辑  收藏  举报