左偏树基础教学
感觉这两天数据结构没啥可说的
但是有个左偏树 听起来很高级但是特别简单
那我就说一下吧
左偏树教学
左偏树 顾名思义 往左偏的树
它肯定不是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)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 }