线段树 (存储区间)

本页使用了标题或全文手工转换
维基百科,自由的百科全书

线段树(英语:Segment tree)是一种二叉树形数据结构,1977年由乔恩·本特利发明[1],用以存储区间线段,并且允许快速查询结构内包含某一点的所有区间。

一个包含个区间的线段树,空间复杂度为,查询的时间复杂度则为,其中是符合条件的区间数量。

此数据结构亦可推广到高维度。

结构[编辑]

线段树是一个平衡的二叉树,它将每个长度不为1的区间划分成左右两个区间递归求解。令整个区间的长度为N,则其有N个叶节点,每个叶节点代表一个单位区间,每个内部结点代表的区间为其两个儿子代表区间的联集。这种数据结构可以方便的进行大部分的区间操作。

本处以一维的线段树为例。

线段树结构示意,其存储的线段显示在图片的下方。

令S是一维线段的集合。将这些线段的端点坐标由小到大排序,令其为。我们将被这些端点切分的每一个区间称为“单位区间”(每个端点所在的位置会单独成为一个单位区间),从左到右包含:

线段树的结构为一个二叉树,每个节点都代表一个坐标区间,节点N所代表的区间记为Int(N),则其需符合以下条件:

  • 其每一个叶节点,从左到右代表每个单位区间。
  • 其内部节点代表的区间是其两个儿子代表的区间之联集。
  • 每个节点(包含叶子)中有一个存储线段的数据结构。若一个线段S的坐标区间包含Int(N)但不包含Int(parent(N)),则节点N中会存储线段S。

实现[编辑]

C++[编辑]

在此以求出范围最小值作为示例

template <typename T>
class SegMinTree {
 public:
  // 新建一个最小值线段树用于处理[0, n)的数据
  SegMinTree(int n) : N(n), values_(4 * n), deltas_(4 * n) {}

  // 返回指定位置的数据
  T Get(int index) const {
    return GetRangeMin(index, index + 1);
  }

  // 将数据写入指定位置
  void Set(int index, T value) {
    IncrementRange(index, index + 1, value - Get(index));
  }

  // 返回区间上的最小值
  T GetRangeMin(int start, int end) const {
    return Query(FullSegment(), start, end);
  }

  // 对一段区间上的所有值加上同一个增量
  void IncrementRange(int start, int end, T delta) {
    Increment(FullSegment(), start, end, delta);
  }

 private:
  struct Segment {
    int id;
    int start;
    int end;

    bool Overlaps(int start, int end) const {
      return this->start < end && this->end > start;
    }

    bool IsIn(int start, int end) const {
      return start <= this->start && this->end <= end;
    }

    Segment Left() const {
      return {.id = id * 2, .start = start, .end = (start + end + 1) / 2};
    }

    Segment Right() const {
      return {.id = id * 2 + 1, .start = (start + end + 1) / 2, .end = end};
    }
  };

  Segment FullSegment() const {
    return {.id = 1, .start = 0, .end = N};
  }

  T Query(const Segment& segment, int start, int end) const {
    if (!segment.Overlaps(start, end)) {
      return std::numeric_limits<T>::max();
    }
    if (segment.IsIn(start, end)) {
      return values_[segment.id];
    }
    // 处理部分重合的情况
    T children_value = std::min(Query(segment.Left(), start, end), Query(segment.Right(), start, end));
    return deltas_[segment.id] + children_value;
  }

  // 返回segment里面新的最小值(跟[start, end)无关).
  T Increment(const Segment& segment, int start, int end, T delta) {
    if (!segment.Overlaps(start, end)) {
      return values_[segment.id];  // 没有改变
    }
    if (segment.IsIn(start, end)) {
      values_[segment.id] += delta;
      deltas_[segment.id] += delta;
      return values_[segment.id];
    }
    // 处理部分重合的情况
    T value = std::min(Increment(segment.Left(), start, end, delta),
                       Increment(segment.Right(), start, end, delta));
    value += deltas_[segment.id];
    values_[segment.id] = value;
    return value;                          
  }

  const int N;
  std::vector<T> values_;
  std::vector<T> deltas_;  // deltas_[id] 里的值只用于子结点。
};

C[编辑]

在此以求出范围最小值作为示例

#include <bits/stdc++.h>
using namespace std;
#define int long long
int n , q , seg[800005] = {0} , x , a , b , arr[200005];
 
//初始化線段樹
void build(int id , int l , int r)
{
	if(l == r){
		seg[id] = arr[l];
		return;
	}
	int mid = (l+r)>>1;
	build(2*id,l,mid);
	build(2*id+1,mid+1,r);
	seg[id] = min(seg[2*id],seg[2*id+1]);
	return;
}
 
//從線段樹中提取資訊
int query(int id , int l , int r , int ql , int qr)
{
	if(qr < l || ql > r)return 1e9;
	if(ql <= l && r <= qr)return seg[id];
	int mid = (l+r)>>1;
	return min(query(2*id,l,mid,ql,qr),
			   query(2*id+1,mid+1,r,ql,qr));
}
 
//更新線段樹
void update(int id , int l , int r , int k , int u)
{
	if(l==r){
		seg[id] = u;
		return;
	}
	int mid = (l+r)>>1;
	if(k <= mid)update(2*id,l,mid,k,u);
	else update(2*id+1,mid+1,r,k,u);
	seg[id] = min(seg[2*id] , seg[2*id+1]);
	return;
}

signed main()
{
	//輸入陣列大小 提問數量
	cin >> n >> q;
	for(int i = 1 ; i <= n ; i++)cin >> arr[i];
	build(1,1,n);
	for(int i = 0 ; i < q ; i++)
	{
		//輸入 1 a b 代表將第a個數改為b
		//輸入 2 a b 代表求[a,b]中的最小值
		cin >> x >> a >> b;
		if(x == 1)update(1,1,n,a,b);
		else if(x == 2)cout << query(1,1,n,a,b) << "\n";
	}
	return 0;
}

参考资料[编辑]

  1. ^ de Berg 等人 2000,p.229)