ラインセグメントツリーは高度なデータ構造であり、通常はまったくアクセスできませんが、その出現により特定の問題が解決されます。このデータ構造の設計は学ぶ価値があります。
目次
分析と拡張
1.ラインセグメントツリーの設計
配列要素のセットが与えられた場合:
A [0]、A [1]、A [2]、A [3]、A [4]、A [5]、A [6]、A [7]
これらの配列が使用されます間隔はA [0…7]と表すことができ、[0,7]を維持することができます。この間隔は均等に分割されて丸められ、最終的に次のような形式になります。これがラインセグメントツリーモデルです。
1.上図に示すように、各ノードはラインセグメントを格納するか、データの間隔と言えます。
2.ラインセグメントツリーは、必ずしも完全なバイナリツリーである必要はありません。ユーザー提供要素の数は関連しています。(下の図に示すように、ユーザーは10個の要素でカプセル化したラインセグメントツリーを提供します)
3。ラインセグメントツリーはバランスの取れたバイナリツリーです。実際に私たちが以前に学んだことヒープもバランスの取れたバイナリツリーです。
4.ラインセグメントツリーは、必ずしも完全なバイナリツリーである必要はありません。これは、ラインセグメントツリーの要素数に関連しています。
2.バランスの取れたバイナリツリーの概要
バイナリツリーのツリーの最大深度とツリーの最小深度の差は1を超えません。次に、このツリーはバランスの取れたバイナリツリーです。
3. n個の要素があるとすると、ラインセグメントツリーを表すために配列を使用するためにどのくらいのスペースを適用する必要がありますか?
ユーザーが指定された数の配列要素を指定し、これらの要素をラインセグメントツリーに格納します。ラインセグメントツリーの最下層が配列を使用して実装されている場合、この配列はどのくらいのスペースを適用する必要がありますか?上図のように、A [0,7] 8個の要素があるとすると、15ノードのバイナリツリーが形成されます。このとき、配列を実装するために15個のスペースを申請する必要があります。次に、それについて説明します。
(1)バイナリツリーがいっぱいになると、スペースの導出を申請する必要があります
ユーザーが提供する要素の数がnの場合、以下に示すように、完全なバイナリツリーを形成できます。
特別な状況、つまり、ユーザーが提供する要素の数がnの場合、2nのスペースを申請するだけで済みます。
注:
1。この特殊なケースはn = 2 ^ hです。つまり、nの値は2のh乗です。
2. n = 2 ^ hが満たされると、完全なバイナリツリーを形成できます。
(2)完全でないバイナリツリーの場合、スペースの導出を申請する必要があります
ユーザーが提供する要素はnであり、nは必ずしもn = 2 ^ hを満たすとは限りません。n= 2 ^ h + kである可能性があるため、リーフノードにさらにいくつかの要素が存在する可能性があります。次の図に示すように、現時点では、このツリーを完全なバイナリツリーとして扱い、処理して、空の位置を空の要素として扱うことができます。
4nスペースを申請する理由:
ここでの最悪のケースを考えると、n = 2 ^ h + k、もう1つのレイヤーが必要です。元々、n = 2 ^ hの場合、2nスペースを申請する必要があります。もう1つのレイヤーは2n多いです。 。
コードデザイン
1.ラインセグメントツリーの構築
/**
* Create by SunnyDay on 2020/08/15
*/
public interface Merger<E> {
// 吧泛型E 代表的两种类型元素融合成为一种元素。
E merge(E a, E b);
}
/**
* Create by SunnyDay on 2020/08/12
* 线段树,基于数组方式实现。
*/
public class SegmentTree<E> {
private E[] data; // 内部维护用户传递过来的数组
private E[] tree;//线段树的数组实现
private Merger<E> merger; // 融合器,消除类型之间的兼容性。
/**
* 构造,用户传一个数组,我们内部维护这个数组。
*/
@SuppressWarnings("unchecked")
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];
// }
// 使用for 遍历数组,给另一个数组赋值时。系统建议使用 System.arraycopy 函数
System.arraycopy(arr, 0, data, 0, arr.length);
tree = (E[]) new Object[4 * arr.length]; // 申请数组元素四倍空间
// 默认情况下根节点的索引为0,区间左右端点为[0,data.length-1]
buildSegmentTree(0, 0, data.length - 1);
}
/**
* 返回完全二叉树中 给定索引所代表元素左孩子节点的索引
*/
private int leftChild(int index) {
return index * 2 + 1;// 公式 参考推导图
}
/**
* 返回完全二叉树中 给定索引所代表元素有孩子节点的索引
*/
private int rightChild(int index) {
return index * 2 + 2;// 公式 参考推导图
}
public int getSize() {
return data.length;
}
/**
* 获得指定索引的元素
*/
public E get(int index) {
if (0 < index || index >= data.length) {
throw new IllegalArgumentException("index is illegal");
}
return data[index];
}
}
/**
* 在treeIndex 位置 创建区间为[left,right]的线段树
*/
private void buildSegmentTree(int treeIndex, int left, int right) {
// 1、递归终结条件(递归到底,区间就一个元素)
//(1)找到底的条件,写判断。
//(2)return
if (left == right) {
// left 代表数组索引区间,treeIndex代表 线段树数组表示中的索引位置
tree[treeIndex] = data[left];//data[right] 意思一样
return;
}
//2、区间元素为多个时,treeIndex 有左右孩子。
int leftTreeIndex = leftChild(treeIndex);// 左孩子索引
int rightTreeIndex = rightChild(treeIndex);// 右孩子索引
// (1)总的区间有了,则中点也可找出。
//int middle = (left + right) / 2;// 可能会整型溢出
int middle = left + (right - left) / 2;
// (2)treeIndex 位置子孩子的区间也就可标识出来了即:[left,middle],[middle+1,right]
// (3) 有了索引,区间表示,则可递归创建左右子树作为线段树。
buildSegmentTree(leftTreeIndex, left, middle);
buildSegmentTree(rightTreeIndex, middle + 1, right);
// treeIndex 索引对应区间元素和则为其左右子树元素之和
//tree[treeIndex] = tree[leftTreeIndex] + tree[rightTreeIndex];
//Operator '+' cannot be applied to 'E', 'E'
//思考:+ 的使用范围应该是同种类型。
// Object a = 10;
// Object b = "a";
// Object c = a+b;
// 上面不仅出现类型兼容问题,而且+的处理过于局限,用户只能处理区间之和。这里使用接口融合器
// 消除兼容问题,并且业务逻辑用户自己实现。求和,区间极值都可。
tree[treeIndex] = merger.merge(tree[leftTreeIndex], tree[rightTreeIndex]);
}
注:
1。ユーザーが配列を指定した後、ラインセグメントまたは間隔が実際に決定されます
。2 。ツリーの自然な再帰により、再帰によって実装できます。
3.フュージョンデバイスインターフェイスの設計に
注意してください。4。2つの整数の平均のオーバーフロー計算に注意してください。
2.クエリの設計
/**
* @param queryL
* @param queryR
* @function 用户要查询的区间[queryL, queryR]
*/
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,区间为[left,right] 中查询[queryL,queryR] 区间
*/
private E query(int treeIndex, int left, int right, int queryL, int queryR) {
// 1、递归终结条件
if (left == queryL && right == queryR) {
return tree[treeIndex];
}
//2、划分区间
int leftTreeIndex = leftChild(treeIndex);
int rightTreeIndex = rightChild(treeIndex);
int middle = left + (right - left) / 2;
// 3、判断区间
if (queryL >= middle + 1) {
//[queryL,queryR] 区间在[left,right] 去见的右孩子区间
return query(rightTreeIndex, middle + 1, right, queryL, queryR);
} else if (queryR <= middle) {
//[queryL,queryR] 区间在[left,right] 去见的左孩子区间
return query(leftTreeIndex, left, middle, queryL, queryR);
} else {
//[queryL,queryR] 区间在[left,right] 区间的左右孩子都有
E leftResult = query(leftTreeIndex, left, middle, queryL, middle);
E rightResult = query(rightTreeIndex, middle + 1, right, middle + 1, queryR);
return merger.merge(leftResult, rightResult);
}
}
3.更新操作
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 为根节点,区间为[left,right] 内索引为 index 的元素
*/
private void set(int treeIndex, int left, int right, int index, E e) {
if (left == right) {
tree[index] = e;
return;
}
//2、划分区间
int leftTreeIndex = leftChild(treeIndex);
int rightTreeIndex = rightChild(treeIndex);
int middle = left + (right - left) / 2;
if (index>=middle+1){
set(rightTreeIndex,middle+1,right,index,e);
}else {
set(leftTreeIndex,left,middle,index,e);
}
// 更新
tree[treeIndex] =merger.merge(tree[leftTreeIndex],tree[rightTreeIndex]);
}
4.出力
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("[");
for (int i = 0; i < tree.length; i++) {
if (null != tree[i]) {
sb.append(tree[i]);
} else {
sb.append("null");
}
if (i != tree.length - 1) {
sb.append(",");
} else {
sb.append("]");
}
}
return sb.toString();
}
終わり
簡単に言えば、収穫は非常にやりがいがあります、ヨーヨー!