Segment Tree介绍与使用

首先,看如下事例:

假设有数组 A[0..n-1],我们可以进行如下操作:

(1)求范围[ l, r ]间元素之和。0<=l<=r<=n-1;记为 Query 操作;

(2)更新某位置 i 处值,A[i] = new_val, 0<=i<=n-1;记为 Update 操作;

简单直观方法,就是遍历数组,可知此时(1)时间复杂度O(n),(2)时间复杂度O(1)。这种方法对于多更新操作的有利。

另一方法,创建新数组B,其中B[i]=SUM(A[0]..A[i]),0<=i<=n-1。此时(1)时间复杂度O(1),(2)时间复杂度O(n)。这种方法对于多查询操作的有利。

那么当两类操作差不多的情况下。有没有好的方法的呢?这里就引出了Segment Tree 的数据结构,通过使用它,我们可以将两类操作的时间复杂度都控制在O(lgn)。

Segment Tree 的表示描述如下:

1.叶节点中存储了数组中元素。

2.中间节点中存储了表述部分叶节点 Merge 后节点,不同使用场景下,Merge含义不同。上例中,Merge的含义就是叶节点求和。

Segment Tree 数据结构的实现,通常以数组方式来组织表示,对于下标为 i 的节点,其左孩子下标为 2*i +1,右孩子下标为 2*i +2。父节点的下标为 。

例子:数组 { 1,3, 5, 7, 9, 11 },更新位置值和查询任意区间和。建立 Segment Tree 如下,


如何针对给定数组建立 Segment Tree?

对于数组段 arr[0...n-1],每次我们将当前段分为二等分。递归地进行下去,直到当前段长度为1,处理每段时我们在相应节点存储和值。

建立Segment Tree每层(除了叶节点层)都是满的,也就是该树可以看着是完全二叉树(完全二叉树 + n个叶节点),所以Segment 

Tree总节点数目为 2*n + 1。(n个叶节点+ n-1个中间节点)。树的高度为 \lceil \log_2{n}  \rceil。由于以数组来表示树结构,需要开辟的数组的大小为

2 * 2 ^{\lceil \log_2{n}  \rceil} - 1

查询给定范围的和?

树建立后,可以使用如下算法来获取范围和。

int getSum( node, l, r)

{

if range of node is within l and r

return value in node

else if range of node is completely outside l and r

return 0

else

return getSum(node's left child, l ,r ) + getSum(node's right child, l , r)

}

如何更新值?

同建树算法,和查询算法一样,递归的更新。对于给定要更新值的下标 i,令 diff = new_val - old_val。从根节点开始,对于所有 i 在节点的 range of node。

node值加diff。不在节点代表range中, 不进行更新。

void upDate(node , i, diff)

{

if i within range of node

node.value = node.value + diff;

upDate( node's left child, i, diff );

upDate( node's right child, i, diff );

}

实现代码如下:

int getMid(int s, int e) {  returns + (e -s)/2;  }

int constructSTUtil(int arr[], int ss, int se, int* st, int si)
{
    if(ss == se)
    {
        st[si] = arr[ss];
        returnarr[ss];
    }
 
    intmid = getMid(ss, se);
    st[si] =  constructSTUtil(arr, ss, mid, st, si*2+1) + constructSTUtil(arr, mid+1, se, st, si*2+2);
    returnst[si];
}
 
int* constructST(int arr[], int n)
{
    int x = (int)(ceil(log2(n)));//Height of segment tree
    int max_size = 2*(int)pow(2, x) - 1; //Maximum size of segment tree
    int* st = new int[max_size];
 
    constructSTUtil(arr, 0, n-1, st, 0);
    return st;
} 

 
int getSumUtil(int* st, int ss, int se, int qs, int qe, int index)
{
    if(qs <= ss && qe >= se) return st[index];
    if(se < qs || ss > qe) return 0;
 
    int mid = getMid(ss, se);
    return getSumUtil(st, ss, mid, qs, qe, 2*index+1) + getSumUtil(st, mid+1, se, qs, qe, 2*index+2);
}
 
void updateValueUtil(int* st, int ss, int se, int i, int diff, int index)
{
    if(i < ss || i > se) return;

    st[index] = st[index] + diff;
    if(se != ss)
    {
        int mid = getMid(ss, se);
        updateValueUtil(st, ss, mid, i, diff, 2*index + 1);
        updateValueUtil(st, mid+1, se, i, diff, 2*index + 2);
    }
}
 
void updateValue(int arr[], int* st, int n, int i, int new_val)
{
    if(i < 0 || i > n-1)
    {
        printf("Invalid Input");
        return;
    }
 
    int diff = new_val - arr[i];
    arr[i] = new_val;
 
    updateValueUtil(st, 0, n-1, i, diff, 0);
}
 
int getSum(int* st, int n, int qs, int qe)
{
    if(qs < 0 || qe > n-1 || qs > qe)
    {
        printf("Invalid Input");
        return-1;
    }
    return getSumUtil(st, 0, n-1, qs, qe, 0);
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值