1. 程式人生 > >線段樹的建立,區間查詢,單點更新程式碼展示(以求和為例)

線段樹的建立,區間查詢,單點更新程式碼展示(以求和為例)

 線段樹這個東西真的夠奇怪的。。

線段數的節點表示的是一段區間的值,它要求區間之間必須滿足能夠相加的條件,不然不能用線段樹表示。

線段樹包括4中操作,包括建立,區間查詢,單點更新和區間更新,而區間更新是一個最難的部分,需要用到標記。。

這裡就說一下前三種相對簡單的操作。

在這之前,補充兩個知識點:

x>> 1 表示的是x/2;

x<<1 表示的是x*2;

|運算: 與偶數進行運算時必在基礎上+1,與奇數運算結果不變

建立:

建立的時候是從一開始的區間開始劃分,通過二分的思想,將一個大區間分成兩個小區間。當左區間等於右區間時,就可已將他看成單個點進行處理了。然後進行回溯,從下往上求出各個節點的值。

程式碼如下:

//建樹
void build (int l,int r,int re) //[l,r]表示節點表示的區間,re表示節點標號
{
    if(l==r)
    {
        tree[re]=a[l];
        return ;
    }
    int mid=(l+r)>>1;
    build (l,mid,re<<1);  //將區間二分
    build (mid+1,r,re<<1|1);
    //回溯求出根節點的值
    Pushup (re);
}

 上面有個Pushup,這個函式是給根節點賦值的

它的程式碼如下:

//更新根節點的值
void Pushup (int re) //求出根節點的值
{
   tree[re]=tree[re<<1]+tree[re<<1|1];
}

下一個就是單點更新了。

單點更新相當於就是對單個左右區間相等的節點的值進行修改 ,這實際上就是從上往下找要修改的單點,修改完之後進行回溯,

依次往上更新根節點的值。

程式碼如下:

//單點更新
void add (int loc,int data,int l,int r,int re) //loc表示更新的位置,data表示加上的值
{
    //表示找到這個單點了
    if(l==r)
    {
        tree[re]+=data;
    }
    int mid=(l+r)>>1;
    //如果小於中間值,那麼肯定在左子樹那裡,不小於的話就去右子樹找了
    if(loc<=mid)
        add (loc,data,l,mid,re<<1);
    else
        add (loc,data,mid+1,r,re<<1|1);
    //別忘記也要更新根節點的值哦
    Pushup(re);
}

 區間查詢:

這一部分就有點難了。

區間查詢的意思就是給定一個區間查詢這個區間的資訊。

讓我們想想, 如果節點所表示的區間完全包含在所要查詢的區間的話,那麼我們可以直接返回它的值就可以了。

如果超出了查詢範圍了的話, 我們就要進行取捨了。

( 1)如果節點的中點mid大於所要查詢的左區間left的話, 那麼必定該區間中點左邊必定有一部分是在查詢區間, 這樣我們就可以查詢節點的左子樹了。千萬不能也查詢右子樹,因為mid與right的相對大小不一定, 如果mid>right那麼就返回錯誤值了。 。

( 2)如果mid<right的話, 與上面同理,查詢右子樹。

程式碼如下:

//區間查詢
int que (int left,int right,int l,int r,int re) //[left,right]表示要查詢的區間
{
    if(l>=left&&r<=right)  //說明此時節點所代表的區間都在查詢區間之內,可以直接返回了
        return tree[re];
    int mid=(l+r)>>1;
    //通過ans記錄區間之和.
    int ans=0;
    //走到這裡,說明區間超出查詢範圍了,需要進行篩選
    //如果區間終點大於等於left,說明該區間左邊的需要納入查詢範圍
    if(mid>=left)
    {
       ans+=que (left,right,l,mid,re<<1);
    }
    //如果區間終點小於right,說明該區間的右邊需要納入查詢範圍
    if(mid<right)
    {
        ans+=que (left,right,mid+1,r,re<<1|1);
    }
    return ans;
}