【图解算法】线段树 (SegmentTree)

线段树


 线段树是算法竞赛中常用的用来维护 区间信息 的数据结构。线段树可以在 O ( log ⁡ 2 N ) O(\log_{2}{N}) O(log2N) 的时间复杂度内实现单点修改、区间修改、区间查询等操作。

线段树的基本结构


为数组(假设下标从1开始):
a [ 5 ] = [ 1 , 2 , 3 , 4 , 5 ] a[5] = [{1,2,3,4,5}] a[5]=[1,2,3,4,5]
构造线段树如下图(采用堆式存储):

线段树示意

上述数组 D D D 用来保存线段树,由于采用的是堆式存储,因此 D [ i ] D[i] D[i] 的左右孩子结点分别为 D [ 2 × i ] , D [ 2 × i + 1 ] D[2\times i],D[2 \times i + 1] D[2×i],D[2×i+1]。不难发现上图有两种结点,银色结点括号表示该结点包含的数组 a a a 的区间, D [ i ] D[i] D[i] 的值表示 ∑ k = i j a [ k ] \sum_{k=i}^{j}a[k] k=ija[k]。且若区间两端点相等为 [ k , k ] [k,k] [k,k] D [ i ] = a [ k ] D[i] = a[k] D[i]=a[k]即值为绿色结点。

线段树的建立


由于树树递归定义的,因此其建立也是递归的:

void buildST(int left, int right, int p, vector<int>& D, vector<int> & a)
{
    if(left == right)
    {
        D[p] = a[left];
        return;
    }
    int mid = left + (right - left)/2;
    build(left, mid, p*2, D, a);
    build(mid+1, right, p*2+1, D, a);
    D[p] = D[p * 2] + D[p * 2 + 1];
}

线段树的区间查询


区间和:

// [left,right]为待查区间,[cl,cr]为当前区间,p 为当前节点编号,D 为线段树的存储数组
int getSum(int left, int right, int cl, int cr, int p, vector<int> &D)
{
	if(left <= cl && cr <= right) // 当前区间为待查区间的子集
		return D[p];
        // 划分区间,递归查询
	int mid = cl + (cr - cl)/2, sum = 0;
	if(left <= mid) // 与左区间有交集
		sum += getSum(left, right, cl, mid, p * 2, D);
	if(right > mid) // 与右区间有交集
		sum += getSum(left, right, mid+1, cr, p * 2 + 1, D);
	return sum;
}

区间修改:


[ c l , c r ] [cl,cr] [cl,cr]为当前区间,index为要修改的数组 a a a的下标, v a l val val为修改的最终值, p p p为当前节点编号。

void updateST(int cl, int cr, int index, int val, int p, vector<int>& D,vector<int>& a)
{
	if(cl == cr)
	{
		a[index] = val;
		D[p] = val;
		return;
	}
	else
	{
		int mid = cl + (cr - cl)/2;
		if(index >= cl && index <= mid)
			updateST(cl, mid, index, val, p * 2, D, a);
		else if(index > mid && index <= cr)
			updateST(mid + 1, cr, index, val, p * 2 + 1, D, a);
		D[p] = D[p * 2] + D[p * 2 + 1];
	}
}

此时如果将 a [ 1 ] a[1] a[1] 改成 6 6 6 ,则树变成(红色表示有修改的节点):

更新后的树


实验


int main()
{
	vector<int> D(10,0);
	vector<int> a(6);
	for(int i = 0; i < a.size();i++)
		a[i] = i;

	cout << "Building STree:" << endl;
	buildST(1, 5, 1, D, a);
	cout << "STree:";
	for(int i = 1;i < D.size();i++)
		cout << D[i] << " ";
	cout << endl;
	cout << "================================" << endl;
	cout << "Quary:(1,3)"<< endl;
	cout << getSum(1,3,1,5,1,D) << endl;
	cout << "================================" << endl;
	cout << "Update: a[1] = 6" << endl;
	updateST(1,5,1,6,1,D,a);
	cout << "STree:";
	for(int i = 1;i < D.size();i++)
		cout << D[i] << " ";
	cout << endl;
	cout << "================================" << endl;
	cout << "Quary:(1,3)"<< endl;
	cout << getSum(1,3,1,5,1,D) << endl;

}

结果


Result

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值