Java數(shù)據(jù)結(jié)構(gòu)之線段樹詳解
介紹
線段樹(又名區(qū)間樹)也是一種二叉樹,每個(gè)節(jié)點(diǎn)的值等于左右孩子節(jié)點(diǎn)值的和,線段樹示例圖如下

以求和為例,根節(jié)點(diǎn)表示區(qū)間0-5的和,左孩子表示區(qū)間0-2的和,右孩子表示區(qū)間3-5的和,依次類推。
代碼實(shí)現(xiàn)
/**
* 使用數(shù)組實(shí)現(xiàn)線段樹
*/
public class SegmentTree<E> {
private Node[] data;
private int size;
private Merger<E> merger;
public SegmentTree(E[] source, Merger<E> merger) {
this.merger = merger;
this.size = source.length;
this.data = new Node[size * 4];
buildTree(0, source, 0, size - 1);
}
public E search(int queryLeft, int queryRight) {
if (queryLeft < 0 || queryLeft > size || queryRight < 0 || queryRight > size
|| queryLeft > queryRight) {
throw new IllegalArgumentException("index is illegal");
}
return search(0, queryLeft, queryRight);
}
/**
* 查詢區(qū)間queryLeft-queryRight的值
*/
private E search(int treeIndex, int queryLeft, int queryRight) {
Node treeNode = data[treeIndex];
int left = treeNode.left;
int right = treeNode.right;
if (left == queryLeft && right == queryRight) {
return elementData(treeIndex);
}
int leftTreeIndex = leftChild(treeIndex);
int rightTreeIndex = rightChild(treeIndex);
int middle = left + ((right - left) >> 1);
if (queryLeft > middle) {
return search(rightTreeIndex, queryLeft, queryRight);
} else if (queryRight <= middle) {
return search(leftTreeIndex, queryLeft, queryRight);
}
E leftEle = search(leftTreeIndex, queryLeft, middle);
E rightEle = search(rightTreeIndex, middle + 1, queryRight);
return merger.merge(leftEle, rightEle);
}
public void update(int index, E e) {
update(0, index, e);
}
/**
* 更新索引為index的值為e
*/
private void update(int treeIndex, int index, E e) {
Node treeNode = data[treeIndex];
int left = treeNode.left;
int right = treeNode.right;
if (left == right) {
treeNode.data = e;
return;
}
int leftTreeIndex = leftChild(treeIndex);
int rightTreeIndex = rightChild(treeIndex);
int middle = left + ((right - left) >> 1);
if (index > middle) {
update(rightTreeIndex, index, e);
} else {
update(leftTreeIndex, index, e);
}
treeNode.data = merger.merge(elementData(leftTreeIndex), elementData(rightTreeIndex));
}
private void buildTree(int treeIndex, E[] source, int left, int right) {
if (left == right) {
data[treeIndex] = new Node<>(source[left], left, right);
return;
}
int leftTreeIndex = leftChild(treeIndex);
int rightTreeIndex = rightChild(treeIndex);
int middle = left + ((right - left) >> 1);
buildTree(leftTreeIndex, source, left, middle);
buildTree(rightTreeIndex, source, middle + 1, right);
E treeData = merger.merge(elementData(leftTreeIndex), elementData(rightTreeIndex));
data[treeIndex] = new Node<>(treeData, left, right);
}
@Override
public String toString() {
return Arrays.toString(data);
}
private E elementData(int index) {
return (E) data[index].data;
}
private int leftChild(int index) {
return index * 2 + 1;
}
private int rightChild(int index) {
return index * 2 + 2;
}
private static class Node<E> {
E data;
int left;
int right;
Node(E data, int left, int right) {
this.data = data;
this.left = left;
this.right = right;
}
@Override
public String toString() {
return String.valueOf(data);
}
}
public interface Merger<E> {
E merge(E e1, E e2);
}
}
我們以LeetCode上的一個(gè)問題來分析線段樹的構(gòu)建,查詢和更新,LeetCode307問題如下:
給定一個(gè)整數(shù)數(shù)組,查詢索引區(qū)間[i,j]的元素的總和。
線段樹構(gòu)建
private void buildTree(int treeIndex, E[] source, int left, int right) {
if (left == right) {
data[treeIndex] = new Node<>(source[left], left, right);
return;
}
int leftTreeIndex = leftChild(treeIndex);
int rightTreeIndex = rightChild(treeIndex);
int middle = left + ((right - left) >> 1);
buildTree(leftTreeIndex, source, left, middle);
buildTree(rightTreeIndex, source, middle + 1, right);
E treeData = merger.merge(elementData(leftTreeIndex), elementData(rightTreeIndex));
data[treeIndex] = new Node<>(treeData, left, right);
}
測(cè)試代碼
public class Main {
public static void main(String[] args) {
Integer[] nums = {-2, 0, 3, -5, 2, -1};
SegmentTree<Integer> segmentTree = new SegmentTree<>(nums, Integer::sum);
System.out.println(segmentTree);
}
}
最后構(gòu)造出的線段樹如下,前面為元素值,括號(hào)中為包含的區(qū)間。

