線段樹又名區間樹(segment tree),線段樹也是一種樹結構,該數據結構主要解決區間計算問題,葉子節點保存不可分割的最小區間的值,而非葉子節點保存的是當前區間的狀態,例如求區間最大值、最小值、求和等等;線段樹不是徹底二叉樹,但線段樹是一顆平衡二叉樹。segmentfault
說明:以上線段樹構建於具備8個元素的數組,目的在於求任意區間的元素之和。根節點保存的是整個數組區間的元素之和,其左節點表示[0,3]子區間的元素之和,其右節點表示[4,7]子區間的元素之和,依次類推,直到葉子節點表示的是單個元素的值。這個示例方便與計算區間之和,計算區間的值不須要遍歷整個數組,沿着根節點往葉子節點方向尋找合適的區間。時間複雜度在O(logn)級別。數組
/** * 融合接口 * @param <E> */ public interface Merger<E> { /** * 融合操做 * @param a 元素 * @param b 元素 * @return */ E merge(E a, E b); }
/** * 線段樹,基於靜態數組實現 * @param <E> */ public class SegmentTree<E> { /** * 線段樹容器,靜態數組 */ private E[] tree; private E[] data; /** * 融合器,處理元素融合邏輯 */ private Merger<E> merger; public SegmentTree(E[] arr,Merger<E> merger) { data = (E[]) new Object[arr.length]; this.merger = merger; for (int i = 0; i < arr.length; i++) { data[i] = arr[i]; } //初始化靜態數組容器爲4n,足夠容納線段樹結構 tree = (E[]) new Object[4 * arr.length]; //構建線段樹 buildSegmentTree(0, 0, arr.length - 1); } /** * 在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] = 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]; } /** * 返回徹底二叉樹的數組表示中,一個索引所表示的元素的左孩子節點的索引 * @param index 當前元素索引 * @return */ private int leftChild(int index) { return 2 * index + 1; } /** * 返回徹底二叉樹的數組表示中,一個索引所表示的元素的右孩子節點的索引 * @param index 當前元素索引 * @return */ private int rightChild(int index) { return 2 * index + 2; } /** * 返回區間[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("index is illegal"); } 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; 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 merger.merge(leftResult, rightResult); } /** * 將index位置的值,更新爲e * @param index * @param 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 * @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; int leftTreeIndex = leftChild(treeIndex); int rightTreeIndex = rightChild(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]); } @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(); } }