线段树是一种数据结构,是一种二叉树。线段树将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。线段树对于区间求和等区间操作能够实现复杂度为O(logn)的操作,故得以广泛利用。修改一个值的操作也是O(logn)。

线段树是建立在线段基础上,每个结点都代表了一条线段[a,b]。长度为1的线段称为元线段。非元线段都有两个子结点,左结点代表的线段为[a,(a + b) / 2],右结点代表的线段为[((a + b) / 2)+1,b]。

线段树示意图如下:

在上图中,叶子结点(绿色)是真正的节点,而上面蓝色的线段则表示一段线段的和。(这里面线段树就以区间加和为例)。比如说A[0...9]实际上就是从索引0加和到9得到的结果。所以说,任何一个节点的值都等于其左右两个子节点的和。

线段树的求和和更新用到的知识的递归和分治。比如说更新一个索引为i的值为val。那么要找到这个点然后分别更新它的父节点,父节点的父节点等等。

我们在这里规定根节点的索引为0,那么定位某个索引为index根节点的左孩子右孩子,就类似堆的数据结构,左孩子索引为2*index+1,2*index+2。

1. 当创建一个线段树的时候要从下至上创建根节点,两个孩子创建好以后,根据需要的业务逻辑(区间和,区间最大值,区间最小值),来创建这两个孩子的根节点。
2. 当查询一段区间和的时候,查询区间有可能在线段树某个节点的左半区间(左孩子)那就递归查询左孩子,或者右半区间(右孩子)那就递归查询右孩子,或者(左半右半都有一部分),就递归查询左半部分,右半部分,然后根据业务逻辑合并返回
3. 当更新一个节点值的时候:如果更新位置在节点的左半区间(左孩子),就递归更新左孩子,如果在右半区间,就递归更新右孩子。更新完成以后,里面的小区间变化了,包裹的大区间即该节点也要根据业务逻辑更新

下面我们看一下用数组实现线段树以及更新单点的值,以及求区间的值是如何实现的(注释写的非常详细了):

//线段树
class SegmentTree{
public:vector<int> tree; //存放线段树的数字int lentree;SegmentTree(vector<int> &nums){//用nums来初始化tree数组lentree = nums.size(); //记录树中叶节点的长度/*初始化4N长度的线段树,默认值是0。 索引从0开始,一个节点i的子节点索引分别是2i+1和2i+2*/for (int i = 0; i < 4 * lentree; i++) tree.push_back(0);buildTree(nums, 0, 0, lentree - 1);//建树函数}~SegmentTree(){}//tree_index 是节点在线段树的索引。l和r是线段树中任何一个节点的左边界和右边界,当l==r时候表示单个节点void buildTree(vector<int> &nums, int tree_index, int l, int r){//如果是叶子结点if (l > r) return;if (l == r){tree[tree_index] = nums[l];return;}int mid = (l + r) / 2;//将nums数组分成【l, mid】和【mid + 1, r】两段//计算左右子节点的索引int left = 2 * tree_index + 1;int right = 2 * tree_index + 2;//分别构建左右子树buildTree(nums, left, l, mid);buildTree(nums, right, mid + 1, r);//这里是用了分治法的思想,建好了左右两个子树然后再计算两者之和,得到整个区间的和tree[tree_index] = tree[left] + tree[right];}//查询的是ql到qr之间的和。l,r表示当前查询的左右边界。tree_index表示当前查询到的节点的编号int query(int tree_index, int l, int r, int ql, int qr){if ((l == ql) && (r = qr)){//已经查询到叶子结点了return tree[tree_index];}int mid = (l + r) / 2;int left = 2 * tree_index + 1;int right = 2 * tree_index + 2;if (qr <= mid){//要查询的部分完全在线段树的左半段return query(left, l, mid, ql, qr);}else if (ql > mid){//要查询的部分完全在线段树的右半段return query(right, mid + 1, r, ql, qr);}else{//左右两段都有return query(left, l, mid, ql, mid) + query(right, mid + 1, r, mid + 1, qr);}}void update(int tree_index, int l, int r, int index, int val){//更新位于某个叶子结点(索引为index)的值为valif (l == r){tree[tree_index] = val;return;}int mid = (l + r) / 2;int left = 2 * tree_index + 1;int right = 2 * tree_index + 2;if (index <= mid){//索引index在mid左边,说明在左子树上面update(left, l, mid, index, val);}else{//在右子树上面update(right, mid + 1, r, index, val);}//同样用了分治的办法,左右子节点更新完毕之后再更新自己tree[tree_index] = tree[left] + tree[right];}//输出所有叶子结点的值void print(int tree_index, int l, int r){if (l > r) return;if (l == r){cout << tree[tree_index] << ' ';return;}int mid = (l + r) / 2;int left = 2 * tree_index + 1;int right = 2 * tree_index + 2;print(left, l, mid);print(right, mid + 1, r); }
};//外部调用线段树解决问题的类
class NumArray {
public:SegmentTree *segtree;NumArray(vector<int>& nums) {segtree = new SegmentTree(nums);segtree->print(0, 0, nums.size() - 1);cout << endl;}void update(int i, int val) {//外部调用线段树的更新值函数cout << "before update: " << endl;segtree->print(0, 0, segtree->lentree - 1);cout << "-----------------------------------" << endl;segtree->update(0, 0, segtree->lentree - 1, i, val);cout << "after update:" << endl;segtree->print(0, 0, segtree->lentree - 1);cout << "-----------------------------------" << endl;}int sumRange(int i, int j) {//区间求和return segtree->query(0, 0, segtree->lentree - 1, i, j);}
};int main(){vector<int> nums = {1, 3, 5};NumArray* obj = new NumArray(nums);//修改索引1的值为2obj->update(1, 2);int param_2 = obj->sumRange(0, 2);cout << param_2 << endl;system("pause");return 0;
}

输出是:

c++ 数据结构之 线段树相关推荐

