P3369 [Modèle] Arbre équilibré ordinaire (construction et analyse d'arbre FHQ Treap)

description du sujet

Vous devez écrire une structure de données (voir le titre du sujet) pour conserver certains nombres, qui doivent fournir les opérations suivantes :

  1. insérer x numéro
  2. Supprimer x numéros (s'il y a plusieurs numéros identiques, un seul doit être supprimé)
  3. Interroger le classement du nombre x (le classement est défini comme le nombre de nombres inférieur au nombre actuel +1 )
  4. Interroger le numéro de rang x
  5. Trouver le prédécesseur de x (le prédécesseur est défini comme le plus grand nombre inférieur à x)
  6. Trouver le successeur de x (le successeur est défini comme le plus petit nombre supérieur à x)

format d'entrée

La première ligne est n, indiquant le nombre d'opérations, et chacune des n lignes suivantes a deux nombres opt et x, et opt ​​indique le numéro de séquence de l'opération ( 1≤opt≤6 )

format de sortie

Pour les opérations 3, 4, 5, 6, chaque ligne génère un nombre, indiquant la réponse correspondante

Exemples d'entrée et de sortie

Tapez #1 pour copier

10 
1 106465 
4 1 
1 317721 
1 460929 
1 
644985 1 
84185 1 89851 
6 81968 
1 492737 
5 493598

copie de la sortie n°1

106465 
84185 
492737

Instructions/Conseils

【Plage de données】
Pour 100 % de données, 1≤n≤105, ∣x∣≤107

construire l'arbre :

Construisez l'arborescence FHQTreap :

La forme finale de l'arborescence FHQ Treap est déterminée par la valeur clé et la priorité. Son génie est que toutes les opérations n'utilisent que les deux opérations de base de fractionnement et de fusion, et la complexité de ces deux opérations est O(log2 n).

Connaissances préalables :

  • C++
  • 二叉搜索树Les propriétés fondamentales de
  • 二叉堆

Dans un arbre de recherche binaire, le nœud gauche est plus petit que le nœud racine et le nœud racine est plus petit que le nœud droit.

En utilisant le parcours dans l’ordre, vous pouvez voir qu’il s’agit d’une séquence croissante de manière monotone.

Tas binaire :

Il existe de gros tas de racines et de petits tas de racines.

Le grand tas racine est le nœud racine > le nœud gauche et le nœud racine > le nœud droit, le petit tas racine est l'opposé ;

La couche supérieure du grand tas de racines et du petit tas de racines est généralement un arbre binaire complet. À ce stade, l’efficacité de la recherche sera améliorée et la complexité temporelle sera réduite. O(log2n)

Avec les points de connaissances ci-dessus :

Analysez le sujet :

1. Un arbre binaire doit avoir des nœuds gauche et droit.

2. Il doit y avoir une valeur clé, une variable aléatoire.

3. La taille des nœuds gauche et droit + lui-même (en ce moment, c'est très utile dans le classement)

le code s'affiche comme ci-dessous :

struct Node {
	int ls, rs;   // 左右 子节点 
	int key, pri; //  key为值 pri 为随机的优先级
	int size; // 当前节点为根的子树的节点数量,用于求第k大和排名
}t[M]; 

cœur:

1. Divisez et renvoyez deux arbres enracinés en L et R

Regardez d'abord l'image : divisé en deux arbres, le sous-arbre de gauche est inférieur à x et le sous-arbre de droite est supérieur à x.

 Code ci-dessus :

void Split(int u, int x, int& L, int& R) {
	if (u == 0) { //到达叶子,递归返回
		L = 0, R = 0;
		return;
	}

	if (t[u].key <= x) { // 本节点比x小,那么到右子树找x 
		L = u;           // 左树的根是本节点  // 下一个如果到 这来 上一个的rs 为 这个节点u ,因为u.rs的全部字节点都大于 u的值
		Split(t[u].rs, x, t[u].rs, R); // 通过rs传回新的子节点  
	}
	else {
		R = u; // 根节点 
		Split(t[u].ls, x, L, t[u].ls);
	}
	Update(u);
}

Renvoie les racines L et R des deux arbres.

2. Fusionner deux sous-arbres

C'est l'inverse du fractionnement : le faire avec des valeurs aléatoires garantit que la hauteur de l'arbre reste petite, c'est pourquoi des valeurs aléatoires sont introduites.

Prenons d'abord le petit tas de racines comme exemple et fusionnons deux arbres comme indiqué sur la figure.

 Ici, j'utilise le gros tas racine comme exemple :

int Merge(int L, int R) {
	if (L == 0 || R == 0) {
		return L + R; // 左 or  右节点 
	} // 建立大顶堆
	if (t[L].pri > t[R].pri) { // 合并树  随机值 到的在右边
		t[L].rs = Merge(t[L].rs, R); // 
		Update(L);
		return L;
	}
	else {
		t[R].ls = Merge(L, t[R].ls);
		Update(R);
		return R;
	}
}

Celui qui a le poids le plus élevé est en haut.

Il est beaucoup plus facile d’écrire avec deux pensées.

Voir les commentaires pour l'explication du code

Code de résolution de problèmes :

#include<iostream>
#include<cmath>
#include<stdlib.h>
using namespace std;
const int M = 1e6 + 10;
int cnt = 0, root = 0;
struct Node {
	int ls, rs;   // 左右 子节点 
	int key, pri; //  key为值 pri 为随机的优先级
	int size; // 当前节点为根的子树的节点数量,用于求第k大和排名
}t[M]; 
void newNode(int x) {
	cnt++;
	t[cnt].size = 1;
	t[cnt].ls = t[cnt].rs = 0;
	t[cnt].key = x;
	t[cnt].pri = rand();
}
void Update(int u) {
	t[u].size = t[t[u].ls].size + t[t[u].rs].size+1;
}
void Split(int u, int x, int& L, int& R) {
	if (u == 0) { //到达叶子,递归返回
		L = 0, R = 0;
		return;
	}

	if (t[u].key <= x) { // 本节点比x小,那么到右子树找x 
		L = u;           // 左树的根是本节点  // 下一个如果到 这来 上一个的rs 为 这个节点u ,因为u.rs的全部字节点都大于 u的值
		Split(t[u].rs, x, t[u].rs, R); // 通过rs传回新的子节点  
	}
	else {
		R = u; // 根节点 
		Split(t[u].ls, x, L, t[u].ls);
	}
	Update(u);
}

int Merge(int L, int R) {
	if (L == 0 || R == 0) {
		return L + R; // 左 or  右节点 
	} // 建立大顶堆
	if (t[L].pri > t[R].pri) { // 合并树  随机值 到的在右边
		t[L].rs = Merge(t[L].rs, R); // 
		Update(L);
		return L;
	}
	else {
		t[R].ls = Merge(L, t[R].ls);
		Update(R);
		return R;
	}
}

void Insert(int x) {
	int L, R; // 左右根的节点
	Split(root, x, L, R); 
	newNode(x); //生成x 
	int aa = Merge(L, cnt); //合并节点,这里是生成的节点,合并左子树中
	root = Merge(aa, R); //两棵树进行合并
}
void Del(int x) {//删除节点
	int L, R, p;
	Split(root, x, L, R); //先抛出 左根的节点 小于等于x   右根  大于 x
 	Split(L, x - 1, L, p); //在进行抛 右节点一定为 x 以p为根 ,左节点一定小于 x的,
	p = Merge(t[p].ls, t[p].rs); //我们只需将连接左右儿子,根节点就会被抛弃 
	root = Merge(Merge(L, p), R); // 合并左右子树
}

void Rank(int x) {//计算x的排名
	int L, R;
	Split(root, x - 1, L, R);
	printf("%d\n", t[L].size + 1); //左节点 + 1
	root = Merge(L, R);
}

int kth(int u, int k) { //计算排名为k的点 这个要和上面弄清楚
	if (k == t[t[u].ls].size + 1) {
		return u;
	}
	if (k <= t[t[u].ls].size) {
		return kth(t[u].ls, k);
	}
	if (k > t[t[u].ls].size) {  // 这里是 ls 不是 r 遍历右子树
		return kth(t[u].rs, k - t[t[u].ls].size - 1);
	}
}
void Precursor(int x) {
	int L, R;
	Split(root, x - 1, L, R);
	printf("%d\n", t[kth(L, t[L].size)].key);//这个size是节点的个数 刚好就是排名最后的点的位置
	root = Merge(L, R);
}
void Successor(int x) { //右子树的第一个点
	int L, R;
	Split(root, x, L, R);
	printf("%d\n", t[kth(R, 1)].key); // 和上面同理
	root = Merge(L, R);
}

int main() {
	srand(time(NULL));
	int n;
	cin >> n;
	while (n--) {
		int opt, x;
		cin >> opt >> x;
		switch (opt) {
		case 1:Insert(x); break;
		case 2:Del(x); break;
		case 3:Rank(x); break;
		case 4:printf("%d\n", t[kth(root, x)].key); break;
		case 5:Precursor(x); break;
		case 6:Successor(x); break;
		}
	}
	return 0;
}

Je suppose que tu aimes

Origine blog.csdn.net/zhi6fui/article/details/130108810
conseillé
Classement