参考博文:http://www.cnblogs.com/TenosDoIt/p/3453089.html
参考视频:https://www.youtube.com/watch?v=ZBHKZF5w4YU&list=PLrmLmBdmIlpv_jNDXtJGYTPNQ2L1gdHxu&index=22
最近在看数据结构的相关知识,想着看点东西总该写一写总结一下心理才踏实;
线段树是什么呢?给出该博文的定义:
线段树,是一个完全二叉树,它在各个节点保存一条线段(数组中的一段子数组),主要用于高效解决连续区间的动态查询问题,由于二叉结构的特性,它基本能保持每个操作的复杂度为O(logn)。
线段树的每个节点表示一个区间,子节点则分别表示父节点的左右半区间,例如父亲的区间是[a,b],那么(c=(a+b)/2)左儿子的区间是[a,c],右儿子的区间是[c+1,b]。
也许看过树状数组的同学会有印象,树状数组里面的c[i]表示的是1~i区间的总和。
于是可以将线段树看做是树状数组的升级版。
来了解一下线段树主要想解决什么问题:
问题描述如下:从数组arr[0...n-1]中查找某个数组某个区间内的最小值,其中数组大小固定,但是数组中的元素的值可以随时更新。
对这个问题一个简单的解法是:遍历数组区间找到最小值,时间复杂度是O(n),额外的空间复杂度O(1)。当数据量特别大,而查询操作很频繁的时候,耗时可能会不满足需求。
比如:-1,3,4,0,2,1 .现在我希望知道2~4区间里的最小值
传统的自然是直接求取,要么是使用一个二维数组存储所有区间的值。
当区间内有元素更新了之后,需要对整个二维数组进行更新,效率比较有限。
于是线段树就是为了解决这一类问题;
让我们来看一下线段树长什么样子:
可以看到线段树的每个节点保存了一个区间的最小值,当我们需要查询一个区间的最小值的时候,将目标区间从根节点开始下降,有3种情况:
- 部分覆盖 --->返回min(left,right) 其中left和right分别为左右子树所返回的值
- 完全覆盖 --->返回root,表明当前区间在某个节点内
- 未覆盖 ---> 返回INT_MAX,这个值将在递归的过程中由min函数进行舍弃
故考虑区间[0,3]
根节点部分覆盖,下降到左右孩子节点
[0,3]完全覆盖左子树节点,返回-1,
[0,3]部分覆盖右子树,继续判断左右子树
[0,3]和[5,5]不覆盖,返回INT_MAX
......
于是可以在O(lgN)时间内求出最小值。
于是如何将一个数组转化为一个线段树呢?
由于线段树是一棵完全二叉树,所以也可以用数组表示!!
我们看以下代码
void constructTree(int input[],int segTree[],int low,int high,int pos){
if(low==high){
segTree[pos]=input[low];
return;
}
//二分
int mid=(low+high)/2;
//递归构造左右子树
constructTree(input,segTree,low,mid,2*pos+1);
constructTree(input,segTree,mid+1,high,2*pos+2);
//根据左右子树的值回溯更新当前节点的值
segTree[pos]=min(segTree[2*pos+1],segTree[2*pos+2]);
}
代码比较简单,而且我们利用了一个重要的性质:对于一个完全二叉树, 左孩子编号=2*root编号+1 右孩子编号=2*root编号+2
那么如何求一个区间的最小值呢?
于是我们采取和前面推导过程类似的递归方法,分为3种情况进行递归:
int rangeMinQuery(int segTree[],int qlow,int qhigh,int low,int high,int pos) {
if(qlow<low && qhigh>high){//完全覆盖
return segTree[pos];
}
if(qlow>high || qhigh<low){//未覆盖
return INT_MAX;
}
//部分覆盖
int mid=(low+high)/2;
return min(rangeMinQuery(segTree,qlow,qhigh,low,mid,2*pos+1),
rangeMinQuery(segTree,qlow,qhigh,mid+1,high,2*pos+2));
}
待续....