9、线段树
为什么使用线段树
最经典的线段染色问题
什么是线段树
线段树中每个节点存储的均是一个区间内的数字总和
- 叶子节点均在最后一层
- 叶子节点不一定都在最后一层
平衡二叉树
平衡二叉树中:二叉树中最大的深度和最小的深度之差最大等于1
线段树是平衡二叉树
堆也是平衡二叉树
线段树的实现
- 线段树的理论基础(将线段树看成是一个满二叉树,将有n个元素的数组构造成一个含有4n个节点的线段树)
import java.util.Arrays;
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);
}
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];
}
private int leftChild(int index) {
return 2*index+1;
}
private int rightChild(int index) {
return 2*index+2;
}
private void buildSegmentTree(int treeIndex,int l ,int r) {
if( l == r) {
tree[treeIndex] = data[l];
return;
}
int leftTreeSegment = leftChild(treeIndex);
int rightTreeSegment = rightChild(treeIndex);
int mid = l +(r-l)/2;
buildSegmentTree(leftTreeSegment,l,mid);
buildSegmentTree(rightTreeSegment,mid+1,r);
tree[treeIndex] = merger.merge(tree[leftTreeSegment],tree[rightTreeSegment]);
}
@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 l,E r);
}
测试程序
//测试线段树
Integer nums[] = {-2,-1,0,1,2,3};
//参数中添加匿名内部类Merger,或者直接通过lambda表达式:(a,b) -> a + b
SegmentTree<Integer> segmentTree = new SegmentTree<>(nums, new Merger<Integer>() {
@Override
public Integer merge(Integer l, Integer r) {
return l +r;
}
});
System.out.println(segmentTree);
查询线段树
//查询线段树某个区间内的值[queryL,queryR]
public E query(int queryL,int queryR) {
if(queryL <0 || queryL >= data.length
|| queryR <0||queryR >= data.length) {
throw new IllegalArgumentException("Index is illggal");
}
return query(0,0,data.length -1,queryL,queryR);
}
//在以treeID为根的线段树中[l....r]的范围里,搜索区间[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 leftIndex = leftChild(treeIndex);
int rightIndex = rightChild(treeIndex);
if(queryL >= mid +1) {
return query(rightIndex,mid+1,r,queryL,queryR);
} else if(queryR <= mid) {
return query(leftIndex,l,mid,queryL,queryR);
}
E leftReuslt = query(leftIndex,l,mid,queryL,mid);
E rightReuslt = query(rightIndex, mid+1,r, mid+1,queryR);
return merger.merge(leftReuslt,rightReuslt);
}
leetcode线段树相关操作
- 区域和检索-不可变
[0,1,2,3,4,5]------>[2,4] = 9
- 实现1
Class NumArray{
private int[] sum;
public NumArray(int[] nums) {
sum = new int[nums.length+1];
sum[0] = 0;
for(int i = 1;i<sum.length;i++) {
sum[i] = sum[i-1]+nums[i-1];
}
}
public int sumRange(int i, int j) {
return sum[j+1] -sum[i];
}
}
- 区域去检索-可变
Class NumArray{
private int[] sum;
private int[] data;
public NumArray(int[] nums) {
data = new int[nums.length];
for(int i=0; i<nums.length;i++) {
data[i] = nums[i];
}
sum = new int[nums.length+1];
sum[0] = 0;
for(int i = 1;i<sum.length;i++) {
sum[i] = sum[i-1]+nums[i-1];
}
}
//更新数据的操作过程
public void update(int index,int val) {
data[index] = val;
for(int i =index+1; i<sum.length; i++) {
sum[i] = sum[i-1]+data[i-1];
}
}
public int sumRange(int i, int j) {
return sum[j+1] -sum[i];
}
}
线段树中的更新操作
//通过更新数组的值,更新线段树的节点
public void set(int index,E val) {
if(index<0 || index>=data.length) {
throw new IllegalArgumentException("Index is illegal");
}
data[index] = val;
}
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]);
}