Segment Tree介绍与使用

来源:互联网 发布:网络版点歌软件 编辑:程序博客网 时间:2024/06/07 02:34

首先,看如下事例:

假设有数组 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);}

原创粉丝点击