12

数据结构-Segment Tree 线段树

 2 years ago
source link: https://mikeygithub.github.io/2022/04/17/yuque/xqpkce/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
数据结构-Segment Tree 线段树

数据结构-_

Mikey 2022年4月17日 下午

2k 字

30 分钟

线段树(Segment Tree)主要用于维护区间信息(要求满足结合律)。与树状数组相比,它可以实现 O(logn)的区间修改,还可以同时支持多种操作(加、乘),更具通用性。

线段树 segmentTree 是一个二叉树,每个结点保存数组 nums 在区间 [s,e] 的最小值、最大值或者总和等信息。线段树可以用树也可以用数组(堆式存储)来实现。对于数组实现,假设根结点的下标为 0,如果一个结点在数组的下标为 node,那么它的左子结点下标为 node×2+1,右子结点下标为 node×2+2。

[s,e] =>[start,end]

线段树的创建通过给定的数组开辟一个四倍数组来存储(参考二叉堆),左子结点下标为 node×2+1,右子结点下标为 node×2+2,递归构建。

public class SegmentTree {

private int[] segmentTree;
private int n;

public SegmentTree(int[] nums) {
n = nums.length;
segmentTree = new int[nums.length * 4];//一般需要4倍数组大小
build(0, 0, n - 1, nums);//构建线段树
}
//[1, 2, 3, 4, 5]
//[15, 6, 9, 3, 3, 4, 5, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
private void build(int node, int s, int e, int[] nums) {
if (s == e) {// 如果 start==end 那递归结束,当前区间只有一个数(节点),segmentTree[node] = nums[s]
segmentTree[node] = nums[s];
return;
}
int m = s + (e - s) / 2;//取中点(防止溢出)
build(node * 2 + 1, s, m, nums);//构建左子树
build(node * 2 + 2, m + 1, e, nums);//构建右子树
segmentTree[node] = segmentTree[node * 2 + 1] + segmentTree[node * 2 + 2];//当前数节点
}
}

更新节点主要是递归+二分(根据下标)查找指定的节点,将其替换为最新值,递归更新其父节点即可

public void update(int index, int val) {
change(index, val, 0, 0, n - 1);
}
private void change(int index, int val, int node, int s, int e) {
if (s == e) {//递归结束(查找到对应的下标)
segmentTree[node] = val;//更新最新值
return;
}
int m = s + (e - s) / 2;//获取中点
if (index <= m) {//如果m>=index说明在左边区间
change(index, val, node * 2 + 1, s, m);
} else {//否则在右边区间
change(index, val, node * 2 + 2, m + 1, e);
}
//更新父节点
segmentTree[node] = segmentTree[node * 2 + 1] + segmentTree[node * 2 + 2];
}

根据指定的下标 left、right 求该区间的数据和。

public int sumRange(int left, int right) {
return range(left, right, 0, 0, n - 1);
}

/**
* 范围求和 range 函数
* 给定区间 [left,right] 时,我们将区间 [left,right] 拆成多个结点对应的区间。
* 如果结点 node 对应的区间与 [left,right] 相同,可以直接返回该结点的值,即当前区间和。
* 如果结点 node 对应的区间与 [left,right] 不同,设左子结点对应的区间的右端点为 m,那么将区间 [left,right] 沿点 m 拆成两个区间,分别计算左子结点和右子结点。
* 我们从根结点开始递归地拆分区间 [left,right]
*
* @param left 目标范围左索引
* @param right 目标范围右索引
* @param node 当前索引
* @param s 开始左区间
* @param e 结束右区间
* @return 返回[left,right]数值之和
*/
private int range(int left, int right, int node, int s, int e) {
//(递归结束)没有重叠范围,返回当前节点
if (left == s && right == e) return segmentTree[node];
int m = s + (e - s) / 2;//获取中点
if (right <= m) {//如果right <= m说明[left,right]在全部左区间
return range(left, right, node * 2 + 1, s, m);
} else if (left > m) {//如果right <= m说明[left,right]在全部在右区间
return range(left, right, node * 2 + 2, m + 1, e);
} else {//否则[left,right]有部分在左区间有部分在右区间,把两者相加即可
return range(left, m, node * 2 + 1, s, m) + range(m + 1, right, node * 2 + 2, m + 1, e);
}
}