  1. 307. Range Sum Query - Mutable | 307. 区域和检索 - 数组可修改(数据结构:线段树,图文详解)

    题目 https://leetcode.com/problems/range-sum-query-mutable/ 吐槽官方题解 这题的 英文版官方题解,配图和代码不一致,而且描述不清:力扣国内版题解 ...

  2. 数据结构之线段树入门(单点更新区间查询)

    线段树是学习数据结构必须学习的一种数据结构,在ACM,蓝桥等比赛中是经常出现的.利用线段树解题,会使得题目简单易理解.而且线段树是数据结构中比较基础而且用的很多的一种. 线段树定义 线段树是一种二叉搜 ...

  3. 【BZOJ4370】【IOI2015】horses 数据结构 平衡树+线段树

    4370: [IOI2015]horses马 Time Limit: 30 Sec Memory Limit: 1500 MB Description 像他的祖先一样,Mansur喜欢繁殖马匹.目前, ...

  4. 【数据结构】线段树的扩展与应用

    线段树是一种非常基础的数据结构,但有的时候仅仅是普通的线段树无法满足需求,那么我们就要对其进行一些扩展. Chapter1:标记永久化 实现 普通的线段树通过懒标记(Lazy Tag)以 O ( n ...

  5. 蒟蒻的ACM数据结构(一)-线段树

    浅谈线段树的指针写法 一.基本概念 二.代码实现与基本操作 0.基础数据结构 1.建树 built函数 2. 单点查询 3.单点修改 4.区间查询 5.区间修改 三.优化 (一). Lazy-Tag懒 ...

  6. 0x43.数据结构进阶 - 线段树

    目录 一.基础线段树 线段树的建树 线段树的单点修改 线段树的区间查询 线段树的延迟标记(懒惰标记) 1.POJ3486 ASimpleProblemwithIntegersA\ Simple\ Pr ...

  7. 数据结构:线段树及ST算法比较

    ST算法是一种高效的计算区间最值的方法. 他的思想是将询问区间分解成两个最长的二次幂的长度的区间并集的形式. 所以与线段树不同,这种区间分解其实存在相交的分解. 因此ST算法能维护的只是一些简单的信息 ...

  8. 数据结构之线段树进阶(区间更新lazy标记)

    之前说了线段树的点更新和区间求和.其实点更新是区间更新的一种最基础的做法.我们把一个点想像成一个区间的话,不就是最简单的区间更新了嘛. 为什么要把区间更新和点更新分开来看呢?假如我们对区间[l,r]进 ...

  9. 数据结构之线段树合并——永无乡,Lomsat gelral,Tree Rotations,Tree Rotations Escape Through Leaf

    文章目录 [HNOI2012]永无乡 Lomsat gelral 「POI2011 R2 Day2」旋转树木 Tree Rotations Escape Through Leaf 线段树合并与 fhq ...

最新文章

  1. [数字技巧]最大连续子序列和
  2. linux动态库文件.so为什么有多个版本号?(多个名字)(小版本升级)
  3. homeassistant树莓派cpu_集成ESP8266的WiFi RGB灯泡接入Home Assistant
  4. Preference跳转activity出错Unable to find explicit activity class
  5. 表单oninput和onchange事件区别
  6. 复旦教授:不打不骂不罚是培养不出优秀孩子的!值得一看
  7. 爬get接口_网络字体反爬之起点中文小说
  8. box2dweb 学习笔记--sample讲解
  9. (原创)sqlite封装库SmartDB1.3发布
  10. Domino中运用ajax判断帐号是否存在的简单例子
  11. C语言PAT乙级试题答案1016
  12. 可以丢掉SGD和Adam了,新的深度学习优化器Ranger:RAdam + LookAhead强强结合
  13. SAP 生产订单创建修改日期
  14. 360°视频论文调研
  15. UEditor之——图片上传组件大小4M的限制
  16. 关于腾讯应用宝上架的应用版本回退的问题
  17. Python--快递收费小程序
  18. 云计算(Day 8)
  19. Git详解之必知点----Git、本地仓库、远程仓库、IDEA集成Git
  20. CF 71A [字符串统计]

热门文章

  1. python从入门到精通需要多久-Python从入门到精通:一个月就够了
  2. Kaldi语音识别快速入门
  3. Nuance语音识别技术及解决方案
  4. 独家:中国电信提出建产业统一开发平台 避免个人与政企业务脱节
  5. 一种提升语音识别准确率的方法与流程
  6. lda主题模型困惑度_主题模型(三):LDA主题个数选择
  7. js 异步执行_js执行机制:同步与异步(宏任务与微任务)
  8. python安装numpy模块教程_Windows系统中安装Python模块pip numpy matplotlib
  9. 主进程退出后子进程还会存在吗?_[docker]从一个实例,一窥docker进程管理
  10. 报错:Uncaught TypeError: 获取的元素节点.setAttribute is not a function