首页 > 其他分享 >Treap 树

Treap 树

时间:2024-02-08 20:12:59浏览次数:27  
标签:左子 结点 int ret Treap ls 节点

1. 概念

Treap 树是维护二叉查找树平衡的一种方法。它的核心思想是给每个结点设置一个随机的优先级,使它成为一个,即父亲的优先级一定小于(或大于)孩子的优先级。大于则为大根堆,小于则为小根堆。本节使用的是小根堆。这样就可以实现概率上的平衡。

下图是一棵 Treap 树的建树方法。

其中字母是键值,数字是优先级。可以发现,建好树后,中序遍历键值递增,且按优先级满足大根堆。

2. 旋转法

旋转法是 Treap 树调节平衡的经典方法。它的示意图如下:

代码如下:

void rotate(int &u,bool le){
	/*旋转节点为u,le为是否左旋*/
	int k;
	/*k为新的根节点*/
	if(le){
		k=t[u].rs;
		t[u].rs=t[k].ls;
		t[k].ls=u;
	}else{
		k=t[u].ls;
		t[u].ls=t[k].rs;
		t[k].rs=u;
	}
	t[k].sz=t[u].sz;
	/*更新k子树大小*/
	pushup(u);
	/*更新u子树大小*/
	u=k;
	/*更新根节点*/
}

通过旋转法,可以实现以下操作:

  1. 插入结点 \(k\)

根据权值,找到一个合适的位置插入结点 \(k\),并为它分配优先级。接下来只要它的优先级比它的父亲小,就将它向上调整。可以用旋转法来调整。

调整的过程如下图:

代码如下:

void insert(int &u,int k){
	/*插入结点k,现在的子树是u*/
	if(!u){
		/*这里没有结点,可以直接插入*/
		newnode(k);
		/*新建节点*/
		u=tot;
		return;
	}
	if(k<t[u].vl){
		insert(t[u].ls,k);
		/*插入左子树*/
		if(t[t[u].ls].pri<t[u].pri){
			/*左儿子的优先级更小,就右旋*/
			rotate(u,0);
		}
	}else{
		insert(t[u].rs,k);
		/*插入右子树*/
		if(t[t[u].rs].pri<t[u].pri){
			/*右儿子的优先级更小,就左旋*/
			rotate(u,1);
		}
	}
	pushup(u);
	/*更新子树大小*/
}
  1. 删除结点 \(k\)

如果 \(k\) 是叶子节点,直接删除。

如果 \(k\) 只有一个子树,就将子树移到 \(k\) 的位置,直接删除。

否则说明它有两个子节点。将这两个子节点中优先级更小的那个旋转上来,这样 \(k\) 就向下移动一层。直到 \(k\) 成为叶子节点后就可以直接删除了。

代码如下:

void erase(int &u,int k){
	/*删除结点k,此时子树为u*/
	if(k==t[u].vl){
		if(!t[u].ls&&!t[u].rs){
			u=0;return;
			/*叶子节点直接删除*/
		}else if(!t[u].ls||!t[u].rs){
			u=t[u].ls+t[u].rs;return;
			/*有一个子树为空,直接删除*/
		}else if(t[t[u].ls].vl<t[t[u].rs].vl){
			rotate(u,0);erase(t[u].rs,k);
			/*左儿子优先级小,右旋,继续递归删除*/
		}else{
			rotate(u,1);erase(t[u].ls,k);
			/*右儿子优先级小,左旋,继续递归删除*/
		}
	}else if(k<t[u].vl){
		erase(t[u].ls,k);
		/*在左子树上,继续递归删除*/
	}else{
		erase(t[u].rs,k);
		/*在右子树上,继续递归删除*/
	}
	pushup(u);
	/*更新子树大小*/
}
  1. 求 \(k\) 的排名

排名为比 \(k\) 小的数的个数加一。从根节点开始递归,如果这个数大于等于 \(k\),就递归左子树。否则答案为递归右子树的答案左子树的大小加 \(1\)。

代码如下:

void rank(int u,int k,int &ret){
	/*查询比k小的数的个数,此时子树为u,最后答案为ret*/
	if(!u){
		ret=0;
	}else if(k<=t[u].vl){
		/*比k小的数都在左子树*/
		rank(t[u].ls,k,ret);
	}else{
		/*在右子树上*/
		rank(t[u].rs,k,ret);
		ret+=t[t[u].ls].sz+1;
		/*右子树上的排名+左子树大小+1*/
	}
}
  1. 求排名为 \(k\) 的数

从根结点开始递归,如果这个结点左子树的大小加一等于 \(k\),那么答案就为这个结点的权值。否则如果 \(k\) 小于这个点左子树的大小加一,就在左子树上找,否则在右子树上找。

代码如下:

void kth(int u,int k,int &ret){
	/*查询排名为k的数,此时子树为u,最后答案为ret*/
	if(k==t[t[u].ls].sz+1){
		/*找到了*/
		ret=t[u].vl;
	}else if(k<t[t[u].ls].sz+1){
		/*在左子树上*/
		kth(t[u].ls,k,ret);
	}else{
		/*在右子树上*/
		kth(t[u].rs,k-t[t[u].ls].sz-1,ret);
	}
}
  1. 求 \(k\) 的前驱

从根节点开始递归。

如果这个点的权值大于等于 \(k\),就在左子树上找。否则在左子树上找,如果没找到,那么这个点就是答案。

代码如下:

