首页 > 其他分享 >FHQ

FHQ

时间:2023-09-11 19:24:29浏览次数:35  
标签:删除 val int FHQ tr root 节点

FHQ

1.BST 二叉搜索树(看着比较好)

二叉搜索树(Binary Search Tree)也叫二叉查找树,他是具有下列性质的一种二叉树。

若左子树不空,则左子树上所有节点的值都小于根节点的值;

若右子树不空,则右子树上所有节点的值都大于根节点的值;

任意节点的子树也都是二叉搜索树;

二叉搜索树有一个重要特性就是他的中序遍历结果一定是有序的。如上图,二叉搜索树的中序遍历结果是 [1,3,4,6,8,9] 。

二叉搜索树的查找:

二叉搜索树的查找可以通过递归的方式,也可以通过 while 循环的方式,原理都一样,如下图所示,步骤如下:

如果当前节点为空,则搜索失败。

否则,如果当前节点的值等于要查找的值,则直接返回。

否则,如果要查找的值比当前节点小,就往当前节点的左子树找。如果要查找的值比当前节点值大,就往当前节点的右子树找。

private TreeNode searchBST(TreeNode root, int val) {
    if (root == null) { // 查找不成功。
        return null;
    } else if (val == root.val) { // 查找成功。
        return root;
    } else if (val < root.val) // 左子树找。
        return searchBST(root.left, val);
    else // 右子树找
        return searchBST(root.right, val);
}

二叉搜索树的插入:

二叉搜索树插入的时候首先要找到他需要插入的位置,然后在插入,如下图所示,步骤如下:

如果当前节点为空,直接创建一个新的节点返回。

如果要插入的值比当前节点小,就往当前节点的左子树查找插入的位置。

如果要插入的值比当前节点大,就往当前节点的右子树查找插入的位置。

代码如下:

 private TreeNode insertBST(TreeNode root, int val) {
     if (root == null)// 如果为空,创建一个新的节点。
         return new TreeNode(val);
     else if (val < root.val)// 插入左子树。
         root.left = insertBST(root.left, val);
     else // 插入右子树。
         root.right = insertBST(root.right, val);
     return root;
 }

二叉搜索树的删除:

二叉树的删除相对来说要麻烦一些,如果删除的是叶子节点,可以直接删除。如果删除的节点只有一个子节点,让这个仅有的子节点替代他。如果删除的节点有两个子节点,就需要考虑删除当前节点后子节点该怎么存放。删除有两种实现方式,一种是直接删除需要删除的节点,一种是使用移形换位法,直接删除有个缺点就是会导致树严重不平衡,在后面我们讲 AVL 树和红黑树的时候使用的都是移形换位法。这里我们先来讲下直接删除是怎么操作的。

在讲解二叉搜索树的删除之前我们先来了解一下二叉树的前驱节点和后继节点。

前驱节点:对一棵二叉树进行中序遍历,遍历后的结果中,当前节点的前一个节点为该节点的前驱节点;

后继节点:对一棵二叉树进行中序遍历,遍历后的结果中,当前节点的后一个节点为该节点的后继节点;

Alt text

如上图所示,二叉树中节点 4 的前驱节点是 3 ,后继节点是 5 。假设要删除节点 4 ,有两种方式。一种是让待删除节点的右子节点成为他前驱节点的右子节点,这样待删除的节点就只有一个子节点了,如下图所示。

还一种是让待删除节点的左子节点成为他后继节点的左子节点,如图下图所示。

这里的关键是怎么查找一个节点的前驱节点和后继节点,有的同学认为通过打印二叉树的中序遍历结果即可找到,确实可以找到,实际上不需要那么麻烦。一个节点的前驱节点是他左子树中的最大节点,这个节点就是他左子树往右一直走下去的节点。同理一个节点的后继节点就是他右子树的最小节点,这个节点就是他右子树往左一直走下去的节点。

 // 查找节点treeNode的前驱节点。
 static TreeNode preNode(TreeNode treeNode) {
     TreeNode leftBig = treeNode.left;
     while (leftBig.right != null)
         leftBig = leftBig.right;
     return leftBig;
 }
 
 // 查找节点treeNode的后继节点。
 static TreeNode postNode(TreeNode treeNode) {
     TreeNode rightSmall = treeNode.right;
     while (rightSmall.left != null)
         rightSmall = rightSmall.left;
     return rightSmall;
 }

