• 线段树


    1. 背景

       假设现在有一个非常大的数组 arr,对数组里面的数字需要反复做两个操作:

           1)随机的选择一块区间,然后对区间里面的所有数字求和。

           2)随机修改数组里面的某一个值,即更新操作。

       因为数组是随机存取的,所以更新操作很容易,其时间复杂度是 $O(1)$。

       对于求和操作,其时间复杂度取决于区间的长度,假设区间长度为 $L$,则求和操作的时间复杂度为 $O(L)$。

       那有没有办法降低求和操作的时间复杂度呢?

       首先我们想到的是另外建立一个数组 sumArr,这个数组存储的是 arr 数组的前缀和,即

    $$sumArr[i] = sum_{j=0}^{i}arr[j]$$

       要求某段的和,只需在 sumArr 数组里找到对应下标做一次减法操作就可以了,时间复杂度变成了 $O(1)$。

       这样做的话更新操作的时间复杂度上升了,因为每次更新 arr 数组中的一个元素,都得更新数组 sumArr。

       那有没有办法平衡一下求和更新两个操作的时间复杂度呢?

       下面介绍线段树。

    2. 线段树

       以下面数组进行举例:

           

       线段树本身是一个二叉树,每个结点代表一段区间,结点值就是区间内所有元素的和。

       对于线段树中的每一个非叶子节点 $[a,b]$,它的左儿子表示的区间为 $[a,frac{a+b}{2}]$,右儿子表示的区间为 $[frac{a+b}{2}+1,b]$,

       因此线段树是平衡二叉树。

       针对上面的数组,可以建立如下线段树:

           

       可以看出每个叶子结点就是单个元素。

       我们将根节点标号为 0,那么对于任意一个结点 $i$,其左孩子的结点 $id$ 为 $left = 2i + 1$,右孩子的结点 $id$ 为 $right = 2i + 2$。

       因为其本身是一个平衡二叉树,所以可以用数组存储,浪费的空间小。

    #include <stdio.h>
    
    #define MAX_LEN 1000
    
    void BuildTree(int arr[], int tree[], int node, int start, int end)
    {
        if(start == end) {
            tree[node] = arr[start];
        }
        else {
            int mid = (start + end) / 2;
            int leftNode = node * 2 + 1;
            int rightNode = node * 2 + 2;
    
            BuildTree(arr, tree, leftNode, start, mid);
            BuildTree(arr, tree, rightNode, mid + 1, end);
    
            tree[node] = tree[leftNode] + tree[rightNode];
        }
    }
    
    void UpdateTree(int arr[], int tree[], int node, int start, int end, int idx, int value)
    {
        if (start == end) {
            arr[idx] = value;
            tree[node] = value;
        }
        else {
            int mid = (start + end) / 2;
            int leftNode = node * 2 + 1;
            int rightNode = node * 2 + 2;
    
            if (idx >= start && idx <= mid) {
                UpdateTree(arr, tree, leftNode, start, mid, idx, value);
            }
            else {
                UpdateTree(arr, tree, rightNode, mid + 1, end, idx, value);
            }
    
            tree[node] = tree[leftNode] + tree[rightNode];
        }
    }
    
    int QueryTree(int arr[], int tree[], int node, int start, int end, int L, int R)
    {
        if (R < start || L > end) return 0;
        if (start == end) return tree[node];
        if(L <= start && end <= R) return tree[node];
    
        int mid = (start + end) / 2;
        int leftNode = node * 2 + 1;
        int rightNode = node * 2 + 2;
        int sumLeft = QueryTree(arr, tree, leftNode, start, mid, L, R);
        int sunRight = QueryTree(arr, tree, rightNode, mid + 1, end, L, R);
    
        return sumLeft + sunRight;
    }
    
    int main()
    {
        int arr[] = {1,3,7,9,11};
        int size = 6;
        int tree[MAX_LEN] = {0};
    
        BuildTree(arr, tree, 0 , 0, size - 1);
    
        return 0;
    }
    
  • 相关阅读:
    我的知识库(4) java获取页面编码(Z)
    知识库(3)JAVA 正则表达式 (超详细)
    The Struts dispatcher cannot be found. This is usually caused by using Struts tags without the associated filter. Struts
    某人总结的《英语听力的技巧 》,挺搞的
    我的知识库(5)java单例模式详解
    构建可扩展程序
    SerialPort (RS232 Serial COM Port) in C# .NET
    Python学习笔记——String、Sequences
    UI题目我的答案
    jQuery学习系列学会操纵Form表单元素(1)
  • 原文地址:https://www.cnblogs.com/yanghh/p/13660603.html
Copyright © 2020-2023  润新知