线段树——建树、更新、查询步骤解析(附习题)

本文详细解析线段树的原理及应用,包括如何建树、更新和查询区间和,并通过实例说明线段树在解决区间查询问题上的优势。此外,还介绍了如何使用线段树解决数组元素连续性的判断问题。
摘要由CSDN通过智能技术生成

①、什么是线段树?
答:线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。它能够实现很多功能,例如快速求区间和、求区间最大值、求区间最小值等。(在修改元素的基础上)
②、为什么我们要用线段树?
答:使用线段树可以快速的查找某一个节点在若干条线段中出现的次数,时间复杂度为O(logN)。
例如:
有 N 个元素的数组。
现在有两个操作:1、修改元素 2、求子区间和。
思路一:
假如我们用时间复杂度 O(1) 修改了元素 arr[idx] = val,那么查询区间和的时间复杂度将是 O(N)
思路二:
假如我们提前求出每个元素的前缀和并存储在sum数组中,那么查询区间 [x, y] 的方式为 sum[y] - sum[x-1] 时间复杂度为 O(1),可是每修改一次 sum 数组都要更新时间复杂度又上升到 O(N)。
思路三:
利用线段树二分的思维,将查询与修改的操作都为时间复杂度 O(logN)。

本文步骤解析便实现一种功能——快速求出区间和


推荐学习视频

线段树——建树

为了更形象地将数组体现在线段树上,这里我将数组先初始化:

int arr[6] = {
   1, 3, 5, 7, 9, 11};
int tree[100]={
   0};

线段树的示意图
建树1
ps : 黑色数字为每个节点的权值、红色数字为每个节点在数组 tree 的下标。

通过观察树的结构可得
①、每个叶子节点都是数组 arr 中的元素而且它们从左到右的顺序都是符合 arr 数组上的排序。
②、除了叶子节点外,每个节点上的权值都等于它的左右节点上的权值和。
③、除了叶子节点外,每个节点上的左右子节点的序号都能通过节点序号推导。例如节点的序号为 x,那么它的左子节点的序号为 2 * x + 1,右子节点的序号为 2 * x + 2。

到这里不难想到:线段树是通过二分的方式建立的

为了更加直观,下面的红圈将树补齐为完全二叉树。
建树2


建树代码

//建树代码
void bulid_tree(int node, int start, int end){
   
	//到达叶子节点
	if(start == end){
   
		tree[node] = arr[start];
	}
	else{
   
		int mid = (start + end) / 2;	//二分
		int left_node  = 2 * node + 1;	//推导左子节点
		int right_node = 2 * node + 2;
		bulid_tree(left_node,  start, mid);	//递推
		bulid_tree(right_node, mid+1, end);
		//求得node节点的权值
		tree[node] = tree[left_node] + tree[right_node];
	}
	return ;
} 

线段树——更新

假如我们要改一个 arr 数组中的一个元素,那么我们只要更新根节点到对应元素的叶子节点的路径即可,不必再遍历其他节点,这也正是线段树的优势所在。

例如:当arr[1] = 3 ,修改为 arr[1] = 8。
①、选择路径 0 --> 1 --> 3 --> 8
更新
②、修改路径上节点权值
在这里插入图片描述


更新代码

void updata_tree(int node, int start, int end, int idx, int val){
   
   //找到对应叶子节点
   if(start == end){
   
   	arr[idx]   = val;
   	tree[node] = val;
   }
   else{
   
   	int mid = (start + end) / 2;
   	int left_node  = node * 2 + 1;
   	int right_node = node * 2 + 2;
   	
   	if(idx >= start && idx<=mid){
   
   		updata_tree(left_node,  start, mid, idx, val);
   	}
   	else{
   
   		updata_tree(right_node, mid+1, end, idx, val);
   	}
   	//更新路径上的节点
   	tree[node] = tree[left_node] + tree[right_node]; 
   }
}

线段树——查询

当我们查询 arr 数组区间 [x, y] 的和,我们需要将线段树上对应区间节点相加

例如:查询 arr 数组上 [2, 4] 的区间和。
①、寻找区间上对应的节点。
查询
②、将对应节点上的权值返回到上一级函数,最终返回到主函数的便是 [3, 5] 的区间。

优化:线段树的 2 号节点上的权值 27 恰好等于的是 [3, 5] 的区间和(7+9+11)。我们大可不必一定要查询到叶子节点,若遍历的节点在所要查询的区间内,我们可以直接返回节点上的权值


查询代码

int query_tree(int node, int start, int end, int L, int R){
   
	if(R < start || L > end){
   
		return 0;
	}
	else if((start >= L && end <= R)){
   
		return tree[node];
	}
	else{
   
		int mid = (start + end) / 2;
		int left_node  = 2 * node + 1;
		int right_node = 2 * node + 2;
		
		int sum_left  = query_tree(left_node,  start, mid, L, R);
		int sum_right = query_tree(right_node, mid+1, end, L, R);
		return sum_left + sum_right
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值