左偏树(Leftist Heap/Tree)简介及代码

左偏树是一种常用的优先队列(堆)结构。与二叉堆相比,左偏树可以高效的实现两个堆的合并操作。

左偏树实现方便,编程复杂度低,而且有着不俗的效率表现。

它的一个常见应用就是与并查集结合使用。利用并查集确定两个元素是否在同一集合,利用左偏树确定某个集合中优先级最高的元素。

  1 #include <cstdio>
  2 #include <cstring>
  3 #include <algorithm>
  4
  5 template <class T>
  6 struct HeapNode
  7 {
  8     typedef HeapNode<T> Node;
  9     Node* lch;
 10     Node* rch;
 11     T val;
 12     int dist;
 13
 14     HeapNode(const T& _val):lch(0),rch(0),val(_val),dist(0) {}
 15
 16     void clear()
 17     {
 18         if(lch) lch->clear();
 19         if(rch) rch->clear();
 20         delete this;
 21     }
 22 };
 23
 24 template <class T,class Comp>
 25 struct LeftistHeap
 26 {
 27     typedef HeapNode<T> Node;
 28     typedef LeftistHeap<T,Comp> Heap;
 29
 30     Node* root;
 31     Comp cmp;
 32
 33     LeftistHeap():root(0) {}
 34     ~LeftistHeap()
 35     {
 36         clear();
 37     }
 38
 39     void clear()
 40     {
 41         if(root) root->clear();
 42         root=0;
 43     }
 44
 45     Node* merge(Node* A,Node* B)
 46     {
 47         if(!A) return B;
 48         if(!B) return A;
 49
 50         if(cmp(B->val,A->val)) std::swap(A,B);
 51         A->rch=merge(B,A->rch);
 52
 53         if(!A->lch || A->rch->dist > A->lch->dist)
 54             std::swap(A->lch,A->rch);
 55         A->dist = (A->rch) ? A->rch->dist + 1 : 0 ;
 56
 57         return A;
 58     }
 59
 60     void push(const T& _val)
 61     {
 62         Node* nNode=new Node(_val);
 63         root=merge(root,nNode);
 64     }
 65
 66     Heap& operator << (const T& _val)
 67     {
 68         push(_val);
 69         return *this;
 70     }
 71
 72     T top()
 73     {
 74         return root->val;
 75     }
 76
 77     void pop()
 78     {
 79         Node* temp=root;
 80         root=merge(temp->lch,temp->rch);
 81         delete temp;
 82     }
 83
 84     Heap& operator >> (T& _dest)
 85     {
 86         _dest=top();
 87         pop();
 88         return *this;
 89     }
 90
 91     void merge(Heap& _other)
 92     {
 93         this->root=merge(this->root,_other.root);
 94         _other.root=0;
 95     }
 96
 97     bool empty()
 98     {
 99         return root==0;
100     }
101 };

Leftist Heap

定义左偏树节点的“距离”(dist)为从其右子树开始,一直向右走的路径总长。特别地,若某个节点没有右孩子,其dist值为0。

树中的每个节点都必须满足左孩子的dist值不小于右孩子(如果有的话)的dist值。

和大多数可并堆一样,左偏树的核心操作就是合并(Merge)操作。

(以下伪代码以小根堆为例,节点的数据域记为val)

function merge(Node* A,Node* B)

if(A和B中某一个为空) return 另一个   //特判,同时也是递归终止的条件

交换A和B(如果需要的话),使得A的val小于B的val

A->rch = merge(B,A->rch)

if(A的左孩子的dist小于右孩子的dist或A的左孩子不存在) 交换A的左、右孩子

根据A的右孩子更新A的dist

return A

实现细节详见代码。

有了合并操作,其他的也就水到渠成了:

插入(push):建立一个新节点,然后把它视为一个左偏树,将其与已有的合并。

删除(pop):删除其根节点,合并原先根节点的左右孩子。

附一道左偏树+并查集的练习题:

  1 #include <cstdio>
  2 #include <cstring>
  3 #include <algorithm>
  4
  5 template <class T>
  6 struct HeapNode
  7 {
  8     typedef HeapNode<T> Node;
  9     Node* lch;
 10     Node* rch;
 11     T val;
 12     int dist;
 13
 14     HeapNode(const T& _val):lch(0),rch(0),val(_val),dist(0) {}
 15
 16     void clear()
 17     {
 18         if(lch) lch->clear();
 19         if(rch) rch->clear();
 20         delete this;
 21     }
 22 };
 23
 24 template <class T,class Comp>
 25 struct LeftistHeap
 26 {
 27     typedef HeapNode<T> Node;
 28     typedef LeftistHeap<T,Comp> Heap;
 29
 30     Node* root;
 31     Comp cmp;
 32
 33     LeftistHeap():root(0) {}
 34     ~LeftistHeap()
 35     {
 36         clear();
 37     }
 38
 39     void clear()
 40     {
 41         if(root) root->clear();
 42         root=0;
 43     }
 44
 45     Node* merge(Node* A,Node* B)
 46     {
 47         if(!A) return B;
 48         if(!B) return A;
 49
 50         if(cmp(B->val,A->val)) std::swap(A,B);
 51         A->rch=merge(B,A->rch);
 52
 53         if(!A->lch || A->rch->dist > A->lch->dist)
 54             std::swap(A->lch,A->rch);
 55         A->dist = (A->rch) ? A->rch->dist + 1 : 0 ;
 56
 57         return A;
 58     }
 59
 60     void push(const T& _val)
 61     {
 62         Node* nNode=new Node(_val);
 63         root=merge(root,nNode);
 64     }
 65
 66     Heap& operator << (const T& _val)
 67     {
 68         push(_val);
 69         return *this;
 70     }
 71
 72     T top()
 73     {
 74         return root->val;
 75     }
 76
 77     void pop()
 78     {
 79         Node* temp=root;
 80         root=merge(temp->lch,temp->rch);
 81         delete temp;
 82     }
 83
 84     Heap& operator >> (T& _dest)
 85     {
 86         _dest=top();
 87         pop();
 88         return *this;
 89     }
 90
 91     void merge(Heap& _other)
 92     {
 93         this->root=merge(this->root,_other.root);
 94         _other.root=0;
 95     }
 96
 97     bool empty()
 98     {
 99         return root==0;
100     }
101 };
102
103 #include <functional>
104
105 const int maxN=100005;
106
107 int N,M;
108 int idx[maxN];
109
110 int father(int x)
111 {
112     return idx[x]==x ? x : idx[x]=father(idx[x]) ;
113 }
114
115 LeftistHeap<int,std::greater<int> > heap[maxN];
116
117 void init()
118 {
119     for(int i=0;i<maxN;i++) heap[i].clear();
120     for(int i=0;i<maxN;i++) idx[i]=i;
121 }
122
123 bool solve()
124 {
125     init();
126
127     if(scanf("%d",&N)==EOF) return false;
128     for(int i=1;i<=N;i++)
129     {
130         int s; scanf("%d",&s);
131         heap[i].push(s);
132     }
133
134     scanf("%d\n",&M);
135     while(M--)
136     {
137         int mk1,mk2;
138         scanf("%d%d",&mk1,&mk2);
139
140         int f1=father(mk1);
141         int f2=father(mk2);
142         if(f1==f2)
143         {
144             printf("-1\n");
145             continue;
146         }
147
148         int s1,s2;
149         heap[f1]>>s1;
150         heap[f2]>>s2;
151
152         if(f1<f2)
153         {
154             idx[f2]=f1;
155             heap[f1].merge(heap[f2]);
156             if(heap[f1].empty()) printf("%d\n",std::max(s1,s2)>>1);
157             else printf("%d\n",std::max(heap[f1].top(),std::max(s1,s2)>>1));
158             heap[f1] << (s1>>1) << (s2>>1);
159         }
160         else
161         {
162             idx[f1]=f2;
163             heap[f2].merge(heap[f1]);
164             if(heap[f2].empty()) printf("%d\n",std::max(s1,s2)>>1);
165             else printf("%d\n",std::max(heap[f2].top(),std::max(s1,s2)>>1));
166             heap[f2] << (s1>>1) << (s2>>1);
167         }
168     }
169
170     return true;
171 }
172
173 int main()
174 {
175     while(solve());
176     return 0;
177 }

Problem:ZOJ P2334

时间: 2024-10-25 07:14:49

左偏树(Leftist Heap/Tree)简介及代码的相关文章

【转】左偏树(可合并优先队列)

[可并堆与左偏树] 我们最常用的二叉堆,是最常用的优先队列,它可以在O(logN)内实现插入和删除最小值操作.但是对于合并两个有序的优先队列,二叉堆就显得力不从心了. 左偏树是一种可并堆(Mergeable Heap),意思是可以在O(logN)时间内完成两个堆的合并操作.左偏树(Leftist Tree),或者叫左倾树,左式树,左式堆(Leftist Heap),左堆.顾名思义,它好象是向左偏的,实际上它是一种趋于非常不平衡的二叉树结构,但却能够实现对数级的合并时间复杂度. [左偏树的定义]

左偏树 学习笔记

左偏树(Leftist Tree)是一种可并堆的实现.左偏树是一棵二叉树,它的节点除了和二叉树的节点一样具有左右子树指针( left, right)外,还有两个属性,键值和距离(dist). 键值:是用于比较节点的大小. 距离:节点i称为外节点(external node),当且仅当节点i的左子树或右子树为空 ( left(i) = NULL或right(i) = NULL ):节点i称为外节点(external node),当且仅当节点i的左子树或右子树为空 ( left(i) = NULL或

浅谈左偏树在OI中的应用

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

左偏树学习

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

关于左偏树的一些东东

大概所有的预备知识这里都有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库

BZOJ 1455 罗马游戏 左偏树

题目大意:给定n个点,每个点有一个权值,提供两种操作: 1.将两个点所在集合合并 2.将一个点所在集合的最小的点删除并输出权值 很裸的可并堆 n<=100W 启发式合并不用想了 左偏树就是快啊~ #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #define M 1001001 using namespace std; struct abcd{ abcd

【左偏树】【APIO】Dispatching

2809: [Apio2012]dispatching Time Limit: 10 Sec Memory Limit: 128 MB Submit: 1932 Solved: 967 Description 在一个忍者的帮派里,一些忍者们被选中派遣给顾客,然后依据自己的工作获取报偿.在这个帮派里,有一名忍者被称之为 Master.除了 Master以外,每名忍者都有且仅有一个上级.为保密,同时增强忍者们的领导力,所有与他们工作相关的指令总是由上级发送给他的直接下属,而不允许通过其他的方式发送.

ZOJ 3512 Financial Fraud (左偏树)

题意:给定一个序列,求另一个不递减序列,使得Abs(bi - ai) 和最小. 析:首先是在每个相同的区间中,中位数是最优的,然后由于要合并,和维护中位数,所以我们选用左偏树来维护,当然也可以用划分树来做. 代码如下: #pragma comment(linker, "/STACK:1024000000,1024000000") #include <cstdio> #include <string> #include <cstdlib> #inclu

左偏树初步 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