前言
对于给出一个一维数组,求出数组中指定区间内的最大值这个区间最大值问题,最直接的方法就是暴力遍历数组的指定区间,时间复杂度为O(n)。线段树就是用于解决这类的问题的一种数据结构。它也是一种二叉树,不过节点存的数据是区间的起始坐标以及该区间的最大值,也就是说每一个节点存的是一个区间以及区间内最大值
以 [5,2,6,1] 为例:
代码实现
结构定义
typedef struct SegmentTreeNode{
int start;
int end;
int max;
struct SegmentTreeNode *left,*right;
}SegmentTreeNode,*SegmentTree;
接口实现
常用宏定义:
#define OK 1
#define ERROR 0
#define OVERFLOW -1
#define TRUE 1
#define FALSE 0
#define SUCCESS 1
#define INT_MINVALUE -2147483648
//根据数组,起始下标以及截止下标,递归创建一棵线段树(注意这里的线段树传的是引用形参)
Status createSegmentTree(SegmentTree &T,int start,int end,int *num){
//参数有误,起始下标大于截止下标或数组为空
if(start > end || num == NULL) return ERROR;
if(T == NULL){
T = (SegmentTree)malloc(sizeof(SegmentTreeNode));
if(T == NULL) return OVERFLOW;
}
T->max = INT_MINVALUE;
T->left = T->right = NULL;
//递归边界,当当前区间只剩下一个数
if(start == end){
T->start = start;
T->end = end;
T->max = num[start];
T->left = T->right = NULL;
return TRUE;
}
T->start = start;
T->end = end;
int mid = (end + start) >> 1;
//递归构建左右两边区间
createSegmentTree(T->left,start,mid,num);
createSegmentTree(T->right,mid + 1,end,num);
//左右子树(区间)构建好后,左右子树(区间)两个最大值中较大的就是整个区间的最大值
if(T->left != NULL) T->max = T->left->max > T->max ? T->left->max : T->max;
if(T->right != NULL) T->max = T->right->max > T->max ? T->right->max : T->max;
return TRUE;
}
//查找线段树中指定区间的最大值
int findSegmentMax(SegmentTree T,int start,int end){
//参数有误
if(start > end) return INT_MINVALUE;
//参数有误,要查询的区间范围已经超过当前节点的区间范围
if(start < T->start || end > T->end) return INT_MINVALUE;
//区间就是当前节点的区间
if(start == T->start && end == T->end) return T->max;
int mid = (T->start + T->end) >> 1;
//要查找的区间包含了当前节点的区间的中间值,就要以中间值为界拆分为两半分别查找
//如当前节点区间为[0,7],要查找的区间为[2,5],就要查找在当前节点的左子树查找[2,3]的最大值,
//再在右子树查找[4,5]的最大值,两次查找到最大值的较大值就是[2,5]的最大值
if(end >= mid && start <= mid){
int rightMax = findSegmentMax(T->right,mid + 1,end);
int leftMax = findSegmentMax(T->left,start,mid);
return rightMax > leftMax ? rightMax : leftMax;
}else if(start > mid){
//要查找的区间在当前区间的中间值的右边,直接查找当前节点的右子树即可
return findSegmentMax(T->right,start,end);
}else if(end < mid){
//要查找的区间在当前区间的中间值的左边,直接查找当前节点的左子树即可
return findSegmentMax(T->left,start,end);
}
}
时间复杂度分析
在建树操作中,数组每一个元素都会被处理到,所以复杂度是O(n)
分析查找操作之前要先知道:由于每一次都是对区间左右平分到左右子树中,所以最后树的高度会是logn。所以就算查找时一直找到了叶子节点,复杂度也会是O(logn)。如果是对一个数组进行多次求区间最大值的操作,暴力做的话每一次都需要O(n)的复杂度,使用线段树的话,只需要先花O(n)的复杂度建树,以后每次查找只需要O(logn)