線段樹是什麼東西
一句話定義
很簡單,線段樹這東西就是爲了節省在修改數組後,爲了簡便的獲得區間值。
如數組
int [] List={1,2,3,4}; 很顯然 ,正常人的思維是 要獲取那一段的值,就把這一段加起來 如 要獲取區間
[2,3] 就把list[2]+list[3]加起來。時間複雜度很顯然是O(n) 如果要頻繁的修改,多次修改,很顯然不可取。
線段樹的提出
很顯然,一個數組可以拆分爲任意個連續數組的集合。如果中一個數組中間,將該數組分開,那麼可以保證所有分開的
數組區間是連續的。
既:int mid= (right+left)/2 (left 爲一個數字左端開始的座標,right 爲該數組右端開始的座標),這裏不用說你肯定知道
需要查詢一個區間段的時候,形如[1-3]。如在list中查詢區間2->3的值。只需要選取部分拆分開的數組,既可知道
該區間的值。
爲什麼是部分?上面已經說明了,只要一個數組及其子數組是從數組中間分開的,那麼其區間段肯定是連續的。
[1,2,3,4]
左分[1,2] 右分[3,4]
左分[1] 右分[2] 左分[3] 右分[4]
很顯然,一個區間段,肯定是屬於線段樹一個區間到三個區間的,自己想想[0-3] ,[1-3],[2-3]的區別。
線段樹二分的原理近似於二叉樹,層次也只會有log2N層次。既每次查詢任意區間的時間複雜度爲Log2N。
線段樹的修改
按照上面的[1,2,3,4]圖,可以看出,修改任意一個元素後,會發生改變的肯定是他的超集。所以可以利用線段樹的
劃分性質,對線段樹進行dfs,到底後,線段樹節點所代表的值就是未被修改的值,將該節點修改爲新值。在層層返回
的過程中,代碼會自動的將線段樹路徑進行修改。修改時間複雜度等同於查詢複雜度,其實就相當於是查詢了一個區間
[k,k]的值
線段樹代碼- LeetCode315. 計算右側小於當前元素的個數
菜鳥肯定會對爲什麼線段樹數組的大小開爲4N,很疑惑。如果不是專門搞數學的,別去研究了,推導歸納起來有點複雜
還有就是如何證明 子節點的位置 leftNode=2*node +1 rightNode=2*node+2 自己動腦子想想,比賽考研都不考
我就不打證明了。
下面這個題,因爲題目特殊性不需要build函數,自己找個簡單題目練習一下就OK,其實builde就是update改一下。寫這個代碼主要看你的遞歸能力。
public class 計算右側小於當前元素的個數2 {
static int[] tree;
public List<Integer> countSmaller(int[] nums) {
int min = Integer.MAX_VALUE;
int max = Integer.MIN_VALUE;
for (int i = 0; i < nums.length; i++) {
min = Math.min(min, nums[i]);
max = Math.max(max, nums[i]);
}
tree = new int[4 * (max - min + 1)];
Stack<Integer> stack = new Stack<Integer>();
for (int i = nums.length - 1; i >= 0; i--) {
// 查詢右側
stack.push(query(min, max, min, nums[i] - 1, 0));
// 把該數字加入
update(min, max, 0, nums[i]);
}
List<Integer >list=new ArrayList<Integer>();
while(!stack.isEmpty())list.add(stack.pop());
return list;
}
public static void update(int start, int end, int node, int index) {
//錯誤就出在這裏
if (start == end&& end==index ) {
tree[node]+=1;
return;
}
int mid = (start + end) >> 1;
if (mid >= index) {
update(start, mid, node * 2+1, index);
} else {
update(mid + 1, end, node * 2 + 2, index);
}
tree[node] = tree[node * 2+1] + tree[node * 2 + 2];
}
public static int query(int start, int end, int l, int r, int node) {
if (l > r)
return 0;
// 如果查詢範圍內沒有,就直接返回
if (l > end || r < start)
return 0;
// 如果該區間已經被包含在線段樹內,直接返回
else if (l <= start && r >= end) {
return tree[node];
}
int mid = (start + end) >> 1;
int rValue = query(start, mid, l, r, node * 2+1);
int lValue = query(mid + 1, end, l, r, node * 2 + 2);
return rValue + lValue;
}
}