社区讨论
Re:**线段树**的数组开**这么大**才是最合适的
学术版参与者 20已保存回复 24
讨论操作
快速查看讨论及其快照的属性,并进行相关操作。
- 当前回复
- 24 条
- 当前快照
- 1 份
- 快照标识符
- @mi6v70vq
- 此快照首次捕获于
- 2025/11/20 11:21 4 个月前
- 此快照最后确认于
- 2025/11/20 15:58 4 个月前
这是对zhu1679514458的问题:线段树数组空间开多大比较合适?的一个解答。
这里的线段树指的是,每个节点都有或个孩子的二叉树。
编号为的节点的左孩子(如果存在的话)的编号为,右孩子的编号为。而根节点的编号一般为。
一棵拥有个叶子节点的线段树被称为-线段树。
除此之外,一棵-线段树还需要满足其左子树为一棵-线段树,其右子树为一棵-线段树。
有上述条件可以唯一确定一棵-线段树的形态。
接下来要探讨的是:一棵-线段树的节点中的最大的编号,与有什么关系。因为最大的编号和在程序中实现时所用的数组大小以及使用的内存大小有密切的联系。
有人认为小于,有人认为不会超过。有人提出可以证明。
真相到底是什么?
① 线段树的高度和形态
-线段树高度为。
-线段树高度为。
-线段树高度均为。
-线段树高度均为。
-线段树高度均为。
可以看出,一棵-线段树的高度为或等于。
根据此,结合二叉树的简单性质,我们可以得到一个线段树的比较松的上估计:。证实了小于的断言。
而观察线段树的形态可以得出另一个结论:一棵线段树除了最后一层,其他层都是满的。
据此,有人提出了另一个声称更紧的估计:。并据此计算得出的结论。然而这是错误的。因为这个估计依赖于一个错误的假设:线段树最后一层的节点是靠左连续的,或称“完全二叉树”的形态。
实际上,在时,-线段树的就等于。这是不满足估计的最小的。
那么什么界才是精确的呢?线段树的数组到底要开多大呢?
② 线段树的结构
只要知道了-线段树的值,就能确定线段树的形态。只要知道了线段树的根节点编号,就能确定线段树的是多少。
那么任何快速地计算呢?考虑以下算法:
若线段树为-线段树,那么其为根节点编号。
若线段树为-线段树,且根节点编号为,那么其为根节点编号为的-线段树的。
若线段树为-线段树,根节点编号为,且-线段树的深度等于-线段树的深度,那么其为根节点编号为的-线段树的。
若线段树为-线段树,根节点编号为,且-线段树的深度不等于-线段树的深度,那么其为根节点编号为的-线段树的。
不难证明上述算法可以在的时间复杂度内计算出正确的结果。
这是一个可接受的算法,接下来我用 c++ 语言实现了计算的函数,并用它计算了与的关系:
CPP#include<cstdio>
int Dep[100000001];
int max(int i,int j){return i>j?i:j;}
void Init(){
Dep[1]=1;
for(int i=2;i<=100000000;++i)
Dep[i]=max(Dep[i>>1],Dep[i+1>>1])+1;
}
int GetNum(int RootNum,int Size){
if(Size==1) return RootNum;
if(!(Size&1)) return GetNum(RootNum<<1|1,Size>>1);
if(Dep[Size>>1]!=Dep[Size+1>>1])
return GetNum(RootNum<<1,Size+1>>1);
return GetNum(RootNum<<1|1,Size>>1 );
}
int main(){
Init();
double Max=0;
for(int i=1;i<=1000000;++i){
int id=GetNum(1,i);
double R=1.*id/i;
if(R>Max) Max=R;
// printf(" # %d : %d , %.15lf\n",i,id,R);
}
printf("%.15lf\n",Max);
return 0;
}
输出的结果是
3.992197027439024,这意味着在内,最大能达到倍的那么大。这的确不超出我们的预期。所以结论是什么?线段树的数组到底要开多大?
结论就是:线段树开倍总没错。你也可以算出一个-线段树的最大能到多少。更简单且内存占用不大的方法是算出第一个比大的的次幂,并把数组开到那么大。
其实有一种方法可以在近乎的时间内算出-线段树的,只涉及位运算,你能想到吗?
回复
共 24 条回复,欢迎继续交流。
正在加载回复...