此博客参考了线段树从零开始。考虑读者已阅读参考博客。
存储sum用数组的长度
参考博客提出,用数组来存储各个节点(每个节点存储的是,该节点代表区间内数的总和)的sum,就不需要弄成,建立树节点的class,还为这个class提供左右孩子的方法之类的,直接用数组来代替原二叉树结构。
但参考博客提出,sum数组的长度为存数数组的长度的4倍,其实不对,只要得到树的层数h,再得到h层的完全二叉树的最大节点数,再加一个1,就是sum数组的长度。
#利用递归,深度遍历到第一个终点,这个叶子节点必定在二叉树的最后一层
#(通过画二叉树可以发现上述成立)
#便得到了二叉树的层数
#因为是二叉树,虽然不是完全二叉树,但用完全二叉树来存储就可以使用
#性质:节点索引从1开始,i节点的左孩子为2i,右孩子为2i+1
#所以,只要获得该二叉树的层数,再以完全二叉树的最大节点数作为数组的长度
def get_ceng(l,r):
if(l==r):
return 1;
m=(l+r)//2
return get_ceng(l,m)+1
def get_length(l,r):
ceng = get_ceng(l,r)
print(ceng)
length = 2**ceng-1#n层的完全二叉树,节点数最大为2的n次方-1
length = length+1#因为索引是从1开始用的,0索引没有用,但必须得浪费,所以加1
return length
print(get_length(0,12))
运行结果:。5代表二叉树层数,32代表存储数组需要的长度。参考博客是传入参数是1,13.本文是0,12,但画出来二叉树结构是一样的,只是每个索引都减一。
总结:不管画出来的二叉树是啥样,是不是完全二叉树,都可以按照完全二叉树来存储,虽然会浪费掉一些空间,但就可以使用完全二叉树的良好性质:如果节点索引从1开始,那么i节点的左孩子为2i,右孩子为2i+1。
原文线段树结构:
本文线段树结构:
线段树——建树
#有两种索引,一是完全二叉树的节点索引,该索引从1开始,为了方便计算
#一种是原数组的索引,该索引从0开始,从1开始都可以,本文从0
num=[1,2,3,4,1,2,3,4,1,2,3,4,1]
length = get_length(1,13)
sum_list=[0]*length
def pushup(rt):
sum_list[rt]=sum_list[2*rt]+sum_list[2*rt+1]
def build(l,r,rt):#l为区间左端点,r为区间右端点,rt为完全二叉树索引
#l,r也是num的索引;第一次调用时,rt为1
if(l==r):
sum_list[rt]=num[l]
return
m = (l+r)//2#这里向下取整
build(l,m,2*rt)
build(m+1,r,2*rt+1)
pushup(rt)
build(0,len(num)-1,1)
print(sum_list)
运行结果:
在二叉树索引中,考虑[0,12]是索引1,[0,6]是索引2,[7,12]是索引3,以此类推。发现sum_list的打印结果与上图是一一对应的。注意后面没有使用到的完全二叉树节点,就全为0,而这些节点都没有用到。
分析:sum_list的0索引不用,第一个数必为0.
线段树——修改
def update(target,add,l,r,rt):#target为目标,add为加的值
if(l==r):
sum_list[rt]=sum_list[rt]+add
return
m = (l+r)//2
if(target<=m):
update(target,add,l,m,2*rt)
else:
update(target,add,m+1,r,2*rt+1)
pushup(rt)
update(5,7,0,len(num)-1,1)
print(sum_list)
运行结果:
分析:第一次调用时,update函数的后三个参数与build函数一样。程序想让num[5]+=7,但是实际上只需要修改,[5],[4,5],[4,6],[0,6],[0,12]这五个区间的sum就行,这里也体现出只需要log n的复杂度。
线段树——查询sum
def quary(L,R,l,r,rt):
if( (L<=l) and (r<=R)):
return sum_list[rt]
m = (l+r)//2
count = 0
#分为两个区间[l,m][m+1,r]
if(L<=m):
count+=quary(L,R,l,m,2*rt)
if(R>=m+1):
count+=quary(L,R,m+1,r,2*rt+1)
return count
print(quary(1,11,0,len(num)-1,1))
运行结果:
分析:程序想统计[1,11]区间内的总和,实际上加和[1],[2,3],[4,6],[7,9],[10,11],也就是36=2+7+13+7+7,计算五个数就可以算出[1,11]区间内的总和。