如果学过treap(一种平衡二叉树),笛卡尔树应该很好理解
本文只介绍基础的建树,没有深入的东西。(你懂我意思吧)
笛卡尔树也是二叉树+堆
一、二叉树与堆的概念
二、笛卡尔树的结构
笛卡尔树可以按顺序插入一个数组,我们将下标当值插入二叉树,然后数组的值作为“优先级”,维护堆的性质。
盗一份网上的图你们参考一下,结构就是这个样子
三、笛卡尔树的构造
先给整块的代码和整体的思想,再对代码仔细讲一下
整体的思想:
由于数组按顺序插入,下标一定递增,因此每个点根据二叉树一定会插入当前树的最右子节点,接下去维护堆的性质进行一些操作,我们用栈维护右链,每次插入都在栈中找到第一个比当前的数值小的,那个点就是当前的父节点,弹出栈的链就作为当前节点的左儿子,然后当前点入栈。
由于每个点入栈出栈各一次,构造复杂度为O(N)
///ch[][]子节点,0为左儿子,1为右儿子
///val[]记录当前节点的值
///sta[]为栈
///sz单纯用来记录插入了几个点(可以不要)
void insert(int id,int v){
int st_tot = tot;
ch[id][0] = ch[id][1] = 0;
while (tot>=0){
if (val[sta[tot]] <= v){
ch[sta[tot]][1] = id;
break;
}
else tot--;
}
if (tot < st_tot) {ch[id][0] = sta[tot+1];if (sta[tot+1]==root) root = id;}
val[id] = v;
sta[++tot] = id;
sz++;
}
这个板子是自己打的,可能有些地方不够简洁,如果可以更简洁,请评论留言告诉我。
那么首先看第一行
int st_tot = tot;
st_tot,称之为本轮开始时的tot,这个变量的用处是判断本次插入栈中是否有元素弹出,如果有,那么当前节点左儿子就有啦,即下面这一行
if (tot < st_tot) {ch[id][0] = sta[tot+1];if (sta[tot+1]==root) root = id;}
这里还有一个操作就是更新笛卡尔树的根节点,很明显,根节点发生改变只有在当前插入节点把原先根节点作为儿子时,因此此时根节点转移。具体为什么会让当前节点的左儿子连最后一个出栈的元素,请看下面的讲解
ch[id][0] = ch[id][1] = 0;
while (tot>=0){
if (val[sta[tot]] <= v){
ch[sta[tot]][1] = id;
break;
}
else tot--;
}
首先我们明确:栈内存的是当前笛卡尔树的右链,即ch[根]->r_son->r_son->r_son...
上面这一片代码首先初始化了一下当前节点的两个儿子
然后弹出栈内元素,弹出来的节点,都是对应数值大于当前节点的,他们应该作为当前节点的儿子,否则堆的性质不符合。
我们弹出元素直到栈内维护的剩余的右链末的那个点对应数值比当前点小,此时,当前节点连作右链末的右儿子,此时的树符合笛卡尔树的性质。
那么丢掉的链咋办?丢掉的链头连到当前节点的左儿子。
因为这一串链下标比当前小且值比当前节点大。
下面的代码就不讲了。
四、笛卡尔树的基基基础功能
①我们回想一下维护的结构:左儿子下标更小,右儿子下标更大,两个儿子的对应数值都比当前节点大
②笛卡尔树是无旋treap实现build函数的思想。
③其他不会,有缘回来补充(咕咕咕)
五、HDU 1506的题解
六、模板的一些修改
笛卡尔树的根节点求法:
root不用像上述的代码一样每次更新,我们知道栈实时维护右链,所有sta[0]就是根节点啦