数据结构之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
  }

}

参考

数据结构--SQRT分解
leetcode307 区域和检索 - 数组可修改

posted @ 2022-05-16 21:46  strongmore  阅读(332)  评论(0编辑  收藏  举报