segment tree

WHAT& WHY

線段樹是一種二叉樹,但並一定是完全二叉樹(如最後的8~10部分), 直觀來說就是把一段區間分成儘可能多的部分,最終劃分成每個點佔一段。其優點是應用範圍廣,易擴展:任何具有區間加性的性質都能用線段樹維護。

在線段樹種,對於任意一個節點[a,b),它的左子節點的區間為[a,(a+b)/2), 右子節點的區間為[(a+b)/2, b)。當然還有另一種表示方法:對於任意一個節點[a,b],其左子節點為[a,(a+b)/2], 右子節點為[(a+b)/2+1, b]。兩者明顯是一回事,在這裡我們用第一種表示方法(如圖)。

HOW

又到了喜聞樂見的HOW部分。在這裡我們來分析一個求和的二叉樹。

1.如何建樹?

答:使用遞歸遍歷。

對於每一次遞歸,我們需要三個參數:當前節點編號node,當前結點的左範圍l和右範圍r。如果當前節點已經是葉子結點,將原數組中的值放置到這裡。否則遞歸遍歷左子樹和右子樹,當前結點的值為兩個子數之和。

void build(int node, int l, int r) { if (l + 1 == r) { tree[node] = a[l]; } else { int mid = (l + r) / 2; build(node * 2, l, mid); build(node * 2 + 1, mid, r); tree[node] = tree[node * 2] + tree[node * 2 + 1]; }}

2.如何詢問某個特定區間的屬性(在這裡是求和)

答:遞歸遍歷。

其中node 為當前節點編號,l為當前左範圍,r為當前右範圍,ql為目標左範圍,qr為當前右範圍,函數的返回值為sum,也就是當前累積的和。如果目標範圍已經包括了當前遞歸範圍那麼直接返回和,否則繼續分左右兩半遞歸。

int query(int node, int l, int r, int ql, int qr) { int mid = (l + r) / 2; int sum = 0; if (l >= ql && r <= qr) { sum += tree[node]; } else { if (ql < mid) { sum += query(node * 2, l, mid, ql, qr); } if (qr > mid) { sum += query(node * 2 + 1, mid, r, ql, qr); } } return sum;}

3.如何更新某個節點的值

答:當然還是遞歸遍歷啊,都是套路....

首先往下搜到底,改變葉子結點的值,再向上改變所有包含此區間的區間的值。。

int update(int node, int l, int r, int x, int value) { if (l + 1 == r) { tree[node] = value; } else { int mid = (l + r) / 2; if (x < mid) { update(node * 2, l, mid, x, value); } else { update(node * 2 + 1, mid, r, x, value); } } tree[node] = tree[node * 2] + tree[node * 2 + 1];}

來同一發例題~

POJ 2352: 2352 -- Stars

題解: github.com/Prionantha/O

還有另一種使用樹狀數組的解法: github.com/Prionantha/O

樹狀數組筆記:知乎專欄

第二次非自願裝逼..


推薦閱讀:

誰能推薦幾本關於人工智慧的書籍?
以目前的數論水平,計算機運算速度需要達到什麼數量級才有可能破解RSA演算法?
混合硬碟怎麼裝系統呢?
你碰到過的最難調試的 Bug 是什麼樣的?
有哪些(可以是出於實驗目的的)很特別的操作系統?

TAG:算法 | 计算机 |