惰性传播 (Lazy Propagation in Segment Tree)

区间求和 (Sum of given range)

带节点更新的范围最大查询 (Range Maximum Query with Node Update)

高效实现 (Segment Tree Efficient Implementation)

class NumArray {
//线段数(叶子节点都是数组的元素)
//[1,3,5] [1,3,5,7] ......
// 9 16
// / \ / \
// 4 5 4 12
// / \ / \ / \
// 1 3 1 3 5 7 ......
// n=3 node=6 n=4 node=7 n=5 node=9 n=6 node=12 node<=2*n
int[] tree;
int n;
public NumArray(int[] nums) {
n = nums.length;
tree = new int[2*n];
buildTree(nums);
}
//构建线段树
public void buildTree(int[] nums){
//nums=[1,3,5,7] tree = [0,0,0,0,1,3,5,7]
//将所有叶子节点放置在[n,2n)数组中
for (int i = n, j = 0; i < 2 * n; i++, j++)tree[i] = nums[j];
//tree = [0,16,4,12,1,3,5,7]
//把父节点放置在[1,n)数组中 , tree[i] = tree[i * 2] + tree[i * 2 + 1]
for (int i = n - 1; i > 0; --i)tree[i] = tree[i * 2] + tree[i * 2 + 1];
}
//更新某个下标同时需要更新其父节点的值
public void update(int index, int val) {
index+=n;//下标的位置,n+下标
tree[index] = val;
//更新父亲节点,因为tree[i] = tree[i * 2] + tree[i * 2 + 1];所以index如果是奇数则为右子树,index是偶数则为左子树
while(index>0){
int left = index;
int right = index;
if(index % 2 == 0) right = index+1;
else left = index - 1;
//更新父节点值
tree[index / 2] = tree[left] + tree[right];
//更新下标
index /= 2;
}

}
//求和只需要获取其父节点
public int sumRange(int left, int right) {//left,right为下标
//确认在数组中的下标
left+=n;
right+=n;
int ret = 0;
//[1,3,5,7]
while(left<=right){
//1.如果left是左子树可以直接取根节点,如果是右子树那只取右节点
if(left % 2 == 1 ){
ret+=tree[left];
left++;
}
//2.如果right是右子树可以直接取根节点,如果是左子树那只取左节点
if(right % 2 == 0 ){
ret+=tree[right];
right--;
}
left/=2;
right/=2;
}
return ret;
}
}

采用位运算加速

package io.example.algorithm.tree;

//线段数(叶子节点都是数组的元素)
// [1,3,5] [1,3,5,7] ......
// 9 16
// / \ / \
// 4 5 4 12
// / \ / \ / \
// 1 3 1 3 5 7 ......
// n=3 node=5 n=4 node=7 n=5 node=9 n=6 node=12 node<=2*n
public class SegmentTree {

// 设置数组的最大值
int N = 100000;
int n; //节点个数
// 设置数组的最大值
int[] tree = new int[2 * N];

// |--------|
// | +-+
// 构建线段树 v | |
//[1,3,5,7] -> [0,0,0,0][1,3,5,7] -> [0,16,4,12][1,3,5,7]
// ^ | |
// | +-+
// |------|
void buildTree(int[] arr) {
// [n,2n)按照顺序存储节点
for (int i = 0; i < n; i++) tree[n + i] = arr[i];
// 构建父节点(0,n) 父节点等于 tree[i] = tree[i * 2] + tree[i * 2 + 1];
for (int i = n - 1; i > 0; --i) tree[i] = tree[i << 1] + tree[i << 1 | 1];
}

/**
* 更新树节点
* @param p 下标
* @param value 值
*/
void updateTreeNode(int p, int value) {
// 直接更新值
tree[p + n] = value;
p = p + n;
// 同时需要更新其父亲节点
for (int i = p; i > 1; i = i / 2) tree[i / 2] = tree[i] + tree[i ^ 1];
for (int i = p; i > 1; i >>= 1) tree[i >> 1] = tree[i] + tree[i ^ 1];
}

// function to get sum on
// interval [l, r)
int query(int l, int r) {
int res = 0;
// loop to find the sum in the range
// for (l += n, r += n; l < r; l = l / 2, r = r / 2) {
for (l += n, r += n; l < r; l >>= 1, r >>= 1) {
if ((l & 1) > 0) res += tree[l++];
if ((r & 1) > 0) res += tree[--r];
}
return res;
}

public static void main(String[] args) {
SegmentTree segmentTree = new SegmentTree();
int[] a = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
// n is global
segmentTree.n = a.length;
// 构建数
segmentTree.buildTree(a);
// print the sum in range(1,2)
// index-based
System.out.println(segmentTree.query(1, 3));
// modify element at 2nd index
segmentTree.updateTreeNode(2, 1);
// print the sum in range(1,2)
// index-based
System.out.println(segmentTree.query(1, 3));
}
}

