左偏树是一种常用的优先队列(堆)结构。与二叉堆相比,左偏树可以高效的实现两个堆的合并操作。
左偏树实现方便,编程复杂度低,而且有着不俗的效率表现。
它的一个常见应用就是与并查集结合使用。利用并查集确定两个元素是否在同一集合,利用左偏树确定某个集合中优先级最高的元素。
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