线段树的概念与性质

线段树首先是一棵树,而且是二叉树。树上的每个节点对应于一个区间[a,b],a,b通常为整数。同一层的节点所代表的区间,互相不重叠。并且同一层的区间加起来是连续的区间,叶子节点的区间是单位长度1,无法再分

例如下图表示的是区间[1,9]的线段树

从图上可以看出线段树具有下面几个性质:

  • 每个区间的长度是区间内整数的个数
  • 叶子节点长度为1,不能再分
  • 若一个节点对应的区间是[a,b],则其子节点对应的区间分别是[a,(a+b)/2]和[((a+b)/2)+1,b](除法去尾取整)
  • 线段树的平分构造实际上是利用二分的方法,若根节点对应的区间是[a,b],那么他的深度为 l o g 2 ( b − a + 1 ) + 1 log_2(b-a+1)+1 log2​(b−a+1)+1(向上取整)
  • 叶子节点的数目和根节点表示的区间长度相同
  • 线段树节点要么0度,要么2度,因此若叶子节点数目为N,则线段树总节点数目为2N-1
  • 线段树上,任意一个区间被分解后得到的“终止节点”数目都是logn量级
  • 线段树上更新叶子节点和进行区间分解时间复杂度都是O(logn)的

以上结论为线段树能在O(logn)的时间内完成插入、更新、查找、统计数据提供了理论依据

线段树的构建

伪代码

function 以节点idx为根结点,idx对应区间为[l,r]对节点idx初始化if(l != r)以idx的左孩子为根建树,区间为[l,(l+r)>>1]以idx的右孩子为根建树,区间为[((l+r)>>1)+1,r]

建树的时间复杂度是O(n),n为根节点对应的区间长度

线段树的基本用途

线段树适用于和区间统计有关的问题。比如某些数据可以按区间进行划分,按区间动态进行修改,而且还需要按区间多次查询,那么使用线段树可以达到较快的速度

线段树应用举例

原题链接:HDU1166

给你一个数列KaTeX parse error: Expected 'EOF', got '&' at position 2: A&̲#095;1A_2.…,并且多次进行下列两个操作:

  1. 对数列里面的某个数进行加减
  2. 询问这个序列里面任意一个连续的子序列KaTeX parse error: Expected 'EOF', got '&' at position 2: A&̲#095;iA_{i…的和是多少

构建一棵线段树,[1,n]就是根节点对应的区间,在每个节点记录该节点对应的区间内的和sum

对于操作1,因为序列里面KaTeX parse error: Expected 'EOF', got '&' at position 2: A&̲#095;i最多只会被线段树的logn个节点覆盖。只要求对线段树覆盖的KaTeX parse error: Expected 'EOF', got '&' at position 2: A&̲#095;i所在节点的区间进行操作,因此时间复杂度是O(logn)

对于操作2,同样只需要找到区间覆盖的“终止”节点,然后把“终止”节点的sum累加起来,因为这些节点的数量是logn的,所以这一步的复杂度也是O(logn)

走到节点[l,r]时,如果要查询的区间就是[l,r],那么直接返回该节点的sum,并累加到总和上;如果不是,则取mid = (l+r) >> 1,然后看要查询的区间与[l,mid]或[mid+1,r]哪个有交集,就进入哪个区间进行进一步查询

用线段树解题,关键是要想清楚每个节点要存哪些信息。先建树,然后插入数据,最后更新、查询

每个节点可表示为一个结构体

class Node {int l, r, sum;
}

线段树一般可以由左右指针或数组存储,根节点下标为0,假设线段树某节点为i,则:

  • 左子节点下标为(i << 1) + 1
  • 右子节点下表为(i << 1) + 2

如果根节点区间为[1,n]

  • 使用左右节点指针,则数组需要有2n-1个元素
  • 不适用左右节点指针,则数组需要有 2 ∗ 2 l o g 2 n − 1 2*2^{log_2n}-1 2∗2log2​n−1个元素。由于该值小于等于4n-1,因此实际运用时常开4n大小的数组即可

