本文对线段树做总结,主要给出基本概念以及相应的实际问题。
主要参考了这篇博客[一步一步理解线段树]
基本概念
线段树
线段树,类似区间树,它在各个节点保存一条线段(数组中的一段子数组),主要用于高效解决连续区间的动态查询问题,由于二叉结构的特性,它基本能保持每个操作的复杂度为O(logn)。
线段树的每个节点表示一个区间,子节点则分别表示父节点的左右半区间,例如父亲的区间是[a,b],那么(c=(a+b)/2)左儿子的区间是[a,c],右儿子的区间是[c+1,b]。
例子
下面我们从一个经典的例子来了解线段树,问题描述如下:从数组arr[0…n-1]中查找某个数组某个区间内的最小值,其中数组大小固定,但是数组中的元素的值可以随时更新。
一般解法
对这个问题一个简单的解法是:遍历数组区间找到最小值,时间复杂度是O(n),额外的空间复杂度O(1)。当数据量特别大,而查询操作很频繁的时候,耗时可能会不满足需求。
另一种解法:
另一种解法:使用一个二维数组来保存提前计算好的区间[i,j]内的最小值,那么预处理时间为O(n^2),查询耗时O(1), 但是需要额外的O(n^2)空间,当数据量很大时,这个空间消耗是庞大的,而且当改变了数组中的某一个值时,更新二维数组中的最小值也很麻烦。
我们可以用线段树来解决这个问题:
复杂度如下:
- 预处理耗时O(n),因为对于含有N元素的线段树而言,大概要生成2n个节点。
- 查询、更新操作O(logn),这是优于O(N)的。
- 需要额外的空间O(n)
操作
创建线段树
题目:[创建线段树-I]
/**
* Definition of SegmentTreeNode:
* class SegmentTreeNode {
* public:
* int start, end;
* SegmentTreeNode *left, *right;
* SegmentTreeNode(int start, int end) {
* this->start = start, this->end = end;
* this->left = this->right = NULL;
* }
* }
*/
class Solution {
public:
/**
*@param start, end: Denote an segment / interval
*@return: The root of Segment Tree
*/
SegmentTreeNode * build(int start, int end) {
// write your code here
if( start > end ) return NULL;
SegmentTreeNode* root = new SegmentTreeNode( start, end );
if(start == end) return root;
int mid = (start+end)/2;
root->left = build( start, mid );
root->right = build( mid + 1, end );
return root;
}
};
题目:[创建线段树-II]
/**
* Definition of SegmentTreeNode:
* class SegmentTreeNode {
* public:
* int start, end, max;
* SegmentTreeNode *left, *right;
* SegmentTreeNode(int start, int end, int max) {
* this->start = start;
* this->end = end;
* this->max = max;
* this->left = this->right = NULL;
* }
* }
*/
class Solution {
public:
/**
*@param A: a list of integer
*@return: The root of Segment Tree
*/
SegmentTreeNode * build(vector<int>& A) {
// write your code here
int sz = A.size();
if(!sz) return NULL;
return build(A, 0, sz-1 );
}
private:
SegmentTreeNode* build(vector<int>& A, int start, int end ){
if(start > end) return NULL;
SegmentTreeNode* root = new SegmentTreeNode(start, end, 0);
if( start == end ){
root->max = A[start];
}
else{
int mid = (start + end)/2;
root->left = build(A, start, mid);
root->right = build(A, mid+1, end);
root->max = std::max( root->left->max, root->right->max );
}
return root;
}
};