初识线段树(还没有学lazy标识)

                                                                小白初始线段树

我一开始学习的线段树是储存数组的前缀和和修改前缀和,时间复杂度都是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;
}
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值