找到前驱节点或者后继节点,就可以删除了,删除的步骤如下,假设删除的节点是 p :

如果 p 是叶子节点,则直接删除。

如果 p 不是叶子节点,并且 p 仅有一个子节点,只需要让 p 的父节点变成 p 子节点的父节点即可,也可以理解为让 p 的子节点替换 p 的位置。

如果 p 有两个子节点,就像上面讲的,有两种方式。一种是让 p 的右子节点成为他前驱节点的右子节点。还一种是让 p 的左子节点成为他后继节点的左子节点,两种方式可以随便选一个。

public TreeNode deleteNode(TreeNode root, int val) {
    if (root == null)
        return null;
    if (root.val == val) {
        // 这里把 root 是叶子节点和 root 只有一个子节点合并了。
        if (root.left == null)
            return root.right;
        if (root.right == null)
            return root.left;

        // 这里 root 有两个子节点,找到 root 的前驱节点。
        TreeNode leftBig = root.left;
        while (leftBig.right != null)
            leftBig = leftBig.right;
        // 让root的右子节点成为他前驱节点的右子节点。
        leftBig.right = root.right;
        // 直接返回要删除节点的左子树。
        return root.left;
    } else if (val < root.val) {
        // 要删除的节点在左子树上。
        root.left = deleteNode(root.left, val);
        return root;
    } else {
        // 要删除的节点在右子树上。
        root.right = deleteNode(root.right, val);
        return root;
    }
}

删除节点一般有两种实现方式,一种就是上面讲的,但这种有个缺点,就是会严重破坏树的平衡性。还一种就是移形换位,不是把待删除的节点给删除,而是用他的前驱或者后继节点的值来替换他,然后把这个前驱或后继节点给删除,后面我们讲 AVL 树和红黑树的时候会用这种方式。


2.FHQ

1.定义

struct FHQ
{
   int l,r,v,rd,siz;
}tr[N];
int id,root;//总数 根节点 
inline void update(const int &x){tr[x].siz=tr[tr[x].l].siz+tr[tr[x].r].siz;}//更新
inline int adpot(const int v)//加点 
{
   tr[++id].v=v;
   tr[id].l=tr[id].r=0;
   tr[id].siz=1;
   tr[id].rd=rand();
   return id;
}

2.关键

inline void split(const int &u,const int &v,int &x,int &y)//x y 分开的两棵
{
	if(!u){x=y=0;return ;}
	if(tr[u].v<=v){x=u;split(tr[u].r,v,tr[u].r,y);}
	else {y=u;split(tr[u].l,v,x,tr[u].l);}
	update(u);
}
inline int merge(const int &x,const int &y)//return 合并后的根 
{
	if(!x||!y)return x|y;
	if(tr[x].rd<tr[y].rd)
	{
		tr[x].r=merge(tr[x].r,y);
		update(x);return x;
	}
	else
	{
		tr[y].l=merge(x,tr[y].l);
		update(y);return y;
	}
}

3.可进行某些的操作

inline void insert(const int &v)//加点 
{
	int r1,r2;
	split(root,v,r1,r2);
	root=merge(r1,merge(adpot(v),r2));
}
inline void del(const int &v)//删点
{
	int r1,r2,r3;
	split(root,v,r1,r3);split(r1,v-1,r1,r2);
	r2=merge(tr[r2].l,tr[r2].r);
	root=merge(r1,merge(r2,r3));
}
inline int _rank(const int &v)//排名 
{
	int r1,r2,re;
	split(root,v-1,r1,r2);
	re=tr[r1].siz;
	root=merge(r1,r2);
	return re;
}
inline int numb(int v,int x)//查询排名为x的数 
{
	while(tr[tr[x].l].siz+1!=v)
	{
		if(tr[tr[x].l].siz+1<v)v-=tr[tr[x].l].siz+1,x=tr[x].r;
		else x=tr[x].l;
	}
	return tr[x].v;
}
inline int pre(const int &v)//前驱 
{
	int r1,r2,re;
	split(root,v-1,r1,r2);
	re=numb(tr[r1].siz,r1);
	root=merge(r1,r2);
	return re;
}
inline int suc(const int &v)//后继 
{
	int r1,r2,re;
	split(root,v,r1,r2);
	re=numb(1,r2);
	root=merge(r1,r2);
	return re;
}