构造初始线段树由根节点一次往下递归构造,代码如下

static void make(int l, int r, int idx) { // l为左端点,r为右端点,idx为数组下标n[idx].l = l;n[idx].r = r;if (l == r) //已经是叶节点了n[idx].sum = t[l]; //也可以是t[r]else {make(l,(l + r) >> 1,(idx << 1) + 1); //递归构造左子树make(((l + r) >> 1) + 1, r, (idx << 1) + 2); //递归构造右子树n[idx].sum = n[(idx << 1) + 1].sum + n[(idx << 1) + 2].sum;//父节点值等于子节点值之和,线段树分成两段}
}

t数组存放输入的初始值。当总数为n时,执行make(1,n,0),输入样例得到的额线段树如下图所示

当第idx个营地增加j个人时,从根节点n[0]开始,不断往下递归更改人数,即只要包含改点idx的线段都增加相应的人数j,代码如下

static void add(int i, int j, int idx) { // 第i个营地增加j个人// 从根节点不断往下更改,只要包含点i的线段都增加数量jn[idx].sum += j;if (n[idx].l == i && n[idx].r == i) // 如果找到i的叶子节点则停止return;if (i <= ((n[idx].l + n[idx].r) >> 1)) // 如果i在线段左边add(i, j, (idx << 1) + 1); // 递归进入左子节点else // 如果i在线段右边add(i, j, (idx << 1) + 2); // 递归进入右子节点
}

同理,当第i个营地减少j个人时,代码如下

static void sub(int i, int j, int idx) { // 第i个营地减少j个人// 从根节点不断往下更改,只要包含点i的线段都减少数量jn[idx].sum -= j;if (n[idx].l == i && n[idx].r == i) // 如果找到i的叶子节点则停止return;if (i <= ((n[idx].l + n[idx].r) >> 1)) //如果i在线段左边sub(i, j, (idx << 1) + 1); // 递归进入左子节点elsesub(i, j, (idx << 1) + 2); // 递归进入右子节点
}

询问从[l,r]区间内的营地人数代码如下

static void query(int l, int r, int idx) { // 初始idx为0,即从根节点开始查找if (l <= n[idx].l && r >= n[idx].r)SUM += n[idx].sum;else {int mid = (n[idx].l + n[idx].r) >> 1;if (r <= mid) // 要查询的区间在左边query(l, r, (idx << 1) + 1);else if (l > mid) // 要查询的区间在右边query(l, r, (idx << 1) + 2);else { // 要查询的区间在中间,分段查询,左右都查query(l, r, (idx << 1) + 1);query(l, r, (idx << 1) + 2);}}
}

这段代码可以这么理解,假设要查询的区间为[l,r],当前走到的节点对应的区间是[n[idx].l,n[idx].r],如果 [ n [ i d x ] . l , n [ i d x ] . r ] ∈ [ l , r ] [n[idx].l,n[idx].r]\in [l,r] [n[idx].l,n[idx].r]∈[l,r],则将当前区间的信息记录下来,例如求区间和,就将当前区间和的信息累加到SUM中;如果要查询区间的右端点r<=当前区间的中点,说明需要查询的区间就在左子树里;反之,如果要查询区间的左端点l>当前区间的终点,说明要查询的区间就在右子树里;如果上面两种情况都不满足,说明左右子树均有需要的信息,左右子树都要查

完整代码如下

