小白初始线段树
我一开始学习的线段树是储存数组的前缀和和修改前缀和,时间复杂度都是O(logn)的,因为遇到了省赛有一题是求给定数组中的第1大的数和第2大的数,苦思冥想之后,不会,所以来学习线段树求区间第k大的数。
首先,我学习的线段树的建树过程是利用递归,先遍历左子树,然后遍历右子树,到达叶子节点之后,将叶子节点在数组中表示的范围(范围长度为1)的数组的值赋值给储存树的数组,然后利用递归特性,将子节点的和赋值给父节点,然后创建整个线段树(求前缀和的线段树)。
然后,更新区间值是和建树差不多,利用递归,遍历到目标需要修改的叶子节点,修改叶子节点值后,将其祖宗节点更新,最差情况下遍历整个树也是logn级别的,所以可以使用于数据数量巨大的题目。
代码如下:
#include<iostream>
#include<cmath>
#include<string>
#include<vector>
#include<map>
#include<algorithm>
#include<queue>
#include<cstring>
#include<iomanip>
#include<string.h>
#include<unordered_map>
using namespace std;
#define ll long long
#define ull unsigned long long
#define mod 1000000007
typedef pair<ll, ll> PII;
const int N = 2e5 + 10;
int arr[N];
int tree[4*N + 10];
void build_tree(int arr[], int tree[], int node, int start, int end)
{
if (start == end)//到达叶子节点,将叶子节点的值赋值到存储树的数组之中
{
tree[node] = arr[start];
return;
}
int mid = start + end >> 1;//将范围劈成两半
int left_node = 2 * node + 1;//左孩子节点下标
int right_node = 2 * node + 2;//右孩子节点下标
build_tree(arr, tree, left_node, start, mid);//遍历左子树
build_tree(arr, tree, right_node, mid + 1, end);//遍历右子树
tree[node] = tree[left_node] + tree[right_node];//到达叶子节点之后,将非叶子节点的值更新
}
void up_date(int arr[], int tree[], int node, int start, int end, int idx, int val)
{
if (start == end)//若到达叶子节点,将目标叶子节点的值改为val(目标值)
{
arr[idx] = val;
tree[node] = val;
return;
}
int mid = start + end >> 1;//将范围劈成两半
int left_node = 2 * node + 1;//左孩子节点下标
int right_node = 2 * node + 2;//右孩子节点下标
if (idx <= mid && idx >= start)//若需要修改的目标节点在左子树中,递归遍历左子树
{
up_date(arr, tree, left_node, start, mid, idx, val);
}
else//若需要修改的目标节点在右子树中,递归遍历右子树
{
up_date(arr, tree, right_node, mid + 1, end, idx, val);
}
tree[node] = tree[left_node] + tree[right_node];//叶子节点修改之后,更新修改之后和叶子节点相关的节点
}
int query_tree(int arr[], int tree[], int node, int start, int end, int L, int R)
{
if (end < L || start > R)return 0;//线段树该节点表示范围不在L到R之间直接返回0
else if (start >= L & end <= R)//若线段树中该节点的表示范围在目标范围内,直接返回该节点的节点值
{
return tree[node];
}
int mid = start + end >> 1;//将范围劈成两半
int left_node = 2 * node + 1;//左孩子节点下标
int right_node = 2 * node + 2;//右孩子节点下标
int sum_left = query_tree(arr, tree, left_node, start, mid, L, R);//计算出该节点左子树中在目标范围的节点的数值之和
int sum_right = query_tree(arr, tree, right_node, mid + 1, end, L, R);//计算出该节点右子树中在目标范围的节点的数值之和
return sum_left + sum_right;//返回该节点表示范围内在目标范围内的节点的数值之和
}
int main()
{
ll n;
cin >> n;//输入数组的最终下标(下标从0开始的)
for (int i = 0; i <= n; i++)cin >> arr[i];//输入数组的值
build_tree(arr, tree, 0, 0, n );//建树
return 0;
}