线段树 理解
线段树主要是为了降低对非常庞大的数组的操作的时间复杂度,常见的操作有:
- 对数组中的某一段元素求和。
- 修改数组中某个指定位置元素的值。
- 求某段元素中的最大最小值。
在后面我会分析其各个操作在简单数组中和在线段树中的时间复杂度。
线段数基于二叉数,它是把一个数组从中间分成两段,再往下把每段再分成两段,直到每段的数据只有一个元素,作为叶子节点,采用递归操作,递归往回走的过程中又通过叶子节点,自下往上求出其下每段数据元素的总和,下面是哔哩哔哩上的 正月点灯笼 的线段树的视频的截图:
可以看到若是求数组arr[1]到arr[5]元素和,则可以先通过根节点访问到arr[1]和arr[2]的值,然后直接加上线段3到5,即tree[2]的值即可得到答案,即节点所在线段包含于所求的区域内,可以直接返回节点的值无需挨个去遍历叶子节点,时间复杂度低于常规数组,若数组非常庞大,则两者时间复杂度相差更多。
c++创建线段树代码如下:
//这里使用的数组和图片中的数组不一样,arr表示线段树数组,a为一般数组
struct tree_node//可以先宏定义一个想要的长度给max_len
{
uns value=0,left,right;
}arr[max_len];
void build_tree(ElemType a[],Elemtype node,Elemtype L,Elemtype R)//可以先给EelmType 定以为想用的数据类型,当然传入的实参也可以定义为不一样的类型;这里的数组a[]是想要转化为线段树的数组,node为线段树数组的下标
if(L==R)//可以理解为叶子节点
{
arr[node].value=a[L];//把它的元素的值赋给线段数的叶子节点
arr[node].left=L;//叶子节点的L和R是相等的
arr[node].right=L;
}
else
{
arr[node].left=L;
arr[node].right=R;
Elemtype left_node=2*node+1;//从0开始创建,也可以从1开始创建,不过相应的left_node=2*node,right_node=2*node+1
Elemtype right_node=2*node+2;
Elemtype mid=(L+R)/2;
build_tree(a,left_node,L,mid);
build_tree(a,right_node,mid+1,R);
arr[node].value=arr[left_node].value+arr[right_node].value;//递归返回的时候计算每个节点的值,也就是其下左右孩子节点的数据和,方便区间元素的求和
}
}//调用build_tree函数即可创建线段树,注 以上代码只适合从0开始创建
现在说一下,求数组某一段元素和的代码:
Elemtype sum(Elemtype node,Elemtype L,Elemtype R)
{
if(arr[node].left>R||arr[node].right<L) return 0;//节点所表示的区间不在所求区间内
else if(L<=arr[node].left&&arr[node].right<=R) return arr[node].value;//节点表示区间在所求区间内
else if(arr[node].left==arr[node].right) return arr[node].value;//找到叶子节点
else
{
Elemtype left_node=2*node+1;
Elemtype right_node=2*node+2;
Elemtype sum_left,sum_right;
sum_left=sum(left_node,L,R);
sum_right=sum(right_node,L,R);
return sum_left+sum_right;
}
}
在看一下修改元素的代码:
void updata_value(Elemtype node,Elemtype site,Elemtype data)
{
if(arr[node].left==arr[node].right) arr[node].value=data;//递归出口,找到指定位置,修改其值
else
{
Elemtype left_node=2*node+1;
Elemtype right_node=2*node+2;
if(arr[left_node].left<=site&&arr[left_node].right>=site)//site在左节点范围内
{
updata_value(left_node,site,data);
}
else//site在右节点范围内
{
updata_value(right_node,site,data);
}
arr[node].value=arr[left_node].value+arr[right_node].value;//修改叶子节点值后,其上用到它的值的节点也需修改
}
}
求区域内最大值代码:
Elemtype find_max(Elemtype node,Elemtype L,Elemtype R)
{
if(arr[node].left>R||arr[node].right<L) return 0;//节点所表示的区间不在所求区间内
else if(arr[node].left==arr[node].right) return arr[node].value;//求最大值,需找到叶子节点
else
{
Elemtype left_max,right_max;
Elemtype left_node=2*node+1;
Elemtype right_node=2*node+2;
left_max=find_max(left_node,L,R);
right_max=find_max(right_node,L,R);
return left_max>right_max?left_max:right_max;
}
}
我们可以看到线段树对求和操作时间复杂度降低,为最差情况下为O(lgn),
简单数组为O(n);对查找某个区域内最大值,线段树好像更麻烦一些; 对修改数组操作,线段树需遍历到指定位置的叶子节点,最差情况下为O(lgn),而简单数组为O(1),显然从这个操作来说简单数组更优。
线段树大概平均了求和和修改的复杂度,使得整体上更优,当数组非常庞大时,其优势更明显;
这是我第一次在csdn上做笔记,我是一个编程新手,如果上面有错误的地方,希望大佬们能帮忙指出,感谢!!