线段树又名区间树(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(); } }