定義
左偏樹(Leftist Tree)是一種可並堆的實現。左偏樹是一棵二叉樹,它的節點除了和二叉樹的節點一樣具有左右子樹指針( left, right)外,還有兩個屬性,鍵值和距離(dist)。
先引入一個概念
外節點:一個左子樹為空或者右子樹為空即可在其子樹並入新元素的節點
距離:父節點到外節點最少的經歷的邊數
所以對於外節點,dist(i)=0;
性質
左偏樹的本質是一顆有序的二叉樹,故滿足堆性質
- 節點的鍵值小於或等於它的左右子節點的鍵值
左偏樹,顧名思義,是一顆向左偏的樹,因為左邊節點多而右邊稀少,那么順着右子樹查找外節點一定比順着左子樹查找經過的邊數少,這又引出了左偏樹的兩條性質
-
節點的左子節點的距離不小於右子節點的距離
-
節點的左子節點右子節點也是一顆左偏樹
因為一個節點的dist實質上就是這個節點一直順着右邊查找外節點的距離,那么
- 節點的距離等於它的右子節點的距離加1
即dist(rt)=dist(r)+1;又因為外節點的dist定為0,而它的右節點為空,根據此性質,故空節點的dist為-1
左偏樹還有一些其他的性質和距離與節點的關系,可以參考 左偏樹的特點及其應用——黃源河
合並
在合並兩個堆的時候,左右子樹會漸漸變的平衡,但要時刻保持左偏樹的性質,即當發現左子樹的dist小於右子樹的dist時,swap
還是以小根堆為例
操作
首先定義一些東西
struct node
{
int l,r,key,dist;//左、右兒子,鍵值,距離
node(int d=-1) {dist=d;}
}t[N];
int fa[N];//用並查集記錄每顆左偏樹堆頂編號
合並
結合上方圖示理解
int Merge(int x,int y)//x,y為兩棵樹堆頂的編號
{
//如果一顆樹為空,返回另一棵樹
if(!x) return y;
if(!y) return x;
if(t[x].key > t[y].key) swap(x,y);//選擇x為新樹,若其根節點鍵值大於y,則交換
t[x].r=Merge(t[x].r,y);//然后將y與x的右子樹合並
fa[t[x].r]=x;//將右子樹的父節點賦為x
if(t[t[x].l].dist < t[t[x].r].dist) swap(t[x].l,t[x].r);//為保證左子節點的距離不小於右子節點的距離,合並完后判斷是否需要交換左右子樹
t[x].dist=t[t[x].r].dist+1;//最后更新根節點的dist值
return x;
}
插入
可看做與只有單點的左偏樹合並
void push(int x,int y)//將y插入到編號為x的左偏樹中
{
Merge(x,y);
}
刪除
可看做將當前節點的左右兒子合並
void pop(int x)//刪除編號為x的節點所在左偏樹的堆頂元素
{
x=findf(x);//先找到堆頂編號
int tmp=Merge(t[x].l,t[x].r);
fa[x]=fa[t[x].l]=fa[t[x].r]=tmp;//重置父親
}
查詢
int top(int x)//返回編號為x的節點所在左偏樹的堆頂的鍵值
{
return t[findf(x)].key;
}
模板
注意當堆中有多個最小值時,刪除原序列中靠前的
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
#include <algorithm>
#define N 100005
using namespace std;
int n,m;
int fa[N];
struct node
{
int l,r,key,dist;
node(int d=-1) {dist=d;}
}t[N];
int findf(int x)
{
if(x==fa[x]) return x;
return fa[x]=findf(fa[x]);
}
void con(int x,int y)
{
x=findf(x),y=findf(y);
fa[x]=y;
}
int top(int x){return t[findf(x)].key;}
int Merge(int x,int y)
{
if(!x) return y;
if(!y) return x;
if(t[x].key > t[y].key||(t[x].key==t[y].key&&x>y)) swap(x,y);
t[x].r=Merge(t[x].r,y);fa[t[x].r]=x;
if(t[t[x].l].dist < t[t[x].r].dist) swap(t[x].l,t[x].r);
t[x].dist=t[t[x].r].dist+1;
return x;
}
void pop(int x)
{
x=findf(x);
int tmp=Merge(t[x].l,t[x].r);
fa[x]=fa[t[x].l]=fa[t[x].r]=tmp;
t[x].l=t[x].r=t[x].key=0;t[x].dist=-1;//因為數據有可能給到已經被刪過的點,所以需要將被刪除節點清空
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) {scanf("%d",&t[i].key);t[i].dist=0;fa[i]=i;}
for(int i=1;i<=m;i++)
{
int opt,x,y;scanf("%d%d",&opt,&x);
if(opt==1)
{
scanf("%d",&y);
if(!t[x].key||!t[y].key) continue;//若這個數被刪除,不合並
int fx=findf(x),fy=findf(y);
if(fx!=fy) Merge(fx,fy);//不在一個集合里才合並
}
else
{
if(!t[x].key) printf("-1\n");
else {printf("%d\n",top(x));pop(x);}
}
}
return 0;
}
參考來源
百度百科
https://baike.baidu.com/item/%E5%B7%A6%E5%81%8F%E6%A0%91/2181887?fr=aladdin#2
圖解數據結構(9)——左偏樹 http://www.cnblogs.com/yc_sunniwell/archive/2010/06/28/1766756.html
可並堆?左偏樹? ——MaxMercer http://blog.csdn.net/maxmercer/article/details/75136683
左偏樹(可並堆)——yew1eb http://blog.csdn.net/yew1eb/article/details/19349099