二叉搜索树

1|0二叉搜索树

1|1总定义

  1. 若左子树不空,则左子树上所有结点的值均小于它的根结点的值。
  2. 若右子树不空,则右子树上所有结点的值均大于它的根结点的值。
  3. 左、右子树也分别为二叉搜索树。
  4. 没有权值相等的结点

1|2结点定义

  • 当前节点的权值 val

    • 即序列中的数
  • 左孩子的下标与右孩子的下标 ls rs

  • 计数器 cnt

    • 代表当前的值出现了几遍
  • 子树的小和该结点大小之和 siz

  • struct node{ int val,ls,rs,cnt,siz; }tree[500010];

1|3操作实现

1|0插入

x 是当前节点的下标,v 是要插入的值。

要在树上插入一个 v 的值,就要找到一个合适 v 的位置。

如果本身树的节点内有代表 v 的值的节点,就把该节点的计数器加 1

否则一直向下寻找,直到找到叶子节点,这个时候就可以从这个叶子节点连出一个儿子,代表 v 的节点。具体向下寻找该走左儿子还是右儿子是根据二叉搜索树的性质来的。

void add(int x,int v) { tree[x].siz++; //如果查到这个节点,说明这个节点的子树里面肯定是有v的,所以siz++ if(tree[x].val==v) { //如果恰好有重复的数,就把cnt++,退出即可,因为我们要满足第四条性质 tree[x].cnt++; return ; } if(tree[x].val>v)//如果v<tree[x].val,说明v实在x的左子树里 { if(tree[x].ls!=0) { add(tree[x].ls,v);//如果x有左子树,就去x的左子树 } else//如果不是,v就是x的左子树的权值 { cont++;//cont是目前BST一共有几个节点 tree[cont].val=v; tree[cont].siz=tree[cont].cnt=1; tree[x].ls=cont; } } else{//右子树同理 if(tree[x].rs!=0) { add(tree[x].rs,v); } else { cont++; tree[cont].val=v; tree[cont].siz=tree[cont].cnt=1; tree[x].rs=cont; } } }

1|0找前驱

x 是当前节点的下标,val 是要找前驱的值,ans 是目前找到的比 val 小的数的最大值。

int queryFr(int x, int val, int ans) { if (tree[x].val>=val) {//如果当前值大于val,就说明查的数大了,所以要往左子树找 if (tree[x].ls==0)//如果没有左子树就直接返回找到的ans return ans; else//如果不是的话,去查左子树 return queryFr(tree[x].ls,val,ans); } else {//如果当前值小于val,就说明我们找比val小的了 if (tree[x].rs==0)//如果没有右孩子,就返回tree[x].val,因为走到这一步时,我们后找到的一定比先找到的大(参考第二条性质) return (tree[x].val<val) ? tree[x].val : ans //如果有右孩子,,我们还要找这个节点的右子树,因为万一右子树有比当前节点还大并且小于要找的val的话,ans需要更新 if (tree[x].cnt!=0)//如果当前节数的个数不为0,ans就可以更新为tree[x].val return queryFr(tree[x].rs,val,tree[x].val); else//反之ans不需要更新 return queryFr(tree[x].rs,val,ans); } }

1|0找后继

int queryNe(int x, int val, int ans) { if (tree[x].val<=val) { if (tree[x].rs==0) return ans; else return queryNe(tree[x].rs,val,ans); } else { if (tree[x].ls==0) return (tree[x].val>val)? tree[x].val : ans; if (tree[x].cnt!=0) return queryNe(tree[x].ls,val,tree[x].val); else return queryNe(tree[x].ls,val,ans); } }

1|0按值找排名

这里我们就要用到 siz 了,排名就是比这个值要小的数的个数再 +1,所以我们按值找排名。

int queryVal(int x,int val) { if(x==0) return 0;//没有排名 if(val==tree[x].val) return tree[tree[x].ls].siz; //如果当前节点值=val,则我们加上现在比val小的数的个数,也就是它左子树的大小 if(val<tree[x].val) return queryVal(tree[x].ls,val); //如果当前节点值比val大了,我们就去它的左子树找val,因为左子树的节点值一定是小的 return queryVal(tree[x].rs,val)+tree[tree[x].ls].siz+tree[x].cnt; //如果当前节点值比val小了,我们就去它的右子树找val,同时加上左子树的大小和这个节点的值出现次数 //因为这个节点的值小于val,这个节点的左子树的各个节点的值一定也小于val } //注:这里最终返回的是排名-1,也就是比val小的数的个数,在输出的时候记得+1

1|0按排名找值

由性质 1. 2. ,我们发现排名为 n 的数在 BST 上是第 n 靠左的数。

或者说排名为 n 的数的结点在 BST 中,它的左子树的 siz 与它的各个祖先的左子树的 siz 相加恰好等于 n (减去重复部分)。

由此问题转化。

int queryRk(int x,int rk) { if(x==0) return INF; if(tree[tree[x].ls].siz>=rk)//如果左子树大小>=rk了,就说明答案在左子树里 return queryRk(tree[x].ls,rk);//查左子树 if(tree[tree[x].ls].siz+tree[x].cnt>=rk)//如果左子树大小加上当前的数的多少恰好>=k,说明我们找到答案了 return tree[x].val;//直接返回权值 return queryRk(tree[x].rs,rk-tree[tree[x].ls].siz-tree[x].cnt); //否则就查右子树,同时减去当前节点的次数与左子树的大小 }

1|0删除

具体就是利用二叉搜索树的性质在树上向下爬找到具体节点,把计数器-1。与上文同理就不粘贴代码了

1|4P5076普通二叉树

1|0上述板子

#include<iostream> #include<cstdio> #define re register using namespace std; const int INF=0x7fffffff; int cont; struct node{ int val,siz,cnt,ls,rs; }tree[1000010]; int n,opt,xx; inline void add(int x,int v) { tree[x].siz++; if(tree[x].val==v) { tree[x].cnt++; return ; } if(tree[x].val>v) { if(tree[x].ls!=0) add(tree[x].ls,v); else { cont++; tree[cont].val = v; tree[cont].siz = tree[cont].cnt = 1; tree[x].ls = cont; } } else{ if(tree[x].rs!=0) add(tree[x].rs,v); else { cont++; tree[cont].val = v; tree[cont].siz = tree[cont].cnt=1; tree[x].rs = cont; } } } int queryfr(int x, int val, int ans) { if (tree[x].val>=val) { if (tree[x].ls==0) return ans; else return queryfr(tree[x].ls,val,ans); } else { if (tree[x].rs==0) return tree[x].val; return queryfr(tree[x].rs,val,tree[x].val); } } int queryne(int x, int val, int ans) { if (tree[x].val<=val) { if (tree[x].rs==0) return ans; else return queryne(tree[x].rs,val,ans); } else { if (tree[x].ls==0) return tree[x].val; return queryne(tree[x].ls,val,tree[x].val); } } int queryrk(int x,int rk) { if(x==0) return INF; if(tree[tree[x].ls].siz>=rk) return queryrk(tree[x].ls,rk); if(tree[tree[x].ls].siz+tree[x].cnt>=rk) return tree[x].val; return queryrk(tree[x].rs,rk-tree[tree[x].ls].siz-tree[x].cnt); } int queryval(int x,int val) { if(x==0) return 0; if(val==tree[x].val) return tree[tree[x].ls].siz; if(val<tree[x].val) return queryval(tree[x].ls,val); return queryval(tree[x].rs,val)+tree[tree[x].ls].siz+tree[x].cnt; } inline int read() { re int r=0; re char ch=getchar(); while(ch<'0'||ch>'9') ch=getchar(); while(ch>='0'&&ch<='9'){ r=(r<<3)+(r<<1)+(ch^48); ch=getchar(); } return r; } signed main() { n=read(); while(n--) { opt=read();xx=read(); if(opt==1) printf("%d\n",queryval(1,xx)+1); else if(opt==2) printf("%d\n",queryrk(1,xx)); else if(opt==3) { if (cont == 0) { cout << -2147483647 << endl; } else printf("%d\n",queryfr(1,xx,-INF)); } else if(opt==4) printf("%d\n",queryne(1,xx,INF)); else{ if(cont==0){ cont++; tree[cont].cnt=tree[cont].siz=1; tree[cont].val=xx; } else add(1,xx); } } return 0; }

1|0深基板子

#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> using namespace std; #define MAXN 100010 int n, root, cnt, opt, x; struct Node { int left, right, size, value, num; Node(int l, int r, int s, int v) : left(l), right(r), size(s), value(v), num(1) {} Node() {} } t[MAXN]; void update(int root) { t[root].size = t[t[root].left].size + t[t[root].right].size + t[root].num; } int queryRank(int x, int root) { if (root) { if (x < t[root].value) { return queryRank(x, t[root].left); } if (x > t[root].value) { return queryRank(x, t[root].right) + t[t[root].left].size + t[root].num; } if (x == t[root].value) { return t[t[root].left].size + t[root].num; } } return 1; } int queryVal(int x, int root) { if (x == 0) { return -0x7fffffff; } if (x <= t[t[root].left].size) { return queryVal(x, t[root].left); } if (x <= t[t[root].left].size + t[root].num) { return t[root].value; } return queryVal(x - t[t[root].left].size - t[root].num, t[root].right); } void insert(int x, int &root) { if (x < t[root].value) { if (!t[root].left) { t[root].left = ++cnt; t[cnt] = Node(0, 0, 1, x); } else { insert(x, t[root].left); } } else if (x > t[root].value) { if (!t[root].right) { t[root].right = ++cnt; t[cnt] = Node(0, 0, 1, x); } else { insert(x, t[root].right); } } else { t[root].num++; } update(root); } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr); cin >> n; int num = 0; t[root = ++cnt] = Node(0, 0, 1, 2147483647); while (n--) { cin >> opt >> x; num++; switch (opt) { case 1: cout << queryRank(x, root) << endl; break; case 2: cout << queryVal(x, root) << endl; break; case 3: cout << queryVal(queryRank(x, root) - 1, root) << endl; break; case 4: cout << queryVal(queryRank(x + 1, root), root) << endl; break; case 5: num--; insert(x, root); break; } } return 0; }

__EOF__

本文作者Kdlyh
本文链接https://www.cnblogs.com/kdlyh/p/17853818.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   加固文明幻景  阅读(12)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 一文读懂知识蒸馏
· 终于写完轮子一部分:tcp代理 了,记录一下
点击右上角即可分享
微信分享提示