左偏樹是一種常用的優先隊列(堆)結構。與二叉堆相比,左偏樹可以高效的實現兩個堆的合並操作。
左偏樹實現方便,編程復雜度低,而且有著不俗的效率表現。
它的一個常見應用就是與並查集結合使用。利用並查集確定兩個元素是否在同一集合,利用左偏樹確定某個集合中優先級最高的元素。
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