一、线段树能解决的问题
RMQ(区间最值问题)
区间求和
eg:如下图:区间求和[1:3] 答:42
区间[1:4]的最大值 答:22
二、构建线段树
(1)线段树不是完全二叉树,是平衡二叉树。可以使用数组表示树的节点
(2)给定一个数组arr ,获取生成线段树的高度
eg:2^(h-1) >=arr.length 的最小值
2^(h-1) >=8
h-1 >=log2^(8)
h>=4
所以生成线段树的高度为4
(3)节点个数最多为 : 2^(h) -1
(4)生成线段树
/**
* 构建线段树
* @param start 区间的开始
* @param end 区间的结尾
* @param index 线段树对应的[start:end] 区间的索引
*/
public void buildingSegmentTree(int start, int end, int index) {
//1、递归到底的情况
if (start == end) {
this.data[index] = this.source[start];
return;
}
//2、递归操作
int mid = start + (end - start) / 2;
int lefindex = 2 * index + 1;
int rightindex = lefindex + 1;
buildingSegmentTree(start, mid, lefindex);
buildingSegmentTree(mid + 1, end, rightindex);
this.data[index] = this.merger.merge(this.data[lefindex], this.data[rightindex]);
}
int mid = (start + end )/2;可能会超范围,所以使用int mid = start +(end - start)/2
此构建线段树的方法只支持求和操作,为了具有通用性,可以设计一个融合接口 Merge
public interface Merger<T> {
T merge(T a,T b);
}
private T[] source;//源数组
private T[] data;//线段树对应的的数组
private Merger<T> merger;
public SegmentTree(T[]arr, Merger<T> merger) {
this.merger = merger;
if (arr!=null){
this.source = Arrays.copyOf(arr,arr.length);
int height =(int) Math.ceil(Math.log(this.source.length)/ Math.log(2) +1);
int datalength =(int) Math.pow(2,height)-1;
this.data = (T[])(new Object[datalength]);
buildingSegmentTree(0,this.source.length-1,0);
}
}
三、查询操作
/**
* 进行查询操作
* @param start 线段树节点所表示区间的start
* @param end 线段树节点所表示区间的end
* @param index 线段树上节点的索引
* @param form 查询区间的开始
* @param to 查询区间的结尾
* @return
*/
private T query(int start,int end,int index,int form,int to){
//1、递归到底的情况
if (start == form&& end == to ){
return this.data[index];
}
//2、递归操作 【1】完全不包含左边 【2】完全不包含右边 【3】左右两边都包含
int leftIndex =2*index +1;
int rightIndex =leftIndex+1;
int mid =start +(end -start)/2;
if (form>mid){
return query(mid+1,end,rightIndex,form,to);
}else if (to<=mid){
return query(start,mid,leftIndex,form,to);
}else {
//左边
T leftValue = query(start,mid,leftIndex,form,mid);
T rightValue = query(mid+1,end,rightIndex,mid+1,to);
return this.merger.merge(leftValue,rightValue);
}
}
四、更新操作
/**
* 更新源数组索引为index位置的值为val
* @param site 源数组的索引
* @param val 值
*/
public T update(int site,T val){
if (site<0||site>=this.source.length){
throw new IllegalArgumentException("index in invalid!");
}
//重新渲染线段树
return update(0,this.source.length-1,0,site,val);
}
private T update(int start, int end, int index, int orindex, T val) {
//递归到底
if (start==end&&start==orindex){
return this.source[orindex]=this.data[index]=val;
// return ;
}
//递归操作
int mid = start +(end-start)/2;
int leftIndex = 2*index +1;
int rightIndex = leftIndex+1;
if (mid<orindex){
update(mid+1,end,rightIndex,orindex,val);
}else {
update(start,mid,leftIndex,orindex,val);
}
return this.data[index] =this.merger.merge(this.data[leftIndex],this.data[rightIndex]);
}
五、完整代码
package lesson7;
import java.util.Arrays;
public class SegmentTree<T> {
public static void main(String[] args) {
Integer[] arr = {19, 2, 18, 22, 5};
SegmentTree<Integer> segmentTree = new SegmentTree<>(arr, (Integer a, Integer b) -> a + b);
System.out.println(segmentTree);
Integer result = segmentTree.getRangValue(1, 3);
//索引从1-3的数组的和
System.out.println("arr[1:3]的和=" + result);
Integer integer= segmentTree.update(2,6);
System.out.println("更新索引为2的值为6");
System.out.println("arr数组最大值:"+integer);
System.out.println(segmentTree);
}
private T[] source;//源数组
private T[] data;//线段树对应的的数组
private Merger<T> merger;
public SegmentTree(T[]arr, Merger<T> merger) {
this.merger = merger;
if (arr!=null){
this.source = Arrays.copyOf(arr,arr.length);
int height =(int) Math.ceil(Math.log(this.source.length)/ Math.log(2) +1);
int datalength =(int) Math.pow(2,height)-1;
this.data = (T[])(new Object[datalength]);
buildingSegmentTree(0,this.source.length-1,0);
}
}
/**
* 构建线段树
* @param start 区间的开始
* @param end 区间的结尾
* @param index 线段树对应的[start:end] 区间的索引
*/
public void buildingSegmentTree(int start, int end, int index) {
//1、递归到底的情况
if (start == end) {
this.data[index] = this.source[start];
return;
}
//2、递归操作
int mid = start + (end - start) / 2;
int lefindex = 2 * index + 1;
int rightindex = lefindex + 1;
buildingSegmentTree(start, mid, lefindex);
buildingSegmentTree(mid + 1, end, rightindex);
this.data[index] = this.merger.merge(this.data[lefindex], this.data[rightindex]);
}
public T getRangValue(int form, int to) {
if (form < 0 || to >= this.source.length || form > to) {
throw new IllegalArgumentException("eorror");
}
return query(0, this.source.length - 1, 0, form, to);
}
/**
* 进行查询操作
* @param start 线段树节点所表示区间的start
* @param end 线段树节点所表示区间的end
* @param index 线段树上节点的索引
* @param form 查询区间的开始
* @param to 查询区间的结尾
* @return
*/
private T query(int start,int end,int index,int form,int to){
//1、递归到底的情况
if (start == form&& end == to ){
return this.data[index];
}
//2、递归操作 【1】完全不包含左边 【2】完全不包含右边 【3】左右两边都包含
int leftIndex =2*index +1;
int rightIndex =leftIndex+1;
int mid =start +(end -start)/2;
if (form>mid){
return query(mid+1,end,rightIndex,form,to);
}else if (to<=mid){
return query(start,mid,leftIndex,form,to);
}else {
//左边
T leftValue = query(start,mid,leftIndex,form,mid);
T rightValue = query(mid+1,end,rightIndex,mid+1,to);
return this.merger.merge(leftValue,rightValue);
}
}
/**
* 更新源数组索引为index位置的值为val
* @param site 源数组的索引
* @param val 值
*/
public T update(int site,T val){
if (site<0||site>=this.source.length){
throw new IllegalArgumentException("index in invalid!");
}
//重新渲染线段树
return update(0,this.source.length-1,0,site,val);
}
private T update(int start, int end, int index, int orindex, T val) {
//递归到底
if (start==end&&start==orindex){
return this.source[orindex]=this.data[index]=val;
// return ;
}
//递归操作
int mid = start +(end-start)/2;
int leftIndex = 2*index +1;
int rightIndex = leftIndex+1;
if (mid<orindex){
update(mid+1,end,rightIndex,orindex,val);
}else {
update(start,mid,leftIndex,orindex,val);
}
return this.data[index] =this.merger.merge(this.data[leftIndex],this.data[rightIndex]);
}
@Override
public String toString() {
StringBuilder sb =new StringBuilder("[");
if (this.data!=null&&this.data.length!=0){
Arrays.stream(this.data).forEach(item->{
sb.append(item+",");
});
}
/* sb.deleteCharAt(sb.length()-1);
sb.append("]");
return sb.toString();*/
return sb.deleteCharAt(sb.length() - 1).append("]").toString();
}
}