線段樹
-
線段樹是一種二叉搜索樹,與區間樹相似,它將一些區間劃分爲一些單元區間,每一個單元區間對應線段樹中的一個葉子節點,對於線段樹中的每一個非葉子節點[a,b],它的左兒子表示的區間爲[a,(a+b)/2],右兒子表示的區間爲[(a+b)/2+1,b]。因此線段樹是平衡二叉樹,最後的子節點數目爲N,即整個線段區間的長度。使用線段樹可以快速的查找某一個節點在若干條線段中出現的次數,時間複雜度爲O(logN)。而未優化的空間複雜度爲2N,因此有時需要離散化讓空間壓縮。
-
既不是滿二叉樹,也不是完全二叉樹
-
如果區間有n個元素,那麼我們的線段樹不考慮添加元素,即區間固定,使用4n的靜態空間即可
-
線段樹是有限個元素的數組進行區間表達,而不是我們意義上的數軸的線段
-
代碼:
package 數據結構.SegmentTree; import javax.management.Query; import javax.swing.tree.TreeCellRenderer; public class SegmentTree<E> { private E[] data; private E[] tree; private Merger<E> merger; public SegmentTree(E[] arr,Merger<E> merger) { this.merger = merger; data = (E[])new Object[arr.length]; for (int i = 0; i < arr.length; i++) { data[i] = arr[i]; } tree = (E[])new Object[4*arr.length]; buildSegmentTree(0,0,data.length-1); } //再treeIndex的位置創建表示區間[l...r]的線段樹 private void buildSegmentTree(int treeIndex,int l,int r) { if(l==r) { tree[treeIndex] = data[l]; return; } int leftTreeIndex = leftChild(treeIndex); int rightTreeIndex = rigthChild(treeIndex); int mid = l+(r-l)/2; buildSegmentTree(leftTreeIndex,l,mid); buildSegmentTree(rightTreeIndex,mid+1,r); tree[treeIndex] = merger.merge(tree[leftTreeIndex], tree[rightTreeIndex]);//這裏是取線段中的最大值 } public int getSize() { return data.length; } public E get(int index){ if(index<0||index>=data.length) throw new IllegalArgumentException("index is illegal..."); return data[index]; } //查詢queryl,queryr的值,符合線段左右的值 public E query(int queryL,int queryR) { if(queryL<0||queryL>=data.length||queryR<0||queryR>=data.length||queryL>queryR) throw new IllegalArgumentException("Index is illegal."); return query(0,0,data.length-1,queryL,queryR); } private E query(int treeIndex, int l, int r,int queryL,int queryR) { if (l==queryL&&r==queryR) return tree[treeIndex]; int mid = l+(r-l)/2; int leftTreeIndex = leftChild(treeIndex); int rightTreeIndex = rigthChild(treeIndex); if(queryL>=mid+1) return query(rightTreeIndex,mid+1,r,queryL,queryR); else if(queryR<=mid) return query(leftTreeIndex,l,mid,queryL,queryR); E leftResult = query(leftTreeIndex,l,mid,queryL,mid); E rightResult = query(rightTreeIndex,mid+1,r, mid+1,queryR); return merger.merge(leftResult,rightResult); } //返回完全二叉樹的數組中,一個索引表示的元素的左孩子節點的索引 private int leftChild(int index){ return 2*index+1; } //返回完全二叉樹的數組中,一個索引表示的元素的右孩子節點的索引 private int rigthChild(int index){ return 2*index+2; } @Override public String toString() { StringBuilder res = new StringBuilder(); res.append('['); for (int i = 0; i < tree.length; i++) { if(tree[i]!=null) res.append(tree[i]); else res.append("null"); if(i != tree.length - 1) res.append(", "); } res.append("]"); return res.toString(); } } public interface Merger<E> { E merge(E a,E b); } package 數據結構.SegmentTree; public class Main { public static void main(String[] args) { Integer[] nums = {-2,0,3,-5,2,-1}; SegmentTree<Integer> segTree = new SegmentTree<>(nums,(a, b) -> a+b); System.out.println(segTree.query(2,5)); } }
-
爲線段樹添加更新操作:首先得找到對應的索引位置的節點值進行修改,同時,還要注意的是不僅僅需要對索引進行修改,同時還需要不斷的更新操作
//將index位置的值,更新爲e public void set(int index,E e) { if(index<0||index>=data.length) throw new IllegalArgumentException("Index is illegal"); data[index] = e; set(0,0,data.length-1,index,e); } //在以treeIndex爲根的線段樹中更新index的值爲e private void set(int treeIndex,int l,int r,int index,E e) { if(l==r) { tree[treeIndex] = e; return; } int mid = l+(r-l)/2; int leftTreeIndex = leftChild(treeIndex); int rightTreeIndex = rigthChild(treeIndex); if(index>=mid+1) set(rightTreeIndex,mid+1,r,index,e); else { set(leftTreeIndex,l,mid,index,e); } tree[treeIndex] = merger.merge(tree[leftTreeIndex],tree[rightTreeIndex]); }