Segment_tree
一、Segment_tree
1.什么是线段树?
其实就是以类似于数组的形式存储了一些有关的信息,比如一段区间的和,一段区间的最大值最小值之类的问题
2.为什么需要线段树呢?
当我们需要查询某个区间的最大值最小值的时候我们需要扫一遍查询的区间,而当我们查询的次数为n次时,时间复杂度趋于O(n^2)。那我们如果是计算区间的和的时候不是可以用前缀和优化吗?,但是我们想一想,此时如果我更新数组中的某个值,那么这个前缀和是不是会发生改变,所以我们优化的方案也会使得整个时间复杂度趋于O(n^2),此时线段树就诞生了,因为是一种标准的树结构,无论是查找还是更新值,都会处于O(logn)的复杂度,我们如果有n次查询那也不过是nlogn的复杂度,可谓是大提升。
二、构建思路
1.构树
这里我们采用数组构树,主要是比较容易写一点,当我们有n个节点的时候我们需要多大的数组空间呢?
答案是:4*n,为什么是4*n的空间复杂度呢?这里我们想,当我们需要的时间变得很少的时候,也是需要付出一点空间上的代价的。这里不可能采用一般的树结构,不然会产生斜树那种效果,这样时间复杂度就会退化成O(n),所以我们要构建一棵完全二叉树,考虑有n个节点,极端情况下就会有2*n个子节点,那么我们需要开辟的数组是4*n,那么剩下的n个够不够吗?
证明:
如果有n个节点,那么他的父节点就会有n/2个,一直到最后只有1个节点
那么式子为n/2 + n/4 +.....+1 <=n(采用数学归纳法证明)
根据中间把左右两个范围分开,然后重复这样直到最后只有一个节点了。
2.更新和查询
1.更新的时候,根据范围找就可以了
2.重点是查询的过程,查询某个区间时,有四种情况
代码段在这里:
1 int query(int s,int e,int l,int r,int node) 2 { 3 if(s == e)//如果已经找到了叶子节点,说明这个节点就是要找的区域中的一个值,把他返回即可 4 return tree[node]; 5 else if(s > r || e < l)//如果接下来要找的范围已经不属于查找区域中了,那么直接返回0表示这段不在区域中,不参与计算 6 return 0; 7 else if(s >= l && e <= r)//如果接下来要找的范围包含在了查找区域中,直接返回这个点的tree[node]值即可,表示这个区域的和 8 return tree[node]; 9 else {//其他情况说明查找的区域和当前的区域有相交的地方也有不相交的地方,所以一直二分下去查找就行 10 int m = (s + e) / 2; 11 int lnode = node * 2; 12 int rnode = node * 2 + 1; 13 int lsum = query(s,m,l,r,lnode);//计算这个区域的左边的和 14 int rsum = query(m + 1,e,l,r,rnode);//计算这个区域的右边的和 15 return lsum + rsum;//返回这个区域的和 16 } 17 }
三、代码实现
1 #include "stdio.h" 2 int arr[100]; 3 int tree[400]; 4 void build_tree(int node,int s,int e) 5 { 6 if(s == e){//如果已经到了叶子节点,可以直接进行赋值 7 tree[node] = arr[s]; 8 return; 9 } 10 int m = (s + e) / 2; 11 int lnode = node * 2;//计算左孩子节点 12 int rnode = node * 2 + 1;//计算右孩子节点 13 build_tree(lnode,s,m);//构建左子树 14 build_tree(rnode,m + 1,e);//构建右子树 15 tree[node] = tree[lnode] + tree[rnode];//这个树的根节点等于左子树加右子树的和,当然也可以进行更改 16 } 17 void update_tree(int s,int e,int node,int idx,int val) 18 { 19 if(s == e){//如果已经找到了叶子节点,说明这个节点就是要更改的节点 20 arr[s] = val;//更改原数组中的值 21 tree[node] = val;//更改tree中的值 22 return; 23 } 24 int m = (s + e) / 2; 25 int lnode = node * 2; 26 int rnode = node * 2 + 1; 27 if(idx >= s && idx <= m) 28 update_tree(s,m,lnode,idx,val);//更改的节点处于左子树 29 else 30 update_tree(m + 1,e,rnode,idx,val);//更改的节点处于右子树 31 tree[node] = tree[lnode] + tree[rnode];//重新计算父节点 32 } 33 int query(int s,int e,int l,int r,int node) 34 { 35 if(s == e)//如果已经找到了叶子节点,说明这个节点就是要找的区域中的一个值,把他返回即可 36 return tree[node]; 37 else if(s > r || e < l)//如果接下来要找的范围已经不属于查找区域中了,那么直接返回0表示这段不在区域中,不参与计算 38 return 0; 39 else if(s >= l && e <= r)//如果接下来要找的范围包含在了查找区域中,直接返回这个点的tree[node]值即可,表示这个区域的和 40 return tree[node]; 41 else {//其他情况说明查找的区域和当前的区域有相交的地方也有不相交的地方,所以一直二分下去查找就行 42 int m = (s + e) / 2; 43 int lnode = node * 2; 44 int rnode = node * 2 + 1; 45 int lsum = query(s,m,l,r,lnode);//计算这个区域的左边的和 46 int rsum = query(m + 1,e,l,r,rnode);//计算这个区域的右边的和 47 return lsum + rsum;//返回这个区域的和 48 } 49 } 50 int main() 51 { 52 int n;//test case://6 //1 3 5 7 9 11 53 scanf("%d",&n);//输入节点个数 54 for(int i = 1;i <= n;i++) 55 scanf("%d",&arr[i]); 56 build_tree(1,1,n);//构造线段树 57 for(int i = 1;i <= 4 * n;i++)//n个节点的线段树最长为4 * n 58 printf("%d ",tree[i]); 59 printf("\n\n"); 60 update_tree(1,n,1,5,6);//更新5号节点的值为6 61 for(int i = 1;i <= 4 * n;i++) 62 printf("%d ",tree[i]); 63 printf("\n\n"); 64 int s = query(1,n,3,6,1);//询问3-6这个区间的和 65 printf("%d",s); 66 return 0; 67 }
本文来自博客园,作者:{scanner},转载请注明原文链接:{https://home.cnblogs.com/u/scannerkk/}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】