数据结构之SQRT分解
前言
SQRT分解是一种数据结构,使用分组的思想来解决区间问题,如求区间和,区间最大最小值等。支持动态更新指定索引的值。
将一个包含N个元素的数组分成sqrt(N)组,就是对N开平方。如18个元素,sqrt(18)=4.24,所以分成5组,最后一组不足4个。
原理
以求区间和为例,对数据分组,提前计算好每组数据的和。
查询区间在同一组内,只需要从区间的开始索引到结束索引遍历一遍即可。
查询区间在不同组
查询区间在不同组,后两种情况可以合二为一,都是中间间隔了几组数据。
将区间分成3部分
- 第一部分[3,3]
- 第二部分1,2,3组,每组的数据之前已经提前计算好了
- 第三部分[16,16]
总体时间复杂度O(sqrt(N))
代码实现
import java.util.Arrays;
public class SQRT<T> {
private T[] data;//原数组数据
private int N;//原数组长度
private T[] blocks;//存储每组数据的和或最大最小值等数据
private int B;//每组大小 sqrt(N)
private int Bn;//共分成几组 等于B或B+1
private Merger<T> merger; //合并策略
public SQRT(T[] arr, Merger<T> merger) {
this.merger = merger;
N = arr.length;
data = Arrays.copyOf(arr, N);
B = (int) Math.sqrt(N);
Bn = N / B + (N % B == 0 ? 0 : 1);
blocks = (T[]) new Object[Bn];
for (int i = 0; i < data.length; i++) {
if (i % B == 0) {
//每组的开始
blocks[getBlockIndex(i)] = data[i];
} else {
blocks[getBlockIndex(i)] = this.merger.merge(blocks[getBlockIndex(i)], data[i]);
}
}
}
/**
* 查询指定区间的值
*/
public T queryRange(int left, int right) {
int startBlockIndex = getBlockIndex(left);
int endBlockIndex = getBlockIndex(right);
if (startBlockIndex == endBlockIndex) {
return merge(data, left, right);
}
int leftStart = left;
int leftEnd = getEndIndex(startBlockIndex);
int rightStart = getStartIndex(endBlockIndex);
int rightEnd = right;
T[] parts = (T[]) new Object[3];
parts[0] = merge(data, leftStart, leftEnd);
parts[1] = merge(data, rightStart, rightEnd);
//这一部分有可能为空
parts[2] = merge(blocks, startBlockIndex + 1, endBlockIndex - 1);
if (parts[2] == null) {
return merge(parts, 0, parts.length - 2);
} else {
return merge(parts, 0, parts.length - 1);
}
}
/**
* 更新指定索引的值
*/
public SQRT<T> update(int index, T val) {
int blockIndex = getBlockIndex(index);
data[index] = val;
int startIndex = getStartIndex(blockIndex);
int endIndex = getEndIndex(blockIndex);
blocks[blockIndex] = merge(data, startIndex, endIndex);
return this;
}
/**
* 合并指定区间的数组
*/
private T merge(T[] data, int start, int end) {
if (start > end) {
return null;
}
T res = data[start];
for (int i = start + 1; i <= end && i < data.length; i++) {
res = this.merger.merge(res, data[i]);
}
return res;
}
/**
* 根据原数组索引得到分组的索引(第几组)
*/
private int getBlockIndex(int dataIndex) {
return dataIndex / B;
}
/**
* 根据分组索引(第几组)得到此组的开始索引
*/
private int getStartIndex(int blockIndex) {
return blockIndex * B;
}
/**
* 根据分组索引(第几组)得到此组的结束索引
*/
private int getEndIndex(int blockIndex) {
return (blockIndex + 1) * B - 1;
}
public interface Merger<E> {
E merge(E e1, E e2);
}
}
客户端使用
public class TestSQRT {
public static void main(String[] args) {
Integer[] arr = {1, 2, 3, 4};
System.out.println(new SQRT<>(arr, Integer::sum).queryRange(0, 3));//10
System.out.println(new SQRT<>(arr, Integer::sum).update(0, 5).queryRange(0, 3));//14
System.out.println(new SQRT<>(arr, Integer::max).update(0, 5).queryRange(0, 3));//5
System.out.println(new SQRT<>(arr, Integer::min).update(0, 5).queryRange(0, 3));//2
}
}