遞歸構(gòu)造過程為
- 當(dāng)左指針和右指針相等時(shí),表示為葉子節(jié)點(diǎn)
- 將左孩子和右孩子值相加,構(gòu)造當(dāng)前節(jié)點(diǎn),依次類推
區(qū)間查詢
/**
* 查詢區(qū)間queryLeft-queryRight的值
*/
private E search(int treeIndex, int queryLeft, int queryRight) {
Node treeNode = data[treeIndex];
int left = treeNode.left;
int right = treeNode.right;
if (left == queryLeft && right == queryRight) {
return elementData(treeIndex);
}
int leftTreeIndex = leftChild(treeIndex);
int rightTreeIndex = rightChild(treeIndex);
int middle = left + ((right - left) >> 1);
if (queryLeft > middle) {
return search(rightTreeIndex, queryLeft, queryRight);
} else if (queryRight <= middle) {
return search(leftTreeIndex, queryLeft, queryRight);
}
E leftEle = search(leftTreeIndex, queryLeft, middle);
E rightEle = search(rightTreeIndex, middle + 1, queryRight);
return merger.merge(leftEle, rightEle);
}
查詢區(qū)間2-5的和
public class Main {
public static void main(String[] args) {
Integer[] nums = {-2, 0, 3, -5, 2, -1};
SegmentTree<Integer> segmentTree = new SegmentTree<>(nums, Integer::sum);
System.out.println(segmentTree);
System.out.println(segmentTree.search(2, 5)); // -1
}
}
查詢過程為
- 待查詢的區(qū)間和當(dāng)前節(jié)點(diǎn)的區(qū)間相等,返回當(dāng)前節(jié)點(diǎn)值
- 待查詢左區(qū)間大于中間區(qū)間值,查詢右孩子
- 待查詢右區(qū)間小于中間區(qū)間值,查詢左孩子
- 待查詢左區(qū)間在左孩子,右區(qū)間在右孩子,兩邊查詢結(jié)果相加
更新
/**
* 更新索引為index的值為e
*/
private void update(int treeIndex, int index, E e) {
Node treeNode = data[treeIndex];
int left = treeNode.left;
int right = treeNode.right;
if (left == right) {
treeNode.data = e;
return;
}
int leftTreeIndex = leftChild(treeIndex);
int rightTreeIndex = rightChild(treeIndex);
int middle = left + ((right - left) >> 1);
if (index > middle) {
update(rightTreeIndex, index, e);
} else {
update(leftTreeIndex, index, e);
}
treeNode.data = merger.merge(elementData(leftTreeIndex), elementData(rightTreeIndex));
}
更新只影響元素值,不影響元素區(qū)間。
更新其實(shí)和構(gòu)建的邏輯類似,找到待更新的實(shí)際索引,依次更新父節(jié)點(diǎn)的值。
總結(jié)
線段樹可以很好地處理區(qū)間問題,如區(qū)間求和,求最大最小值等。
以上就是Java 數(shù)據(jù)結(jié)構(gòu)之線段樹詳解的詳細(xì)內(nèi)容,更多關(guān)于Java 線段樹的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
深入解析Java中的Classloader的運(yùn)行機(jī)制
這篇文章主要介紹了Java中的Classloader的運(yùn)行機(jī)制,包括從JVM方面講解類加載器的委托機(jī)制等,需要的朋友可以參考下2015-11-11
Spring MVC 404 Not Found無錯(cuò)誤日志的解決方法
這篇文章主要為大家詳細(xì)介紹了Spring MVC 404 Not Found無錯(cuò)誤日志的解決方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-12-12
mybatis if test 不為空字符串且不為null的問題
這篇文章主要介紹了mybatis if test 不為空字符串且不為null的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03
JPA延遲加載no Session報(bào)錯(cuò)解決分析
這篇文章主要為大家介紹了JPA延遲加載no Session報(bào)錯(cuò)解決分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-09-09
Java中Optional的正確用法與爭(zhēng)議點(diǎn)詳解
這篇文章主要介紹了Java中Optional的正確用法與爭(zhēng)議點(diǎn)的相關(guān)資料,需要的朋友可以參考下2022-11-11
FP-Growth算法的Java實(shí)現(xiàn)+具體實(shí)現(xiàn)思路+代碼
FP-Growth算法比Apriori算法快很多(但是卻比不上時(shí)間,how time slipped away)。在網(wǎng)上搜索后發(fā)現(xiàn)Java實(shí)現(xiàn)的FP-Growth算法很少,且大多數(shù)不太能理解):太菜。所以就自己實(shí)現(xiàn)了一下。這篇文章重點(diǎn)介紹一下我的Java實(shí)現(xiàn)2021-06-06
java?Export大量數(shù)據(jù)導(dǎo)出和打包
這篇文章主要為大家介紹了java?Export大量數(shù)據(jù)的導(dǎo)出和打包實(shí)現(xiàn)過程,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-06-06