import java.util.Scanner;public class Main {static Node[] n;static int[] t;static int SUM;public static void main(String[] args) {Scanner cin = new Scanner(System.in);int T = cin.nextInt();for (int p = 1; p <= T; p++) {int tmp = 0;int N = cin.nextInt();n = new Node[4 * N];t = new int[N + 1];for (int i = 1; i <= N; i++)t[i] = cin.nextInt();make(1, N, 0);while (true) {String str = cin.next();if ("End".equals(str))break;int i = cin.nextInt();int j = cin.nextInt();if ("Query".equals(str)) {SUM = 0;if (tmp == 0) {System.out.println("Case " + p + ":");tmp++;}query(i, j, 0);System.out.println(SUM);} else if ("Add".equals(str))add(i, j, 0);else // Subsub(i, j, 0);}}}static void make(int l, int r, int idx) { // l为左端点,r为右端点,idx为数组下标n[idx] = new Node();n[idx].l = l;n[idx].r = r;if (l == r) // 已经是叶节点了n[idx].sum = t[l]; // 也可以是t[r]else {make(l, (l + r) >> 1, (idx << 1) + 1); // 递归构造左子树make(((l + r) >> 1) + 1, r, (idx << 1) + 2); // 递归构造右子树n[idx].sum = n[(idx << 1) + 1].sum + n[(idx << 1) + 2].sum;// 父节点值等于子节点值之和,线段树分成两段}}static void add(int i, int j, int idx) { // 第i个营地增加j个人// 从根节点不断往下更改,只要包含点i的线段都增加数量jn[idx].sum += j;if (n[idx].l == i && n[idx].r == i) // 如果找到i的叶子节点则停止return;if (i <= ((n[idx].l + n[idx].r) >> 1)) // 如果i在线段左边add(i, j, (idx << 1) + 1); // 递归进入左子节点else // 如果i在线段右边add(i, j, (idx << 1) + 2); // 递归进入右子节点}static void sub(int i, int j, int idx) { // 第i个营地减少j个人// 从根节点不断往下更改,只要包含点i的线段都减少数量jn[idx].sum -= j;if (n[idx].l == i && n[idx].r == i) // 如果找到i的叶子节点则停止return;if (i <= ((n[idx].l + n[idx].r) >> 1)) // 如果i在线段左边sub(i, j, (idx << 1) + 1); // 递归进入左子节点elsesub(i, j, (idx << 1) + 2); // 递归进入右子节点}static void query(int l, int r, int idx) { // 初始idx为0,即从根节点开始查找if (l <= n[idx].l && r >= n[idx].r)SUM += n[idx].sum;else {int mid = (n[idx].l + n[idx].r) >> 1;if (r <= mid) // 要查询的区间在左边query(l, r, (idx << 1) + 1);else if (l > mid) // 要查询的区间在右边query(l, r, (idx << 1) + 2);else { // 要查询的区间在中间,分段查询,左右都查query(l, r, (idx << 1) + 1);query(l, r, (idx << 1) + 2);}}}
}class Node {int l, r, sum;
}

关于线段树的更多习题见线段树例题

单点更新
HDU 1166 敌兵布阵
HDU 1754 I Hate It
POJ 3264 Balanced Lineup
HDU 1394 Minimum Inversion Number
HDU 2795 Billboard
POJ 2352 Stars
POJ 2481 Cows
POJ 3067 Japan
CF 6E Exposition

区间更新
HDU 1698 Just a Hook
HDU 1556 Color the ball
ZOJ 1610 Count the Colors
POJ 2528 Mayor’s posters
Ural 1019 A Line Painting

浅谈线段树(Segment Tree)相关推荐

  1. 【算法微解读】浅谈线段树

    浅谈线段树 (来自TRTTG大佬的供图) 线段树个人理解和运用时,认为这个是一个比较实用的优化算法. 这个东西和区间树有点相似,是一棵二叉搜索树,也就是查找节点和节点所带值的一种算法. 使用线段树可以 ...

  2. 【转】Senior Data Structure · 浅谈线段树(Segment Tree)

    本文章转自洛谷 原作者: _皎月半洒花 一.简介线段树 ps: _此处以询问区间和为例.实际上线段树可以处理很多符合结合律的操作.(比如说加法,a[1]+a[2]+a[3]+a[4]=(a[1]+a[ ...

  3. 线段树(Segment Tree)

    1.概述 线段树,也叫区间树,是一个完全二叉树,它在各个节点保存一条线段(即"子数组"),因而常用于解决数列维护问题,基本能保证每个操作的复杂度为O(lgN). 线段树是一种二叉搜 ...

  4. BZOJ.4695.最假女选手(线段树 Segment tree Beats!)

    题目链接 区间取\(\max,\ \min\)并维护区间和是普通线段树无法处理的. 对于操作二,维护区间最小值\(mn\).最小值个数\(t\).严格次小值\(se\). 当\(mn\geq x\)时 ...

  5. 从MySQL Bug#67718浅谈B+树索引的分裂优化

    从MySQL Bug#67718浅谈B+树索引的分裂优化 1月 6th, 2013 发表评论 | Trackback 问题背景 今天,看到Twitter的DBA团队发布了其最新的MySQL分支:Cha ...

  6. C语言实现段树segment tree(附完整源码)

    C语言实现段树segment tree 段树结构体定义 实现以下6个接口 完整实现和main测试源码 段树结构体定义 typedef struct segment_tree {void *root; ...

  7. 浅谈oracle树状结构层级查询

    oracle树状结构查询即层次递归查询,是sql语句经常用到的,在实际开发中组织结构实现及其层次化实现功能也是经常遇到的,虽然我是一个java程序开发者,我一直觉得只要精通数据库那么对于java开发你 ...

  8. 浅谈oracle树状结构层级查询测试数据

    浅谈oracle树状结构层级查询 oracle树状结构查询即层次递归查询,是sql语句经常用到的,在实际开发中组织结构实现及其层次化实现功能也是经常遇到的,虽然我是一个java程序开发者,我一直觉得只 ...

  9. 【数据结构】线段树(interval tree)

    线段树(interval tree),也叫区间树.也是一种二叉搜索树,同一般的BST不同之处在于:线段树的每一个结点包含的是一个区间而不是一个数.具体的描述如下: 从图上可以看出,线段树的每一个结点都 ...

最新文章

  1. 用python+pillow模块实现抖音晃眼睛的特效,图像处理之路(附源码)
  2. Linux TOP 交互命令
  3. python对应位置相乘
  4. python是c语言写的吗-C语言是学python的基础吗?
  5. 个人操作系统V0.3(Personal Operating System,缩写为POS)是一种小型的多任务嵌 入式操作系统,用于ARM 公司Cortex-M0 内核的微控制器。
  6. TCP/IP网络编程之域名及网络地址
  7. Cmake构建_设置debug与release输出路径
  8. 第十三次博文:教你从立创EDA库导入AD库,保姆级别!
  9. 领导说我对任务的理解是错的
  10. 【研报笔记】光大技术择时系列1:基于阻力支撑相对强度(RSRS)的市场择时
  11. 营销短信API专用通道
  12. [GYCTF2020]Blacklist
  13. 搜索引擎核心读书心得2:暗网抓取
  14. 软件测试修炼之道-转载
  15. sql server中字符集和排序规则到底什么关系
  16. 如何用纯 CSS 创作一个小和尚 1
  17. 微信公众平台开发视频上传
  18. Eclipse各版本官方下载地址
  19. 气动机械手设计,XG916Ⅱ轮式装载机后驱动桥设计,数控机床上下料机械手设计,大直径辊筒双头镗孔专机设计,大直径辊筒双头镗孔专机设计,冲压机床液压控制系统设计,单片机的温度控制系统设计……
  20. [Python] ConnectionResetError: [Errno 104] Connection reset by peer

热门文章

  1. 求职季!50份简历设计,助你找到梦寐以求的工作
  2. Java根据子节点递归父节点
  3. Rust裸指针的安全性实例
  4. 每一次成长,都缘于打破固有的认知
  5. 软件测试要素_软件美好未来的要素
  6. VueCLI3创建项目
  7. c语言指针和结构体(B站鹏哥讲解版)
  8. 黑苹果系统安装通用教程(Clover引导)
  9. 数字式传感器打气泵解决方案——数字轮胎充气泵
  10. Python报错——IndentationError: unexpected indent