首页 > 其他分享 >平衡树

平衡树

时间:2024-09-02 20:47:40浏览次数:8  
标签:merge int siz tr Treap key 平衡

FHQ-Treap

概述

FHQ-Treap,又名无旋 Treap。

显然,FHQ-Treap 不使用旋转操作来维护平衡,他利用分裂和合并两个操作维护平衡。

定义结构体

放个代码

const int N = 1e5 + 10;
int tot, root;
struct FHQ_Treap {
    int l, r, val, key, siz;
} tr[N];
#define lc tr[p].l
#define rc tr[p].r
void update(int p) { // 更新 p 的子树大小 
	tr[p].siz = tr[lc].siz + tr[rc].siz + 1;
}
void create(int &p, int x) { // 新建一棵只有一个节点的 Treap 
	p = ++tot;
	tr[p].val = x;
	tr[p].key = wdz();
	tr[p].siz = 1;
}

分裂

分裂操作和两个参数有关,根节点 \(i\) 和关键值 \(key\)。

分裂操作分为按值分类和按排名分类两种,这里以按值分类为例。

分裂操作就是将一棵 Treap 按权值裁成小于等于 \(key\) 或者大于 \(key\) 的两棵 Treap。

放上代码:

void split(int p, int k, int &x, int &y) {
	// 根节点,关键值,以及分裂后的两个节点 
	if (!p) {
		x = y = 0;
		return;
	}
	if (tr[p].val <= k) { // 权值小于等于 k 
		x = p; // 左子树全部属于第一个子树 
		split(rc, k, rc, y); // 分裂右子树 
	} else {
		y = p;
		split(lc, k, x, lc);
	}
	update(p);
}

合并

合并操作就是将两棵 Treap 合并成一棵 Treap。

由于此时两棵 Treap 中,一棵绝对严格小于另一棵。因此我们此时只需要维护堆的性质即可。

因此关键在于将谁作为谁的什么子树。

反复递归即可(和线段树合并的代码还是很像的)。

int merge(int x, int y) { // 返回合并后的树根 
	if (!x || !y) {
		return x + y;
	}
	if (tr[x].key < tr[y].key) { // x 的优先级小于 y 的优先级 
		tr[x].r = merge(tr[x].r, y);
		// 将子树 y 并入子树 x 的右子树 
		update(x);
		return x;
	} else {
		tr[y].l = merge(x, tr[y].l);
		update(y);
		return y;
	}
}

插入

假设要插入的数是 \(x\),那么我们按照 \(x\) 讲 Treap 分裂成 \(a,b\) 两部分,将 \(x\) 与 \(a\) 合并,再与 \(b\) 合并即可。

split(root, k, x, y); 
create(now, k);
root = merge(merge(x, now), y);

删除

我们考虑先将小于等于 \(x\) 的部分与大于 \(x\) 的部分分离。对于第一部分,我们再将小于 \(x\) 的部分和等于 \(x\) 的部分分离,最后中间删除等于 \(x\) 的部分即可。

split(root, k, x, tmp);
split(x, k - 1, x, y);
// 分离子树 
y = merge(tr[y].l, tr[y].r);
// 合并 x 的子树,也就是去掉 x 
root = merge(merge(x, y), tmp);

查询

查询就是查询排名为几的数。

int kth(int p, int k) { // 查询在 p 及其子树中排名为 k 的数 
	if (k == tr[lc].siz + 1) { // 为当前节点 
		return tr[p].val;
	}
	if (k <= tr[lc].siz) { // 在左子树中 
		return kth(lc, k);
	} else { // 在右子树中 
		return kth(rc, k - tr[lc].siz - 1);
	}
}

代码

P3369 【模板】普通平衡树

// P3369【模板】普通平衡树
#include <bits/stdc++.h>

using namespace std;

mt19937 wdz(time(0));

const int N = 1e5 + 10;
int tot, root;
struct FHQ_Treap {
    int l, r, val, key, siz;
} tr[N];
#define lc tr[p].l
#define rc tr[p].r
void update(int p) { // 更新 p 的子树大小 
	tr[p].siz = tr[lc].siz + tr[rc].siz + 1;
}
void create(int &p, int x) { // 新建一棵只有一个节点的 Treap 
	p = ++tot;
	tr[p].val = x;
	tr[p].key = wdz();
	tr[p].siz = 1;
}
void split(int p, int k, int &x, int &y) {
	// 根节点,关键值,以及分裂后的两个节点 
	if (!p) {
		x = y = 0;
		return;
	}
	if (tr[p].val <= k) { // 权值小于等于 k 
		x = p; // 左子树全部属于第一个子树 
		split(rc, k, rc, y); // 分裂右子树 
	} else {
		y = p;
		split(lc, k, x, lc);
	}
	update(p);
}
int merge(int x, int y) { // 返回合并后的树根 
	if (!x || !y) {
		return x + y;
	}
	if (tr[x].key < tr[y].key) { // x 的优先级小于 y 的优先级 
		tr[x].r = merge(tr[x].r, y);
		// 将子树 y 并入子树 x 的右子树 
		update(x);
		return x;
	} else {
		tr[y].l = merge(x, tr[y].l);
		update(y);
		return y;
	}
}
int kth(int p, int k) { // 查询在 p 及其子树中排名为 k 的数 
	if (k == tr[lc].siz + 1) { // 为当前节点 
		return tr[p].val;
	}
	if (k <= tr[lc].siz) { // 在左子树中 
		return kth(lc, k);
	} else { // 在右子树中 
		return kth(rc, k - tr[lc].siz - 1);
	}
}
int qq;
int now, tmp, x, y;
int main() {
	scanf("%d", &qq);
	while (qq--) {
		int op, k;
		scanf("%d%d", &op, &k);
		if (op == 1) { 
			// 插入 k 
			split(root, k, x, y); 
			create(now, k);
			root = merge(merge(x, now), y);
		} else if (op == 2) { 
			// 删除 k 
			split(root, k, x, tmp);
			split(x, k - 1, x, y);
			// 分离子树 
			y = merge(tr[y].l, tr[y].r);
			// 合并 x 的子树,也就是去掉 x 
			root = merge(merge(x, y), tmp);
		} else if (op == 3) { 
			// 查询 k 数的排名 
			split(root, k - 1, x, y); // 分离子树 
			printf("%d\n", tr[x].siz + 1); // 节点数即为排名 
			root = merge(x, y);
		} else if (op == 4) { 
			// 查询排名为 k 的数 
			printf("%d\n", kth(root, k));
		} else if (op == 5) { 
			// 前驱 
			split(root, k - 1, x, y);
			printf("%d\n", kth(x, tr[x].siz));
			root = merge(x, y); 
		} else { 
			// 后继 
			split(root, k, x, y);
			printf("%d\n", kth(y, 1));
			root = merge(x, y);
		}
	}
	
	return 0;
}