void pre(int u,int k,int &ret){
	/*求k的前驱,此时子树为u,最后答案为ret*/
	if(!u){
		ret=FAIL;return;
	}
	if(k<=t[u].vl){
		/*一定在左子树上*/
		pre(t[u].ls,k,ret);
	}else{
		/*可能是这个点,也可能在右子树*/
		pre(t[u].rs,k,ret);
		/*先在右子树上找*/
		if(ret==FAIL){
			/*如果没找到,答案就是这个点*/
			ret=t[u].vl;
		}
	}
}
  1. 求 \(k\) 的后继

从根节点开始找。

如果这个点的权值小于等于 \(k\),就在右子树上找。否则在左子树上找,如果没找到,那么这个点就是答案。

代码如下:

void suc(int u,int k,int &ret){
	/*求k的后继,此时子树为u,最后答案为ret*/
	if(!u){
		ret=FAIL;return;
	}
	if(k>=t[u].vl){
		/*一定在右子树上*/
		suc(t[u].rs,k,ret);
	}else{
		/*可能是这个点,也可能在左子树*/
		suc(t[u].ls,k,ret);
		/*先在左子树上找*/
		if(ret==FAIL){
			/*如果没找到,答案就是这个点*/
			ret=t[u].vl;
		}
	}
}

例1 洛谷-P3369

代码

标签:左子,结点,int,ret,Treap,ls,节点
From: https://www.cnblogs.com/lrxmg139/p/18012098

相关文章

  • Treap(平衡树)
    Treap前置芝士二叉搜索树(BST),见BST。平衡二叉树(AVL)。先来介绍一下平衡二叉树。背景BST出现以后,人们很快发现一个问题,当其维护一个有序序列时,很可能会退化成链。如图:这样的话,原来\(O(\log{n})\)的复杂度就退化为\(O(n)\),这是我们无法接受的,于是平衡二叉树横空出世......
  • Treap
    前置:BST\(Treap=BST+heap\),通过额外维护堆的性质来避免退化成链的问题。与\(BST\)中结构释义相同的部分不做解释。\(priority\)表示优先级,即该节点在小根堆中的值,\(child[0]\)表示左孩子,\(child[1]\)表示右孩子。\(public\)中,\(empty\):时间复杂度\(O(1)\)。其余操作时间复杂......
  • 平衡树(无旋Treap,范浩强树)学习笔记
    平衡树:YYDS以下是常见的平衡树/要用平衡树实现的算法:Treap(有旋/无旋)Splay树WBLT(WeightBalancedLeafyTree,重量平衡线段树)SBT(SizeBalancedTree,陈启峰树)AVL树B树、B+树笛卡尔树红黑树、左偏红黑树、AA树替罪羊树\(\to\)K-DTree(k-DimensionTree)LT(LeafyTree,平......
  • Treap 学习笔记
    二叉查找树二叉查找树是一棵有点权的二叉树,具有以下几个特征:左孩子的权值小于父亲的权值右孩子的权值大于父亲的权值中序遍历及从小到大排序二叉查找树支持以下几个操作:插入一个数删除一个数找一个数的前驱找一个数的后继询问一个数的排名询问排第几名的数二叉查......
  • Treap
    概述FHQTreap基于Treap发明的“无旋Treap”,代码短,易理解且速度快(在OI算是很优秀的算法了)。FHQTreap核心函数只有两个,分别是分裂和合并。字面意思,就是将某一棵二叉查找树按某种要求分裂成两个或将两个合并成一个。实现变量维护变量名功能root记录所维护......
  • 算法笔记(2)FHQtreap
    原发布于我的个人博客前言FHQtreap绝对是平衡树里最好写,最实用的,他几乎能做所有splay或其它平衡树能做的事,还能可持久化!这篇文章将会介绍FHQtreap的基本操作和维护区间的操作,并附上例题。基本操作FHQtreap的基本操作只有两个,分裂和合并。有些读者可能会问,分裂和合并和平衡树......
  • 【学习笔记】FHQ-Treap
    前置知识:二叉搜索树与二叉堆。1.简介Treap,即Tree+Heap,它的每个结点上存储着一个索引\(key\)和一个值\(val\),其中索引满足二叉堆的性质,值满足二叉搜索树的性质,且索引是随机的。Treap就是通过上述的性质,使树达到平衡。至于为什么索引是随机的,其实很简单:我们插入的每个数的......
  • Atcoder Beginner Contest 324 G Generate Arrays 题解-Treap
    为了更好的阅读体验,请点击这里题目链接套上平衡树板子就能做的很快的题,然后因为是指针存树,因此交换只需要把序列大小较小的挨个拿出来插到相应的地方即可。复杂度\(O(N\log^2N)\)。但是一定要记住不可以直接使用std::swap交换包含带有指针的类的实例(如代码中的Treap类)!......
  • Treap引入
    前置知识treap是由BST和heap组合而成的数据结构,这一点也体现在它的名字上:treap=tree+heapBST中,每个节点的左儿子都比它小,右儿子都比它大,可以实现有序的遍历,但是可能因为数据特殊的排列方式,而退化为线性heap中,每个父节点都是当前子树内权值最大(或最小)的点。在BST的基础上加一个......
  • BST-Treap名次树指针实现板子 Ver2.0
    为了更好的阅读体验,请点击这里这里只有板子没有原理QWQ可实现1.插入x数2.删除x数(若有多个相同的数,只删除一个)3.查询x数的排名(排名定义为比当前数小的数的个数+1)4.查询排名为x的数5.求x的前驱(前驱定义为小于x,且最大的数)6.求x的后继(后继定义为大于x,且......