平衡树学习笔记(一)二叉搜索树
✧<1>定义:
(1)空树为一棵二叉搜索树
(2)二叉搜索树左子树中所有点的权值均小于其根节点
(3)二叉搜索树右子树中所有点的权值均大于其根节点
(4)二叉搜索树的左右子树均为二叉搜索树
✧<2>变量的前置定义:
在接下来的代码块中,我们约定n为结点个数h为高度,val[x]为结点处存的数值,cnt[x]为结点x存的值所出现的次数,lc[x]和rc[x]分别为结点x的左子结点和右子结点。
sum为二叉搜索树中节点的个数,siz[x]为x所在的子树的大小(包括x自身,siz[x]的大小实则为x所在子树中维护的数的个数,而非节点数)。
另外,根据二叉搜索树的二叉性质,h=logn。
✧<3>基本操作:
✡✡✡(1)二叉搜索树的遍历:
前置知识:
先序遍历:依次遍历根、左、右
中序遍历:依次遍历左、根、右
后序遍历:依次遍历左、右、根
排列组合就对了
二叉搜索树一般使用中序遍历:即左、根、右。
由二叉搜索树的递归定义可知,二叉搜索树中序遍历生成的点权序列是单调不降的
由于每个点都只会被遍历到一次,因此时间复杂度为O(n)。
Code
void print(int o)
{
if(!o) return;//如果子树为空树,则返回
print(lc[o]);//遍历左子树
for(int i=1;i<=cnt[o];++i) printf("%d\n",val[o]);
//输出根节点信息
print(rc[o]);//遍历右子树
}
✡✡✡(2)查询最大值&最小值:
根据二叉搜索树的性质,最小值为二叉搜索树左链的顶点,最大值为二叉搜索树右链的顶点。
因此查找最小值的方式为不断查询左儿子,查询最大值的方式为不断查询右儿子。
时间复杂度为O(logn)=O(h)
返回值为最大值和最小值所在的顶点。
Code
int Findmin(int o)
{
if(!lc[o]) return o;
return Findmin(lc[o]);
}
int Findmax(int o)
{
if(!rc[o]) return o;
return Findmax(rc[o]);
}
✡✡✡(3)向二叉搜索树中插入一个值v:
分类讨论:
(1)若o所在的树是空树,那么直接新建一个节点
(2)若o所在的树不为空树,那么分三种情况讨论:
✧✧✧✧✧{1}v的值等于val[o],则cnt[o]++;
✧✧✧✧✧{2}v的值大于val[o],则将v插入o的右子树中
✧✧✧✧✧{3}v的值小于val[o],则将v插入o的左子树中
时间复杂度为O(h)=O(logn)
Code
void insert(int o,int v)
{
if(!o)
{
o=++sum;
siz[o]=cnt[o]=1;
val[o]=v;
lc[o]=rc[o]=0;
}
siz[o]++;
if(v==val[o])
{
cnt[o]++;
return;
}
if(v<val[o]) insert(lc[o],v);
if(v>val[o]) insert(rc[o],v);
return;
}
✡✡✡(4)在二叉搜索树中删除一个值:
还是要分类讨论,这一操作的重点在于要维护二叉搜索树的性质。
先找到权值为v的节点o。
(1)若该节点cnt>1,直接cnt--即可
(2)若该节点cnt=1,则直接删除该节点
✧✧✧✧✧{1}若该节点为叶子节点,则直接删除该节点即可
✧✧✧✧✧{2}若该节点为链节点,即只有一个儿子的节点,则直接用儿子代替该节点
✧✧✧✧✧{3}若该节点的有两个非空子节点,则用左子树中的最大值或者右子树中的最小值代替它
时间复杂度为O(h)=O(logn)
Code
int deletemin(int &o)
{
if(!lc[o])
{
int u=o;
o=rc[o];
return u;
}
else
{
int u=deletemin(o);
siz[o]-=cnt[u];
return u;
}
}
void del(int &o,int v)
{
siz[o]--;
if(v==val[o])
{
if(cnt[o]>1)
{
cnt[o]--;
return;
}
if(lc[o] && rc[o]) o=deletemin(rc[o]);
else o=lc[o]+rc[o];
return;
}
if(v<val[o]) del(lc[o],v);
if(v>val[o]) del(rc[o],v);
}
✡✡✡(5)在二叉搜索树中查询一个元素的排名:
注意:这里指的排名是从小到大的排名
只要稍加考虑就可以知道解法,还是分类讨论。
(1)若v==val[o],则v在o所在的树中的排名为siz[lc[o]]+1
(2)若v>val[o],则排名为cnt[o]+siz[lc[o]]+v在o的右子树中的排名
(3)若v<val[o],则排名为v在o的左子树中的排名
时间复杂度O(h)=O(logn)
Code
int queryrnk(int o,int v)
{
if(v==val[o]) return siz[lc[o]]+1;
if(v<val[o]) return queryrnk(lc[o],v);
if(v>val[o]) return queryrnk(rc[o],v)+siz[lc[o]]+cnt[o];
}
✡✡✡(5)在二叉搜索树中查询排名为k的元素:
在一棵二叉搜索树的子树中,根节点的排名由其左子树的大小决定。
依旧是分类讨论。
(1)若左子树大小siz[lc[o]]>=k,则排名为k的元素在左子树中
(2)若左子树大小siz[lc[o]]<=k-1且siz[lc[o]]>=k-cnt[o],则该元素为根节点o
(3)若子树大小siz[lc[o]]<k-cnt[o],则该元素在右子树中
Code
int querykth(int o,int k)
{
if(siz[lc[o]]>=k) return querykth(lc[o],k);
if(siz[lc[o]]<=k-1 && siz[lc[o]]>=k-cnt[o]) return val[o];
if(siz[lc[o]]<k-cnt[o]) return querykth(rc[o],k-cnt[o]-siz[lc[o]]);
}