标签:删除,val,int,FHQ,tr,root,节点
From: https://www.cnblogs.com/m0m0m/p/17694237.html

相关文章

  • 弱势无图解FHQ-Treap 及各种乱七八糟的错误方法
    众所周知,FHQ-Treap是一个码量少,可区间翻转,能持久化的treap(只是常熟略大)像我这种蒟蒻,想调处有旋Treap或者Splay肯定要个114514年以下可能以FHQ代表FHQ-Treap(逃前置芝士treap的节点拥有两个权值,一个是值,一个是优先级。优先级满足堆的性质,值满足二叉搜索树的性质。当两个权值相......
  • FHQ-Treap 详解
    目录1)FHQ-Treap基本功能理论与实现1.1)FHQ-Treap模型1.2)操作一:分裂(Split)1.3)操作二:合并(Merge)1.4)操作三:插入新节点1.5)删除某个节点1.6)查询某个值的排名1.7)查询排名为\(k\)的值1.8)查询\(x\)的前驱与后继1.9)Endofthisunit2)FHQ-Treap的应用2.1)洛谷P3369END1)FHQ-Treap基本功......
  • 『学习笔记』fhq-treap
    啥是平衡树这边建议去这里。分裂一般指的是按值分裂,意思就是以树上的BST(二叉搜索树)的值为关键值分裂。一般会给一个关键值\(val\),把一棵平衡树分裂成BST值\(\leqval\)和\(>val\)的两部分。主要思想从根开始递归,递归到某一节点,如果当前根节点的BST值小于等于你给的那......
  • FHQ-Treap
    本文仅发布于此博客和作者的洛谷博客,不允许任何人以任何形式转载,无论是否标明出处及作者。前置废话fhq为什么是神。首先我们有一个正常Treap。正常Treap对于每一个值引入了一个随机的键,在节点的值形成一个BST的基础上,保证键形成一个小根堆。也就是把这一个BST做成了笛卡尔树......
  • 「学习笔记」FHQ-treap
    FHQ-treap,即无旋treap,又称分裂合并treap,支持维护序列,可持久化等特性。FHQ-treap有两个核心操作,分裂与合并。通过这两个操作,在很多情况下可以比旋转treap等方便的实现一些操作。FHQ-treap与其他的平衡树相比,他最明显的优点是:它好写!!!,想象一下,在考场上,你用较短的时间写出FH......
  • FHQ-Treap
    简介FHQ-Treap是一种无旋转的Treap。和大多数的平衡树不一样,它并不是用旋转来维护的,而是使用了split(分裂)和merge(合并)两种操作来维护Treap的性质。实现splitsplit操作可以将一个FHQ-Treap按照某个值分裂为两个FHQ-Treap:按照权值分:将权值\(\leval\)的放到一个......
  • FHQ-Treap的详细图解
    第一部分按值分裂的FHQ-Treap按值分裂的FHQ-Treap的典型例题是P3369【模板】普通平衡树。思路FHQ-Treap是什么?FHQ-Treap是二叉搜索树的一种。比如:FHQ-Treap的思想是什么?分裂->操作->合并下面我们就来慢慢讲这些操作。分裂我们可以根据给定的\(k\)将平衡树分......
  • FHQ-Treap
    模板传送FHQ-Treap顾名思义就是范浩强大佬设计的一种二叉平衡树下面我们来讲一下它的原理和代码结构体对于一个节点,我们需要记录的是对应的值子树节点数左右孩子编号对应的随机值structstr{ intval,size,l,r,key;}fhq[100005];看到这里有人疑惑了,这个对应的随机......
  • FHQ_Treap 板子
    namespaceFHQ{#definesiz(x)({Node*_a_=x;_a_==np?0:_a_->sz;})structNode{ Node*ls,*rs; charval;intpri; intsz; voidupdsz(){ sz=1+siz(ls)+siz(rs); }}cs[N];voidoutput(Node*root){ if(root==np)return; output(root-......
  • 「学习笔记」重修 FHQ-treap
    无旋treap的操作方式使得它天生支持维护序列、可持久化等特性。无旋treap又称分裂合并treap。它仅有两种核心操作,即为分裂与合并。通过这两种操作,在很多情况下可以比旋转treap更方便的实现别的操作。变量与宏定义#definelsch[u][0]#definersch[u][1]intcnt,r......