欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

初级线段树小结

程序员文章站 2022-06-20 20:08:59
线段树是一种高效的维护区间的数据结构, 他是通过树的特点,进行了区间的二分法, 通过不断地分治、递归,完成了区间数据的高效管理与维护! 为了区间的方便书写, 我们常常把线段树的区间取为 2 的幂, 方便进行区间的二分, 与形成一个完美二叉树(与完全二叉树有些许的区别, 完全二叉树可以在最后一层中缺少 ......

  线段树是一种高效的维护区间的数据结构, 他是通过树的特点,进行了区间的二分法, 通过不断地分治、递归,完成了区间数据的高效管理与维护!

  为了区间的方便书写, 我们常常把线段树的区间取为 2 的幂, 方便进行区间的二分, 与形成一个完美二叉树(与完全二叉树有些许的区别, 完全二叉树可以在最后一层中缺少若干节点。)

  

 

  线段树常常会有一下几种操作, 包括

  1.线段树的初始化;(注意初始化过程当中的规模确定,确定线段树的大小)

  2. 修改某个节点的数值;(修改过程中,需要不断地对区间进行维护)

  3. 求某段区间的最小值  (这里说的是维护最小值的线段树,当然递归的话, 你也可以维护最大值!)

 

 

  关于以上三种操作的实现:

/*
    在此处我们建立的是维护最小值的线段树, 维护的是其他的话吗,仅仅只需要改一些初始化以及更新的细节点
*/
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <string>
#include <cctype>
#include <algorithm>
#include <vector>
#include <queue>
#include <list>
#include <map>
#include <stack>
#include <set>
using namespace std;

const int inf = 1e8;            // 因为是维护最小值的线段树, 我们初始化当然是最大值
const int max = 1 << 17;        // segment tree 最多有这么多节点
int date[max*2 - 1];            //  但是还要加一倍减一 , 是因为它上面还有node
int n;                          //全局变量, 用来表示线段树的规模;
//初始化
void initial(int n_){           //n_ 是你原本需要数据的规模
    n = 1;
    while(n < n_)   n <<= 1;       // 为了方便, 把线段树的底层规模设置成为了 2 的幂次 ,由n_扩大到了 n !
    printf("n : %d \n", n);
    for(int i = 0; i < 2 * n - 1; i++)  date[i] = inf;
}
//将下标索引k的元素更新为 a; 这里的 k 索引下标是叶子点从0开始输入的数据
void update(int k, int a){
    k += n - 1;                 //直接定位到叶子节点的位置, 进行更新
    date[k] = a;
    while(k > 0){
        k = (k - 1) / 2;        //定位父节点
        date[k] = min(date[2*k+1], date[2*k+2]);
    }
}
// 求的是区间 [a , b) 当中的维护值, [ l ,r )似的是他在该区间的结果;
// k 是节点的编号, 当然 [l, r) 正是这个节点对应的区间
// 注意点, a , b 的区间不是 在线段树上面的区间; 而是你输入数据的区间
int query(int a, int b, int k, int l, int r){
    if(r <= a||l >= b)  return inf;     //区间根本不存在交集的情况
    if(a <= l&&b >= r)  return date[k];
    else{   //那就是仅仅只有部分交点的情况
        int x = query(a, b, 2 * k + 1, l, (l + r) / 2); //左节点
        int y = query(a, b, 2 * k + 2, (l + r) / 2, r); //右节点
        return min(x, y);
    }
}
int main()
{
    printf("the start:\n");
    initial(16);
    printf("the initial if finished\n");
    for(int i = 0; i < 16; i++){
        update(i, 16 - i);
    }
    for(int i = 0; i < 16; i++){
        printf("%d ", 16 - i);
    }
    cout<<endl;
    printf("%d\n", query(0, 16, 0, 0, 16));
    printf("%d\n", query(0, 10, 0, 0, 16));
    printf("%d\n", query(12, 16, 0, 0, 16));
    return 0;
}

 

 

  操作时候的注意事项:

  1. 随我们我们是 2n 个元素, 但是我们的线段树区间要求的是 2n+1 - 1 个; 这个是因为我们的基础元素所处的是叶子,  他们需要父节点进行记录数值。  父节点是维护的数据!

  2. 注意我们的数组是由零开始, 和由 1 开始不同, 他们父节点和左右儿子的所以下表关系有着些许的变化

  eg :  由零开始: parent : x  --->  leftson : x / 2 + 1 ; rightson : x / 2 + 2;

      由一开始: parent : x  --->  leftson : x / 2 + 0 ; rightson : x / 2 + 1;

  3. 更新的操作是基于叶子节点的操作, 因为要直接定位到叶子节点的位置;

  4. 我们进行区间的操作的时候, 可以从下标, 来进行推导区间, 但是太繁琐(仅代表个人观点), 不是直接将区间的范围放入函数的参数当中!  注意在外部调用时候,需要的是 query(a, b, 0, 0, n);

  5. 在初始化时候要注意, 他是 n <<= 1;  不要写成了 n>>1; 这个亚子的话是死循环!