标签:merge,int,siz,tr,Treap,key,平衡
From: https://www.cnblogs.com/zsk123qwq/p/18393505

相关文章

  • Java平衡树--查找树的新建与树的实现
    Java学习+面试指南:https://javaxiaobear.cn1、查找树的定义一棵2-3查找树要么为空,要么满足满足下面两个要求:2-结点含有一个键(及其对应的值)和两条链,左链接指向2-3树中的键都小于该结点,右链接指向的2-3树中的键都大于该结点。3-结点含有两个键(及其对应的值)和三条链,左链接指向的2......
  • Go平衡二叉树
    packagemainimport("fmt")typeAVLNodestruct{dataintheightintleft,right*AVLNode}funcmax(a,bint)int{ifa>b{returna}returnb}funcheight(p*AVLNode)int{ifp!=nil{......
  • 如何开发针对不平衡分类的成本敏感神经网络 python
    如何开发针对不平衡分类的成本敏感神经网络深度学习神经网络是一类灵活的机器学习算法,可以在各种问题上表现良好。神经网络使用误差反向传播算法进行训练,该算法涉及计算模型在训练数据集上产生的误差,并根据这些误差的比例更新模型权重。这种训练方法的局限性在于,每个类别......
  • TPAMI 2024 | 离散且平衡的谱聚类算法:一种可扩展的方法
    DiscreteandBalancedSpectralClusteringWithScalability离散且平衡的谱聚类算法:一种可扩展的方法RongWang,HuiminChen,YihangLu,QianrongZhang,FeipingNie,andXuelongLi摘要谱聚类(SC)因其卓越的聚类性能而成为深入研究的主要课题。尽管取得了成功......
  • 平衡树
    平衡树真的恶心死了!!!!!!好烦啊,又臭又长。有很多种平衡树,替罪羊,treap,fhq,slpay。这里就说splay,和bst和替罪羊了,因为其他我都不会(悲先说二叉排序树(二叉搜索树),他的关系就是左子树所有节点<根节点<右子树所有节点。也就是说,按照中序遍历可以找到有序序列。这个时候我......
  • 【模板】普通平衡树
    具体讲解见OI-wiki(他的左旋右旋跟蓝书的有点不一样,按照蓝书的理解,代码见下),下面是一些补充拓展:1.将一个序列插入到\(x\)的后面:找到\(x\)的后继\(y\),先将\(x\)伸展到根,再将\(y\)伸展到\(x\)的右子树,此时由于\(y\)是\(x\)的后继所以\(y\)的左儿子为空;对序列建出一棵二叉树(可以像线......
  • [学习笔记] Splay & Treap 平衡树 - 数据结构
    [学习笔记]Splay&Treap平衡树-数据结构Splay树又名伸展树,一种平衡二叉查找树,通过\(\text{Splay}\)操作不断把节点旋到根节点来维护整颗树的平衡。说人话,很玄学的玩意,复杂度是单log级别的。为啥是单log,科学的解释请移步OI-WIKI。不科学的解释就是,通过不断\(\tex......
  • YOLOv9改进策略【损失函数篇】| Slide Loss,解决简单样本和困难样本之间的不平衡问题
    一、本文介绍本文记录的是改进YOLOv9的损失函数,将其替换成SlideLoss,并详细说明了优化原因,注意事项等。SlideLoss函数可以有效地解决样本不平衡问题,为困难样本赋予更高的权重,使模型在训练过程中更加关注困难样本。若是在自己的数据集中发现容易样本的数量非常大,而困难样本......
  • 手搓平衡搜索树-红黑树 平衡修正 图文详解 (万字长文)
    目录红黑树简述性质/规则主要规则:推导性质:红黑树的基本实现structRBTreeNodeclassRBTree红黑树的插入红黑树插入修正前言什么时候需要变色:变色的基础:为什么需要旋转与变色变色:旋转需要修正的所有情况先认识最简单的情况1.叔叔是红色结点注意:2.没有叔叔结点3.叔叔是黑色......
  • 数模国赛冲刺 | 数据预处理方法合集(特征工程、数据降维、数据划分、数据平衡)
    ​数据预处理方法合集(特征工程、数据降维、数据划分、数据平衡)本文继续介绍数据预处理中的特征工程、数据降维、数据划分、数据平衡的内容,接下来我们将详细地介绍具体的方法,文末可获得预处理方法合集PDF!目录特征工程特征选择(FeatureSelection)特征提取数据降维线性降......