自定義線段樹(區間樹)
阿新 • • 發佈:2019-01-03
通過學習自定義線段樹(區間樹),瞭解線段樹這一資料結構。
線段樹首先是平衡二叉樹。
用例:查詢一個區間[i,j]的最大值,最小值,或者區間數字和等。
實質:基於區間的統計查詢。
為什麼用線段樹:
|
使用陣列實現 |
使用線段樹 |
更新 |
O(n) |
O(log n) |
查詢 |
O(n) |
O(log n) |
使用滿二叉樹的陣列來表示線段樹,需要用4n的靜態空間。
包結構:
Merger.java介面:
package SegmentTree;
/**
* @author xiaohua
*
* @param <E>
*/
public interface Merger<E> {
E merger(E a, E b);
}
SegmentTree.java:
package SegmentTree; public class SegmentTree<E> { private E[] tree;//底層使用完全二叉樹的陣列實現線段樹 private E[] data;//複製建構函式傳入的陣列 private Merger<E> mer;//表示對區間的操作 /** * 帶參建構函式 */ public SegmentTree(E[] arr,Merger<E> mer) { this.mer=mer; 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,arr.length-1); } /** * 返回線段樹的大小 * @return */ public int getSize() { return data.length; } /** * 獲取指定索引的元素 * @param index * @return */ public E get(int index){ if(index < 0 || index >= data.length) { throw new IllegalArgumentException("索引不合法."); } return data[index]; } /**輔助方法 * 返回完全二叉樹的陣列表示中,一個索引所表示的元素的左孩子節點的索引 * @param index * @return */ private int leftChild(int index) { return 2*index+1; } /**輔助方法 * 返回完全二叉樹的陣列表示中,一個索引所表示的元素的右孩子節點的索引 * @param index * @return */ private int rightChild(int index) { return 2*index+2; } /** * 在treeIndex的位置建立表示區間[l...r]的線段樹 * @param treeIndex * @param l * @param r */ private 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; buildSegmentTree(leftTreeIndex,l,mid); buildSegmentTree(rightTreeIndex,mid+1,r); tree[treeIndex]=mer.merger(tree[leftTreeIndex],tree[rightTreeIndex]); } /** * 返回區間[queryL, queryR]的值 * @param queryL * @param queryR * @return */ public E query(int queryL,int queryR) { if(queryL < 0 || queryL >= data.length || queryR < 0 || queryR >= data.length || queryL > queryR) { throw new IllegalArgumentException("索引不合法."); } return query(0, 0, data.length - 1, queryL, queryR); } /** * 在以treeIndex為根的線段樹中[l...r]的範圍裡,搜尋區間[queryL...queryR]的值 * @param treeIndex * @param l * @param r * @param queryL * @param queryR * @return */ 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; // treeIndex的節點分為[l...mid]和[mid+1...r]兩部分 int leftTreeIndex=leftChild(treeIndex); int rightTreeIndex = rightChild(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 mer.merger(leftResult, rightResult); } /** * 將index位置的值,更新為e * @param index * @param e */ public void set(int index, E e){ if(index < 0 || index >= data.length) throw new IllegalArgumentException("索引不合法"); data[index] = e; set(0, 0, data.length - 1, index, e); } /** * 在以treeIndex為根的線段樹中更新index的值為e * @param treeIndex * @param l * @param r * @param index * @param 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; // treeIndex的節點分為[l...mid]和[mid+1...r]兩部分 int leftTreeIndex = leftChild(treeIndex); int rightTreeIndex = rightChild(treeIndex); if(index >= mid + 1) set(rightTreeIndex, mid + 1, r, index, e); else // index <= mid set(leftTreeIndex, l, mid, index, e); tree[treeIndex] = mer.merger(tree[leftTreeIndex], tree[rightTreeIndex]); } /** * 重寫Object的toString方法 */ @Override public String toString() { StringBuilder sb=new StringBuilder(); sb.append('['); for(int i=0;i<tree.length;i++) { if(tree[i]!=null) { sb.append(tree[i]); }else { sb.append("null"); } if(i!=tree.length-1) { sb.append(", "); } } sb.append(']'); return sb.toString(); } }
Test.java測試類:
package SegmentTree; public class Test { public static void main(String[] args) { Integer[] nums= {-2,0,3,-5,2,-1}; SegmentTree<Integer> segTree=new SegmentTree<>(nums,new Merger<Integer>() { //定義區間操作為計算區間數字的和 @Override public Integer merger(Integer a, Integer b) { return a+b; } }); System.out.println(segTree); System.out.println(segTree.query(0, 2)); System.out.println(segTree.query(2, 5)); System.out.println(segTree.query(0, 5)); } }
控制檯輸出:
[-3, 1, -4, -2, 3, -3, -1, -2, 0, null, null, -5, 2, null, null, null, null, null, null, null, null, null, null, null]
1
-1
-3