高级数据结构线段树

Posted 下一站不是永远

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了高级数据结构线段树相关的知识,希望对你有一定的参考价值。

高级数据结构(Ⅲ)线段树(Segment Tree)
线段树的原理
树的创建
单点修改
区间查找
完整代码及测试

开始这次的学习

高级数据结构(Ⅲ)线段树(Segment Tree)

线段树的原理

线段树是一种二叉搜索树 , 对于线段树中的每一个非叶子结点[a,b],它的左儿子表示的区间为[a, (a+b)/2],右儿子表示的区间为[(a+b)/2+1, b]。因此线段树是平衡二叉树,最后的子节点数目为N,即整个线段区间的长度,其空间复杂度为O(n)。

若对于一个数组使用前缀和数组保存它的区间和,那么查找区间和时间复杂度为O(1),而区间修改时间复杂度为O(n)。使用线段树可以快速查找或修改某个区间的值,时间复杂度为O(logN)。

线段树中每一个结点代表一个区间,对于区间【1, 7】线段树的结构如下图

高级数据结构(三)线段树_数据结构

实例:

class SegmentTree
private static final int[] tree = new int[1000];
int[] arr;

SegmentTree()


SegmentTree(int[] arr)
this.arr = arr;


//创建树···
public void buildTree()

//单点修改更新树
public void updateTree()

//区间查找
public void queryTree()



树的创建

给定一个数组arr = [6, 4, 7, 5, 8, 3 , 9],创建它对应的线段树数组。

对于一个结点k,它的左孩子为2 * k,右孩子为 2 * k + 1,此公式适用于根结点从1开始。但我们在数组中存储元素时下标通常是从0开始的,即根结点从0开始,此时它的左孩子为 2 * k + 1, 右孩子为 2 * k + 2。

如下图所示,数组arr的长度为7,由于树是以2为基数的,且线段树中叶子结点保存的是arr中单个结点的值,我们可以将数组arr的长度设想为8 (2 ^ 3 = 8,理解为新添了一个元素0,这对区间和并不影响),此时它对应的线段树就是一棵结点数为15(1 + 2 + 4 + 8)的满二叉树。相应的结点值,映射到数组tree的值在图中可清晰的看出。

高级数据结构(三)线段树_数组_02

那么,如何用处程序来创建一棵树呢?

由于线段树叶子结点都是数组arr中的某一元素,所以我们可以使用两个变量low和high来标记数组arr的区间,

  • 若low == high,此时令tree[node] = arr[low],并终止递归
  • 否则,将区间二分,分别计算左区间[low, mid]和右区间[mid +1, high],并在最后更新tree[node]

实现:

//创建树
public void buildTree()
this.buildTree(0, 0, arr.length - 1);


private void buildTree(int node, int low, int high)
if(low == high)
tree[node] = arr[low];
return;

int mid = low + (high - low) / 2;
int lnode = 2 * node + 1;
int rnode = 2 * node + 2;
buildTree(lnode, low, mid);
buildTree(rnode, mid + 1, high);
tree[node] = tree[lnode] + tree[rnode];





单点修改

若现在将arr[4]的值修改为1,需要怎样操作呢?

从下图绿色标出的结点不难看出其更新过程,先将其所对应的叶子结点修改,然后继续向上修改其父节点即可。

高级数据结构(三)线段树_数组_03

当long==high&&low==index时更新两个数组的值,否则,缩小区间,在相应的区间搜索,最后更新结点和即可,相应代码如下


//单点修改更新树
public void updateTree(int index, int val)
this.updateTree(0, 0, arr.length - 1, index, val);


private void updateTree(int node, int low, int high, int index, int val)
if(low == high && low == index)
arr[index] = val;
tree[node] = val;
return;

int mid = low + (high - low) / 2;
int lnode = 2 * node + 1;
int rnode = 2 * node + 2;
if(index >= low && index <= mid)
updateTree(lnode, low, mid, index, val);
else
updateTree(rnode, mid + 1, high, index, val);

tree[node] = tree[lnode] + tree[rnode];


区间查找

若现在查找数组arr区间和[3,6],如何利用线段树呢?

在线段树中,我们将它的和划分为两个区间[3]和[4,6],如图中的黄色结点

高级数据结构(三)线段树_结点_04

下面来看看相关代码如何实现,给定一个查找区间[L, R],同样使用变量low和high维护对数组arr的二分查找边界

  • 若当前区间low > R 或者 high < L,说明已超出查找范围,返回0
  • 若[low, high]处于区间[L, R]内,返回当前结点的值tree[node]

然后继续在左右区间查找并保存左右区间的值sumLeft和sumRight,最后返回sumLeft + sumRight即可


//区间查找
public int queryTree(int L, int R)
return this.queryTree(0, 0, arr.length - 1, L, R);


private int queryTree(int node, int low,
int high, int L, int R)
if(low > R || high < L)
return 0;
else if(low >= L && high <= R)
return tree[node];

int mid = low + (high - low) / 2;
int lnode = 2 * node + 1;
int rnode = 2 * node + 2;
int sumleft = queryTree(lnode, low, mid, L, R);
int sumRight = queryTree(rnode, mid + 1, high, L, R);
return sumleft + sumRight;


完整代码及测试

class SegmentTree
private static final int[] tree = new int[1000];
int[] arr;

SegmentTree()



SegmentTree(int[] arr)
this.arr = arr;


//创建树
public void buildTree()
this.buildTree(0, 0, arr.length - 1);


