線段樹解析

2021-07-05 11:30:11 字數 4417 閱讀 9139

概念:

線段樹是一種特殊的結構,它每個節點記錄著乙個區間和這個區間的乙個計數,表示此區間出現的次數。

線段樹分為構造build部分,插入insert部分,以及查詢query部分。其主要的思想就是用空間換時間,來使一些特殊的問題的時間複雜度減少。比如對於一段空間或者乙個數字的出現次數,以線段樹來查詢可以使時間複雜度從乘法減少到log,具體的複雜度分析可以看參考資料1:

舉個栗子:

根節點為1-7區間的線段樹如下所示:

它build的規則是:根節點部分表示的區間是所有資料的[min,max]部分,令mid = (min + max) / 2,左孩子代表的區間是[min,mid],右孩子代表的區間是[mid + 1, max]。

葉子節點代表的是單個數字,即left == right。

那麼我們首先定義乙個資料結構,表示線段數中節點元素:

struct element//建構函式

};

然後,我們再對二叉樹本身寫乙個結構,其中包含了上面所述的元素:

struct node

};

那麼,線段數的build部分的函式如下:

node* build(int n, int m)

else

return ret;

}

接下來:線段數的insert插入規則如下:對於插入的區間[n,m],如果線段數節點當前表示區間[l,r]剛好覆蓋了[n,m],那麼當前節點的count新增上insert的計數部分。

否則,讓子節點去接收。

注意,只有當當前節點恰好符合時才會接收,否則不會接收。

舉個栗子,比如上面的1-7的線段樹,插入[1,4]計數為5的元素,先找根節點[1,7],比較後不時恰好符合,所以看是否需要對[1,4]分割,因為[1,4]都是在左孩子的結點部分,所以遞迴讓左孩子處理,左孩子[1,4]碰到[1,4]剛好符合,所以左孩子的count從0增加為5。

處理後線段樹如下:(其中紅色部分表示各結點的count)

如果再插入乙個[2,6],計數為4的結點:

0層遞迴:根節點[1,7]依舊處理不了,而[2,6]因為橫跨了根節點mid=4部分,所以把[2,6]分割成[2,4]和[5,6]兩部分分別交於左右孩子處理,

1層遞迴:左孩子[1,4]碰到[2,4]部分還是處理不了,它根據自己的mid=3把[2,4]分割成[2,2]和[3,4]部分,交於左右孩子處理

2層遞迴:左孩子[1,2]碰到[2,2]還是處理不了,它交於自己的右孩子處理

3層遞迴:右孩子[2,2]碰到[2,2]恰好,自己的count更新為4。完畢

2層遞迴:右孩子[3,4]碰到[3,4],更好,自己的count更新為4。完畢

1層遞迴:右孩子[5,7]碰到[5,6]處理不了,它交於自己的左孩子處理

2層遞迴:左孩子[5,6]碰到[5,6]恰好,自己的count更新為4。

所以再經過此步,當前的線段樹情況如下:

把**部分呈上:

void insert(int n, int m,int count, node *root)

int mid = (root->e->left + root->e->right) / 2;

if (n <= mid)

else if (m <= mid)

insert(n, m, count, root->lchild);

return;

} if (m >= mid + 1)

else if (n > mid)

insert(n, m, count, root->rchild);

return;

}}

接下來是查詢的部分:

它的規則如下:

對於乙個查詢的值value,如果不在根節點的區間範圍內,返回0。

如果在,則從根節點開始一直找到和此值相同的葉子節點處,返回途徑的各個節點的count的和。

如果查詢的是乙個範圍[vstart,vend],如果不在根節點的範圍內,返回0。

如果在,則從根節點開始一直找到和此區間相同的非葉子節點處,返回途徑的各個節點的count的和。

(其實,查詢乙個值也相當於乙個區間,不過前者是要找到葉子節點處,後者是找到非葉子節點處)

所以,如果是查詢3出現的次數,我們需要途徑[1,7],[1,4],[3,4],[3,3],把各個節點的count累加,3出現的次數就是9。

如下圖:

**如下:以下只有查詢乙個值的**,查詢乙個區間的類似。

int query(int value, node* root)

if (value == root->e->left && value == root->e->right)//如果到了葉子節點,返回當前值

return root->e->count;

int mid = (root->e->left + root->e->right) / 2;

if (value <= mid)

else if (value > mid)

}

我也舉個栗子:

有一座城市,經常下雨,我們找了幾個標誌性建築,假設它們的位置是一維的,每次下雨都有乙個範圍和持續時間,

現在給你m個標誌性建築的位置,和n次下雨的範圍以及持續時間,讓你輸出每次每個建築的所承受的總下雨量。

這個問題當然可以用普通的方法和資料結構解決,但是時間複雜度會很高,為o(n*m)。

可以用線段樹,我們用m個位置中的min和max來build乙個線段樹,然後用每次下雨的範圍和持續時間來insert,最後對於標示性建築的位置來進行query即可。

令len = max - min,空間複雜度是o(2*len),時間複雜度是o(n + m)*log(len)(包含n*log(len)的insert以及m*log(len)的query),當n很大時改進的效率提公升還是很大的。

總體**如下:(相信看過上面的build,insert和query以及圖示過後一定很好理解)

#include#include#include#include#include#include#includeusing namespace std;

//------------線段樹----------------by-apie陳小旭---------------

struct element//建構函式

};struct node

};node* build(int n, int m)

else

return ret;

}void insert(int n, int m,int count, node *root)

int mid = (root->e->left + root->e->right) / 2;

if (n <= mid)

else if (m <= mid)

insert(n, m, count, root->lchild);

return;

} if (m >= mid + 1)

else if (n > mid)

insert(n, m, count, root->rchild);

return; }}

int query(int value, node* root)

if (value == root->e->left && value == root->e->right)//如果到了葉子節點,返回當前值

return root->e->count;

int mid = (root->e->left + root->e->right) / 2;

if (value <= mid)

else if (value > mid)

}int main(void);//記錄的開始點

int end[num];//記錄的結尾點

int count[num];//記錄的持續時間

node* root = build(s, t);

for (int i = 0; i < num; ++i)

vectorv;//查詢的位置集合

for (int i = 0; i < v.size(); ++i)

return 0;

}

線段樹解析(一)

一 線段樹的應用場景 1.用於解決區間問題,例如求某個區間的和 最大值 最小值。2.支援的操作有單點修改 區間修改 區間查詢。二 線段樹 線段樹的核心思想在於 1.線段樹的每個節點預先維護好所對應區間所需要的資訊。2.對於一次查詢,將詢問區間 l,r 拆分到線段樹對應的節點上,通過合併這些節點已經處...

線段樹 02 構建線段樹

public inte ce merger 不能再縮小的基本問題是 對treeindex指向的節點的情況進行討論 public class segmenttree 在treeindex的位置建立表示區間 l.r 的線段樹 private void buildsegmenttree int treei...

線段樹 01 線段樹基礎

物理上 public class segmenttree public int getsize public e get int index 返回完全二叉樹的陣列表示中,乙個索引所表示的元素的左孩子節點的索引 private int leftchild int index 返回完全二叉樹的陣列表示中...