資料結構: 線段樹
阿新 • • 發佈:2018-12-25
線段樹
為什麼使用線段樹
- 區間染色
- 有一面牆,長度為n,每次選擇一段牆進行染色
- M次操作後,我們可以可見多少種顏色
- M次操作後,在[i,j]區間能看見多少種顏色
涉及的操作
- 染色操作 (更新區間)
- 查詢操作 (查詢區間)
陣列 | 線段樹 | |
---|---|---|
染色操作 | O(N) | O(logN) |
查詢操作 | O(N) | O(logN) |
什麼是線段樹
-
以求和為例,每個節點就是儲存的每個區間的和
-
線段樹是平衡二叉樹,但不一定是滿二叉樹
-
堆也是平衡二叉樹
-
可以把線段樹看做滿二叉樹 : 將沒有區間的看做[]
-
若區間有n個元素,陣列需要
4n
的空間節點 -
使用陣列實現:
我們的線段樹不考慮新增元素,即區間固定,使用4n的靜態空間即可
線段樹的基本操作
線段樹的基本操作主要包括構造線段樹,區間查詢和區間修改
1. 構造線段樹
構造線段樹是一個遞迴的過程
C++實現:
// 返回完全二叉樹陣列表示中,一個索引表示的節點的左孩子的索引 int leftChild(int index) { return 2 * index + 1; } // 返回完全二叉樹陣列表示中,一個索引表示的節點的右孩子的索引 int rightChild(int index) { return 2 * index + 2; } // 在treeIdex位置建立區間表示[l...r]的線段樹 void buildSegmentTree(int treeIndex, int l, int r) { if (l == r) { tree[treeIndex] = data[l]; return; } int leftTreeIndex = leftChild(treeIndex); int rightTreeIndex = rightChild(treeIndex); // 遞迴呼叫 int mid = l+(r-l) / 2; // (l+r)/2 會有溢位問題 buildSegmentTree(leftTreeIndex, l, mid); buildSegmentTree(rightTreeIndex, mid + 1, r); // 給tree[]賦值,與業務相關,以求和為例 tree[treeIndex] = tree[leftTreeIndex] + tree[rightTreeIndex]; }
2.區間查詢
區間查詢指的是使用者指定一個區間,獲得這個區間的相關資訊,如區間的最大值,最小值,和等.
查詢的C++程式碼如下:
- 查詢一下是否能夠找到對應的區間
- 若沒有向下繼續遍歷
// 線段樹查詢 T query(int queryL, int queryR) { if (queryR >= 0 && queryR < data.size() && queryL >= 0 && queryL < data.size() && queryR> queryL) { return query(0,0,data.size()-1,queryL,queryR); } } T query(int treeIndex, int l, int r, int queryL, int queryR) { if (l == queryL&&r == queryR) { return tree[treeIndex]; } int leftTreeIndex = leftChild(treeIndex); int rightTreeIndex = rightChild(treeIndex); int mid = l + (r - l) / 2; if (queryL > mid) { return query(rightTreeIndex, mid + 1, r, queryL, queryR); } else if (queryR <= mid) { return query(leftTreeIndex, l, mid, queryL, queryR); } // [queryL..mid] + [mid+1,queryR] T leftRes = query(leftTreeIndex, l, mid, queryL, mid); T rightRes = query(rightTreeIndex, mid + 1, r, mid + 1, queryR); return leftRes + rightRes; }
3.區間更新
3.1 更新某個葉節點:
// 將index位置的值,更新為e
void set(int index, T val) {
if (index >= 0 && index < data.size()) {
data[index] = e;
// 更新線段樹,葉子節點
set(0, 0, data.size() - 1, index, val);
}
}
void set(int treeIndex, int l, int r, int index, T val) {
if (l == r) {
tree[treeIndex] = val;
return;
}
// 去找線段樹中葉子節點位置的索引在哪裡
int leftTreeIndex = leftChild(treeIndex);
int rightTreeIndex = rightChild(treeIndex);
int mid = l + (r - l) / 2;
if (index <= mid) {
set(leftTreeIndex, l, mid, index, val);
} else {
set(rightTreeIndex, mid + 1, r, index, val);
}
// 更新過程
tree[treeIndex] = tree[leftTreeIndex] + tree[rightTreeIndex];
}
3.2 更新某個區間:
採用懶惰更新,延遲標記
待續
LeetCode 307
class NumArray {
public:
vector<int> data;
vector<int> tree;
int size;
// 1. 構建線段樹
void buildSegmentTree(int treeIdx, int l, int r) {
if (l == r) {
tree[treeIdx] = data[l];
return;
}
int mid = l + (r - l) / 2;
int leftChildTreeIdx = treeIdx * 2+1;
int rightChildTreeIdx = treeIdx * 2 + 2;
buildSegmentTree(leftChildTreeIdx, l, mid);
buildSegmentTree(rightChildTreeIdx, mid + 1, r);
tree[treeIdx] = tree[leftChildTreeIdx] + tree[rightChildTreeIdx];
}
// 2.查詢線段樹
int query(int treeIdx, int l, int r, int queryL, int queryR) {
if (l == queryL&&r == queryR) {
return tree[treeIdx];
}
int mid = l + (r - l) / 2;
int leftChildTreeIdx = treeIdx * 2+1;
int rightChildTreeIdx = treeIdx * 2 + 2;
if (queryR <= mid) {
return query(leftChildTreeIdx, l, mid, queryL, queryR);
}
else if (queryL > mid) {
return query(rightChildTreeIdx, mid+1, r, queryL, queryR);
}
int leftResult = query(leftChildTreeIdx, l, mid, queryL, mid);
int rightResult = query(rightChildTreeIdx, mid + 1, r, mid+1, queryR);
return leftResult + rightResult;
}
int query(int queryL, int queryR) {
if (queryL >= 0 && queryL < size
&& queryR >= 0 && queryR < size
&& queryR >= queryL) {
return query(0, 0, size - 1, queryL, queryR);
}
return 0;
}
// 3.設定某個單個節點的值
void set(int treeIdx, int l, int r, int index, int val) {
if (l == r) {
tree[treeIdx] = val;
return;
}
int mid = l + (r - l) / 2;
int leftChildTreeIdx = treeIdx * 2+1;
int rightChildTreeIdx = treeIdx * 2 + 2;
if (index <= mid) {
set(leftChildTreeIdx, l, mid, index, val);
}
else {
set(rightChildTreeIdx, mid + 1,r, index, val);
}
tree[treeIdx] = tree[leftChildTreeIdx] + tree[rightChildTreeIdx];
}
void set(int index, int val) {
if (index >= 0 && index <size) {
data[index] = val;
set(0, 0, size - 1, index, val);
}
}
// 下面是題目的:
NumArray(vector<int> nums) {
// 1. 先拷貝整個陣列
if (nums.size() == 0) {
return;
}
size = nums.size();
data = vector<int>(nums.begin(), nums.end());
// 2. 建立這個線段樹
tree.resize(4 * size);
buildSegmentTree(0, 0, size - 1);
}
void update(int i, int val) {
set(i, val);
}
int sumRange(int i, int j) {
return query(i, j);
}
};