private void buildTree(int node, int low, int high)
if(low == high)
tree[node] = arr[low];
return;

int mid = low + (high - low) / 2;
int lnode = 2 * node + 1;
int rnode = 2 * node + 2;
buildTree(lnode, low, mid);
buildTree(rnode, mid + 1, high);
tree[node] = tree[lnode] + tree[rnode];


//单点修改更新树
public void updateTree(int index, int val)
this.updateTree(0, 0, arr.length - 1, index, val);


private void updateTree(int node, int low, int high, int index, int val)
if(low == high && low == index)
arr[index] = val;
tree[node] = val;
return;

int mid = low + (high - low) / 2;
int lnode = 2 * node + 1;
int rnode = 2 * node + 2;
if(index >= low && index <= mid)
updateTree(lnode, low, mid, index, val);
else
updateTree(rnode, mid + 1, high, index, val);

tree[node] = tree[lnode] + tree[rnode];


//区间查找
public int queryTree(int L, int R)
return this.queryTree(0, 0, arr.length - 1, L, R);


private int queryTree(int node, int low, int high, int L, int R)
if(low > R || high < L)
return 0;
else if(low >= L && high <= R)
return tree[node];

int mid = low + (high - low) / 2;
int lnode = 2 * node + 1;
int rnode = 2 * node + 2;
int sumleft = queryTree(lnode, low, mid, L, R);
int sumRight = queryTree(rnode, mid + 1, high, L, R);
return sumleft + sumRight;


//输出线段树的值
public void printTree()
int size = 15; //size值的大小由arr数组的大小而定
for (int i = 0; i < size; i++)
System.out.print(tree[i] + " ");

System.out.println();




public class SegmentTreeTest
public static void main(String[] args)
int[] arr = 6, 4, 7, 5, 8, 3, 9;
SegmentTree st = new SegmentTree(arr);

//创建线段树
st.buildTree();
st.printTree();
//>>>42 22 20 10 12 11 9 6 4 7 5 8 3 0 0

//查找区间[3, 6]
int sum = st.queryTree(3, 6);
System.out.println(sum);
//>>>25

//单点修改更新树, 令arr[4] = 1
st.updateTree(4, 1);
st.printTree();
//>>>35 22 13 10 12 4 9 6 4 7 5 1 3 0 0

树结点版本

此版本不使用数组保存,而是以结点来保存值,相应代码不难实现,如下

import java.util.ArrayDeque;
import java.util.Deque;

class SegNode
int val;
SegNode lnode;
SegNode rnode;
SegNode()
SegNode(int val)
this.val = val;



class SegTree
SegNode root;
int[] arr;

SegTree()

SegTree(int[] arr)
this.arr = arr;
this.bulidTree();


//创建树
public void bulidTree()
root = this.buildTree(0, arr.length - 1);


private SegNode buildTree(int low, int high)
if(low == high)
return new SegNode(arr[low]);

SegNode node = new SegNode();
int mid = low + (high - low) / 2;
node.lnode = buildTree(low, mid);
node.rnode = buildTree(mid + 1, high);
node.val = node.lnode.val + node.rnode.val;
return node;


//单点修改更新树
public void updateTree(int index, int val)
root = updateTree(root ,0, arr.length - 1, index, val);


private SegNode updateTree(SegNode node, int low, int high, int index, int val)
if(low == high && low == index)
arr[index] = val;
node.val = val;
return node;

int mid = low + (high - low) / 2;
if(index >= low && index <= mid)
node.lnode = updateTree(node.lnode, low, mid, index, val);
else
node.rnode = updateTree(node.rnode, mid + 1, high, index, val);

node.val = node.lnode.val + node.rnode.val;
return node;


//查找区间
public int queryTree(int L, int R)
return queryTree(root, 0, arr.length - 1, L, R);


private int queryTree(SegNode node, int low, int high, int L ,int R)
if(low > R || high < L)
return 0;
else if(low >= L && high <= R)
return node.val;

int mid = low + (high - low) / 2;
int sumLeft = queryTree(node.lnode, low, mid, L, R);
int sumRight = queryTree(node.rnode, mid + 1, high, L, R);
return sumLeft + sumRight;



//输出树(层次遍历)
public void printTree()
Deque<SegNode> queue = new ArrayDeque<SegNode>();
queue.offer(this.root);
while(!queue.isEmpty())
int size = queue.size();
for (int i = 0; i < size; i++)
SegNode node = queue.poll();
System.out.print(node.val + " ");
if(node.lnode != null) queue.offer(node.lnode);
if(node.rnode != null) queue.offer(node.rnode);








public class SegmentTreeNodeTest
public static void main(String[] args)
int[] arr = 6, 4, 7, 5, 8, 3, 9;

//创建线段树
SegTree st = new SegTree(arr);
st.printTree();
System.out.println("");
//>>>42 22 20 10 12 11 9 6 4 7 5 8 3

//查找区间[3, 6]
int sum = st.queryTree(3, 6);
System.out.println(sum);
//>>>25

//单点修改更新树, 令arr[4] = 1
st.updateTree(4, 1);
st.printTree();
System.out.println("");
>>>35 22 13 10 12 4 9 6 4 7 5 1 3


努力!!!


以上是关于高级数据结构线段树的主要内容,如果未能解决你的问题,请参考以下文章

高级数据结构之线段树(Segment Tree)

高级数据结构之线段树(Segment Tree)

高级数据结构-线段树

数据结构算法设计模式

算法模板——线段树

线段树总结