c语言线段树建树程序,线段树的C语言实现

其实关于线段树已经有很多博客的水平远远超过本文,而本文的目的只是在于给出线段树的建立,点修改,求和的不同的并且详细的写法,想给与作者一样的小白一点帮助

·什么是线段树

线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。

简单的来说线段树的作用是将一个区间划分为若干个子区间,它的每一个节点代表了一个子区间。线段树总是拥有这样的性质 :对于B>A的区间[A,B]总有其左子节点的区间为[A,(A+B)/2],其右子节点的的区间为[(A+B)/2+1,B]。

作为高级数据结构的一种,我们首先得了解我们使用线段树来干些什么:

1.数字之和——总数字之和 = 左区间数字之和 + 右区间数字之和

2. 最大公因数(GCD)——总GCD = gcd( 左区间GCD , 右区间GCD );

3.最大值——总最大值=max(左区间最大值,右区间最大值)

接下来是一些关于二叉树的原理图

引自 岩之痕 大佬 >https://blog.csdn.net/zearot/article/details/52280189

86e04dfd6ddf1e0ee7e7ed6b8cd57de5.png

我们之所以使用线段树的原因,除了其固有的性质外还有很重要的一点在于我们可以用数组实现它,那我们如何用数组建立一个线段树呢?

这里是一个很详细的二叉堆实现过程,实际上的话二叉堆与线段树大同小异,所以将这个链接贴在这里,不明白基本定义可以看一下

skywang12345 大佬 http://www.cnblogs.com/skywang12345/p/3610187.html

几个需要注意的地方做一下笔记:

1.线段树开的是一维数组, 然后我们可以通过这样的计算得出我们应该释放的内存大小。

对于一个长度为N的区间(这里假设了N正好是2的一个n次幂),显然可以计算得到其层数为log2N+1,所以的话对于每一层其节点个数为2n-1,进行求和

∑ i = 1 l o g 2 N + 1 2 i − 1 \displaystyle\sum_{i=1}^{log_2N+1} 2^{i-1}i=1∑log2​N+1​2i−1

结果等于2logN+2=4N

其实说了这么多,结论其实很简单:对于N的数组开4N空间作为线段树。而通过上面的计算也可以很清楚的知道,实际上所需要的空间总是小于4N,如果愿意优化的话也可以通过进一步计算得出。

2.在进行节点访问时我们这样实现(这里以从1开始编号的树为例)

Left_child=Node<<2;//访问该节点的左子节点

Right_child=Node<<2|1;//访问该节点的右子节点

·线段树的实现

一个简单的线段树是由这样的三个部分组成的

1.构造线段树,建树函数

我们一般使用递归自底向上构造线段树,所以Build的作用是 在position这个节点构造一个区间的线段树,且这个节点的区间为 [interval_left,interval_right]

int Build(int interval_left,int interval_right,int position);

2.对线段树进行点修改

由于对于一个元素进行修改,意味着对所有的相关父节点进行修改,所以一般来说使用递归修改,但是其实还有一个更加容易理解的方式,非递归方式、从上至下修改。本文给出非递归做法。

int Update_Loop(int arry_position,int add,int interval_left,int interval_right,int position);

3.进行线段树的查询,实际上是查询某一个区间的值

线段树的区间查询利用的是多个区间合并成一个区间,本文给出一个不那么复杂的写法。

int Query(int L,int R,int interval_left,int interval_right,int position);

接下来是具体的实现方式,并对每一个函数给出一个调用例子,该实例基于一个全局变量数组arry[11],该数组从arry[1]开始赋值,1-10分别赋值1-10

·建树函数

int Assign(int position)

{

seg_tree[position]=seg_tree[position<<1]+seg_tree[position<<1|1];

// 线段树一个节点的值 = 其左子节点的值 + 其右子节点的值

return 0;

}

int Build(int interval_left,int interval_right,int position)//参数分别代表,左区间,右区间,节点在线段树中的节点编号

{

if(interval_left==interval_right){//若到达叶节点

seg_tree[position]=arry[interval_left]; //则此线段树的值就是对应的数组内的值

return 0;//返回

}

int interval_middle;//当区间可继续分割,则计算区间中部,切割区间

interval_middle=(interval_left+interval_right)>>1;

Build(interval_left,interval_middle,position<<1);//对区间左部进行构造

Build(interval_middle+1,interval_right,position<<1|1);//对区间右部进行构造

Assign(position);//上面两行递归可以保证此时该节点的子节点都已经被赋值,故此时更新该节点的值

return 0;

}

此时我们要构造arry[11]的线段树则如此调用:

Build(1,10,1);

在第一个节点构造一个区间为[1,10]的线段树,由于是递归所以如此调用便可以保证线段树构造完全

·点修改函数

int Update_Loop(int arry_position,int add,int interval_left,int interval_right,int position)

{ //这个点在区间的位置,增加多少,左区间 ,右区间 ,节点编号

seg_tree[position]+=add;//将编号为1的节点增加add

int interval_middle;

while( interval_left!=interval_right )//只要还没有抵达叶节点,就继续分割区间

{

interval_middle=(interval_left+interval_right)>>1;

if( arry_position<=interval_middle )//检测arry_position在分割后的左边还是右边

{

interval_right=interval_middle;

position=position<<1;

seg_tree[position]+=add;

}else{

interval_left=interval_middle;

position=position<<1|1;

seg_tree[position]+=add;

}

}

seg_tree[position]+=add;//增加叶节点的值

return 0;

}

假如我们要修改arry[1]的值,把其增加10,则如此调用:

Update_Loop(1,10,1,10,1);

·线段树的查询

//Query的作用是在给定的区间查询一个区间的和的值

//在这里关键的思想在于查询一个区间的值,那么这个值一定是等于二分之后左半部分的值加上右半部分的值

int Query(int L,int R,int interval_left,int interval_right,int position)

{

if(L==interval_left&&R==interval_right)//如果正好线段树记录的左右区间与查询的左右区间一样,则直接返回

return seg_tree[position];

if(interval_left>R||interval_rightR)//如果查询区间不在线段树记录的区间内,则返回0

return 0;

int ans=0;

int interval_middle=(interval_left+interval_right)>>1;

int Middle;//Middle的作用是分割所要查询的区间

if(L<=interval_middle&&R>interval_middle) Middle=interval_middle;

else if(R<=interval_middle) Middle=R;

else Middle=L-1;

ans= Query(L,Middle,interval_left,interval_middle,position<<1)+Query(Middle+1,R,interval_middle+1,interval_right,position<<1|1);

return ans;

}

若查询2-10区间的和,则如此调用:

Query(2,10,1,10,1);

最后总结一下,其实线段树并不是一种非常难得数据结构,它与其他树类型可以说是异曲同工,所以重点是掌握一个,那么其他的也差不多都能掌握

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值