https://cp-algorithms.com/data_structures/segment_tree.html
可视化 :https://visualgo.net/zh/segmenttree?slide=1
给定范围之和:https://www.geeksforgeeks.org/segment-tree-set-1-sum-of-given-range/?ref=gcse
在线段树中懒标记:https://www.geeksforgeeks.org/lazy-propagation-in-segment-tree/?ref=gcse
线段树的高效实现:https://www.geeksforgeeks.org/segment-tree-efficient-implementation/?ref=lbp


Recommend

  • 23

    大家好,欢迎阅读周三 算法数据结构 专题,今天我们来聊聊一个新的数据结构,叫做线段树。 线段树这个数据结构很多人可能会有点蒙,觉得没有听说过,但是它非常非常有名,尤其是在竞赛圈,可以说是 竞赛圈的必备技能

  • 7

    Codeforces620E New Year Tree(dfs序+线段树 区间更新)March 07, 2016题目链接 题意:一棵树,每个节点有一个颜色,现在有两种操作,一种是将一棵子树所有节点置为一种...

  • 4
    • halfrost.com 3 years ago
    • Cache

    线段树 Segment Tree 实战

    线段树 Segment tree 是一种二叉树形数据结构,1977年由 Jon Louis Bentley 发明,用以存储区间或线段,并且允许快速查询结构内包含某一点的所有区间。 一个包含 n 个区间的线段树,空间复杂度为 O(n) ,查询的时间复杂度则为O(logn+k) ,其中 k 是符...

  • 7
    • www.guofei.site 3 years ago
    • Cache

    【数据结构5】Tree实现

    定义二叉树 这里除了定义二叉树外,还实现了以下功能: # Definition for a binary tree node. class...

  • 1
    • maskray.me 3 years ago
    • Cache

    Segment tree | MaskRay

    Segment tree In research papers, a segment tree refers to a tree data structure allowing retrieving a list of segments which contain the given point. In competitive programming, the name "segment tree" usually r...

  • 9
    • codeforces.com 2 years ago
    • Cache

    Need help with segment tree.

    ChairMan's blog

  • 6
    • www.geeksforgeeks.org 2 years ago
    • Cache

    Segment Tree | Set 2 (Range Minimum Query)

    Set 2 (Range Minimum Query)Skip to content

  • 5
    • codeforces.com 2 years ago
    • Cache

    [Tutorial] XOR Segment Tree

    Today's post continues my theme of niche but sometimes useful topics in CP. This idea was brought to my attention by magnus.hegdahl, so thanks...

  • 6
    • codeforces.com 1 year ago
    • Cache

    Push-Free Segment Tree

    Figure 1: TiffanyA pdf version of this text could be found

  • 13

    Having trouble in solving segment tree problem. Having trouble in solving segment tree problem. ...

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK