左偏树介绍

来源:互联网 发布:java中scanner的作用 编辑:程序博客网 时间:2024/04/29 06:08

左偏树是一种优先队列,虽然有些简陋,但它可以比较高效的实现队列的合并操作,所以在一些涉及到最值,以及合并的问题中,不妨考虑下这种数据结构。

就开门见山了,下方是左偏树的结构体代码(我自己喜欢这种把数据放在结构体,并用一个数组提前开好内存的做法啦,当然你也可以用指针,也可以把这些数据分开放在不同的数组里,一个数据结构,只要可以实现就好了,实现的方法就是看使用者的习惯了)

struct Heap{int l,r,f,v,h;//l:左节点的编号//r:右节点的编号//f:父节点的编号//v:优先级(这个数值作为优先队列的排序依据)//h:这个节点的高度(下面会有说明)};

结合图片:(图片来自百度百科,侵删)


可以看出这是一棵二叉树,且根据它优先级越大,越接近根节点的性质(优先级最大的点就是根节点),可看出这也是一个堆。

图中的小圆内的数字就是结构体里的v(可以看出这颗二叉树的节点v值越小,优先级越大)。

小圆旁蓝色的数字就是h(貌似大家都喜欢叫这个d,距离的意思,但是自己就是觉得这个数字当做高度更好理解额),h的定义是这样的:

  1. 如果一个节点没有右节点,那么这个节点的高度是0,(一棵树下面没有节点了当然就在地面上,就没有高度了,真是矛盾百出的比喻,=_=)
  2. 如果有右节点,那么这个右节点的高度是右节点的高度加一。
好了,我们知道这个数据结构了,所以来造一棵树吧……

造树代码:
void init(int v){heap[++tot].v=v;heap[tot].l=heap[tot].r=heap[tot].h=0;heap[tot].f=tot;//其实自己感觉这代码想怎么写怎么写啊,自己写一个init还可能误导读者//反正大家明白大概是这么回事就好了,世间没有绝对的事,别拿这个代码当标准}
看到f值的初始化方法,是不是有一点并查集的感觉?
恩恩,当我们想找到一棵树的根时,可以用这个代码:
int find(int x){ heap[x].f==x?x:find(heap[x].f); }//为了让多次查找的时间复杂度变为O(1),可以这样写int find(int x){ heap[x].f==x?x:heap[x].f=find(heap[x].f); }//完全就是并查集的写法额
极快地返回优先级最高的数据,这就是一个优先队列了。

接下来,就是合并两棵左偏树了
先放图好了:(图片来源:http://www.cnblogs.com/yc_sunniwell/archive/2010/06/28/1766756.html,这也是一篇介绍左偏树的博客,侵删啦)



从图中可看出合并两个左偏树的大致过程是这样的:
  1. 先比较顶点的优先级,优先级大的作为被插入树,小的作为插入树。
  2. 把被插入树的右边拿出,与插入树进行一个完整的插入过程,这个过程结束后,得到一个新的左偏树,作为被插入树的右子树(新树的顶点作为被插入树顶点的右顶点)。(可以看出这个插入是一个不断迭代的过程,就像dfs一样)
  3. 比较新树的左右顶点的高度(左右子树的高度),使高度较大的顶点(子树)作为左顶点(左子树)
  4. 根据右子树重新确定根节点的高度(其实自己一直想吐槽自己硬把别人叫做距离的东西叫做高度,没办法,自己改的东西,跪着也要叫完!)
毕竟图是别人的(懒惰且恶劣的自己……),可能你看到这里没怎么看懂,所以上代码吧:
int merge(int a,int b){if(a==0) return b;if(b==0) return a;if(heap[a].v<heap[b].v) swap(a,b);heap[a].r=merge(b,heap[a].r);heap[heap[a].r].f=a;if(heap[heap[a].l].h<heap[heap[a].r].h) swap(heap[a].l,heap[a].r);if(heap[a].r==0) heap[a].h=0;else heap[a].h=heap[heap[a].r].h+1;return a;}
从代码里我们可以看出来,这棵树有着一种左偏的性质(喂喂,说什么啊),不妨看看图吧,一般情况下,树的左边要比右边重(顶点更多),右边的顶点更少,为什么呢?由于插入都是往右插入的,而且右边的顶点数不会超过n/2,那么插入的时间复杂度不会超过O(log n/2),而且,子树的右顶点为空的概率也很大,这样就可以保证比较高效的完成两个优先队列的合并。

接下来说一下pop操作
额,至少这个比std::priority_queue的pop好一点
int pop(int x){int l=node[x].l;int r=node[x].r;node[l].f=l;node[r].f=r;node[x].h=node[x].l=node[x].r=0;return merge(x,merge(l,r));}
返回的是被pop顶点和它的子节点组成的左偏树。
(其实这个代码不是很严谨,因为被pop顶点的上一个顶点还存有这个顶点的信息,但是如果我们使用了前面说到的第二个find()代码,查找顶点速度可以很快,上一个顶点的信息却丢失了,这个信息是无法被修改的。不过一道题目可能并不会要求我们实现如此严谨的功能,毕竟不是内置的数据结构,所以仔细分析题目的要求,确定需要实现的功能才是关键)

接下来放两道题,一题很水,一题一点也不水……
HDU 1512 Monkey King 题解
HDU 5575 Discover Water Tanky 题解

0 0
原创粉丝点击