无旋式treap

定义与原理

定义

  • 无旋式 Treap 本质上是一棵二叉搜索树,每个节点除了存储键值(Key)外,还额外维护一个随机优先级(Priority)。它的键值满足二叉搜索树的性质,即左子树的所有节点键值小于根节点键值,右子树的所有节点键值大于根节点键值;而优先级满足堆的性质,通常是大根堆,即每个节点的优先级大于其左右子节点的优先级。

原理

  • 无旋式 Treap 与传统的有旋 Treap 不同,它不通过旋转操作来维护树的平衡,而是利用分裂(Split)和合并(Merge)这两个基本操作来实现插入、删除等操作,从而保证树的平衡性。随机赋予的优先级使得树在平均情况下具有较好的平衡性,避免了二叉搜索树可能出现的极端不平衡情况(如退化为链表),进而保证了各种操作的时间复杂度接近 。

常见操作

分裂操作(Split)

  • 功能:将一棵 Treap 按照给定的键值 分裂成两棵 Treap,一棵包含所有键值小于等于 的节点,另一棵包含所有键值大于 的节点。
  • 实现思路:从根节点开始递归地比较当前节点的键值与 的大小。如果当前节点的键值小于等于 ,则将当前节点及其左子树划分到左子树中,并继续对其右子树进行分裂;反之,则将当前节点及其右子树划分到右子树中,并继续对其左子树进行分裂。

合并操作(Merge)

  • 功能:将两棵 Treap 合并成一棵 Treap,要求第一棵 Treap 的所有键值都小于第二棵 Treap 的所有键值。
  • 实现思路:比较两棵 Treap 根节点的优先级,选择优先级较高的节点作为新的根节点。如果第一棵 Treap 的根节点优先级高,则将其右子树与第二棵 Treap 合并作为新的右子树;否则,将第二棵 Treap 的左子树与第一棵 Treap 合并作为新的左子树。

插入操作

  • 功能:向 Treap 中插入一个新的节点。
  • 实现思路:首先创建一个新节点,其键值为要插入的值,优先级为随机生成。然后将原 Treap 按照新节点的键值进行分裂,得到两棵 Treap,再将新节点与这两棵 Treap 依次合并。

删除操作

  • 功能:从 Treap 中删除一个指定键值的节点。
  • 实现思路:将原 Treap 按照要删除的键值进行分裂,得到两棵 Treap,一棵包含所有键值小于等于要删除键值的节点,另一棵包含所有键值大于要删除键值的节点。然后再将包含小于等于要删除键值节点的 Treap 按照要删除键值减 1 进行分裂,得到三棵 Treap,中间那棵只包含要删除的节点,将其舍弃,最后将剩下的两棵 Treap 合并。

板子

#include <bits/stdc++.h>
using namespace std;
const int N = 1e4 + 5;
struct node
{
	int l, r;
	int v;
	int key;
	int siz;
} tr[N * 4];
int n, root, idx;

void pushup(int p)
{
	tr[p].siz = tr[tr[p].l].siz + tr[tr[p].r].siz + 1;
}
void split(int p, int v, int &x, int &y) // 根据值V来分裂,p为当前子树根节点,分出x,y两棵子树
{
	if (!p)
	{
		x = y = 0;
		return;
	}
	if (tr[p].v <= v)
	{
		x = p;
		split(tr[x].r, v, tr[x].r, y);
	}
	else
	{
		y = p;
		split(tr[x].l, v, x, tr[x].l);
	}
	pushup(p);
}
int merge(int x, int y) // 合并
{
	if (!x || !y)
		return x + y;
	if (tr[x].key < tr[y].key)
	{
		tr[x].r = merge(tr[x].r, y);
		pushup(x);
		return x;
	}
	else
	{
		tr[y].r = merge(x, tr[y].r);
		pushup(y);
		return y;
	}
	// 返回根节点
}
int newnode(int v)
{
	tr[++idx].v = v;
	tr[idx].key = rand();
	tr[idx].siz = 1;
	return idx;
}
void insert(int v) // 插入
{
	int x, y, z;
	split(root, v, x, y);
	z = newnode(v);
	root = merge(merge(x, z), y);
}
void del(int v) // 删除
{
	int x, y, z;
	split(root, v, x, z);
	split(x, v - 1, x, y); // y是被删除的点
	y = merge(tr[y].l, tr[y].r);
	root = merge(merge(x, y), z);
}
int get_k(int p, int k) // 返回第k个节点
{
	if (k <= tr[tr[p].l].siz)
	{
		return get_k(tr[p].l, k);
	}
	if (k == tr[tr[p].l].siz + 1)
		return p;
	return get_k(tr[p].r, k - tr[tr[p].l].siz - 1);
}
void solve(int p)
{
}
void modify(int l, int r) // 左值,右值  区间处理
{
	int x, y, z;
	split(root, l - 1, x, y);
	split(y, r - l + 1, y, z);
	solve(y);
	root = merge(x, merge(y, z));
	return;
}

int allmerge(int x, int y)//将乱序的两棵树重构合并
{
	if (!x || !y)
		return x + y;
	if (tr[x].key > tr[y].key)
	{
		swap(x,y);
	}
	int a,b;
	split(y,tr[x].v,a,b);
	tr[x].l=allmerge(tr[x].l,a);
	tr[x].r=allmerge(tr[x].l,b);
	pushup(x);
}
int main()
{

	return 0;
}

应用场景

  • 数据存储与检索:可用于高效地存储和检索数据,例如在数据库系统中实现索引结构。
  • 区间查询:结合分裂和合并操作,可以方便地实现区间查询,例如查找某个范围内的所有元素。
  • 动态数据处理:适用于需要频繁插入和删除操作的动态数据集,能够在保证操作效率的同时维护数据的有序性。
posted @ 2025-02-25 07:47  流氓兔LMT  阅读(81)  评论(0)    收藏  举报