高级数据结构 | 线段树的入门与入门

高级数据结构 | 线段树的入门与入门

高级数据结构 | 一步一步理解线段树


  • 目录与索引

    一、啥是线段树

    二、从一个引例理解线段树之美(雾

    三、线段树的实战训练


     

 

到底啥是线段树

线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。
对于线段树中的每一个非叶子节点[a,b],它的左儿子表示的区间为[a,(a+b)/2],右儿子表示的区间为[(a+b)/2+1,b]。因此线段树是平衡二叉树,最后的子节点数目为N,即整个线段区间的长度。
使用线段树可以快速的查找某一个节点在若干条线段中出现的次数,时间复杂度为O(logN)。

也许你会觉得这个解释对于你有些晦涩难懂,没事,下面我们通过一个例子,进一步体验线段树的魅力(雾


一个简单的引例

问题描述(小黑的不稳定数列)

总所周知,小黑有一串十分神奇的不稳定Cys数列。在这串长度为M的不稳定数列中,其中数列的大小固定,但数列中的数据会发生T次乱码及爆炸,在每一次乱码爆炸发生后,数列中的部分元素值会被随机更新,变得面目全非.

现在,小黑想请你帮个忙:即在每一次数列乱码爆炸后,告诉小黑该税额某个区间内的最小值.

  • How to solve this problem?

  • 思路①:对于每一次的数组变化,我们采用遍历数据的区间去寻找数组的最小值。显而意见,该种方法的时间复杂度为O(N),额外的空间复杂度为O(1),而完成T次的寻找泽则需要O(TN)的时间复杂度。作为一种朴素算法,当数据量足够小时,我们显然可以利用这种方法轻松AC(轻松??)。但不可忽视的,当T与N相对大时,即我们要进行的查询操作十分频繁时,耗时可能会不满足需求。另一种解法:使用一个二维数组来保存提前计算好的区间[i,j]内的最小值,那么预处理时间为O(n^2),查询耗时O(1), 但是需要额外的O(n^2)空间,当数据量很大时,这个空间消耗是庞大的,而且当改变了数组中的某一个值时,更新二维数组中的最小值也很麻烦。
  • 思路②:我们可以使用一个二维数组来保存提前计算好的区间[i,j]内的最小值,那么预处理时间复杂度为O(n^2),查询耗时O(1), 但是需要额外的O(n^2)空间,当数据量很大时,这个空间消耗是庞大的,而且当改变了数组中的某一个值时,更新二维数组中的最小值也是很耗时且麻烦的。
  • 思路③:我们可以采用线段树来解决这个问题。对于这个问题,我们可以构建一个如图1.1的理想线段树(类似于一棵满二叉树)模型:

     

       
  • 叶子节点是原始组数Cys中的元素
  • 非叶子节点代表它的所有子孙叶子节点所在区间的最小值

例如对于数列[2, 5, 1, 4, 9, 3],我们可以构造如图1.1的二叉树.特憋需要注意的是,如果你连二叉树都不会,那么请你关闭此帖:

我们可以用线段树来解决这个问题:预处理耗时O(n),查询、更新操作O(logn),需要额外的空间O(n)。根据这个问题我们构造如下的二叉树(背景为白色表示叶子节点,非叶子节点的值是其对应数组区间内的最小值,例如根节点表示数组区间Cys0[0..5]内的最小值是1)

如果你学过树的话,那么你容易发现:线段树近似一棵满二叉树,那么如果要存储线段树,那么需要的空间复杂度是O(n),预处理耗时O(n)。

知道这些后,那么问题来了——对于线段树的操作:创建线段树、查询、节点更新又是如何运作的呢?


About·如何建立线段树

[ccen_cpp]
#include  
using namespace std; 
 
const int maxind = 256; 
int segTree[maxind * 4 + 10]; 
int array[maxind]; 
/* 构造函数,得到线段树 */ 
void build(int node, int begin, int end) 
{ 
 if (begin == end) 
 segTree[node] = array[begin]; /* 只有一个元素,节点记录该单元素 */ 
 else 
 { 
 /* 递归构造左右子树 */ 
 build(2*node, begin, (begin+end)/2); 
 build(2*node+1, (begin+end)/2+1, end); 
 
 /* 回溯时得到当前node节点的线段信息 */ 
 if (segTree[2 * node] <= segTree[2 * node + 1]) 
 segTree[node] = segTree[2 * node]; 
 else 
 segTree[node] = segTree[2 * node + 1]; 
 } 
} 
 
int main() 
{ 
 array[0] = 1, array[1] = 2,array[2] = 2, array[3] = 4, array[4] = 1, array[5] = 3; 
 build(1, 0, 5); 
 for(int i = 1; i<=20; ++i) 
 cout<< "seg"<< i << "=" <

About·如何进行区间查询

 


工欲善其事  必先利其器

高级数据结构 | 线段树的入门与入门

高级数据结构 | 一步一步理解线段树


  • 目录与索引

    一、啥是线段树

    二、从一个引例理解线段树之美(雾

    三、线段树的实战训练


     

 

到底啥是线段树

线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。
对于线段树中的每一个非叶子节点[a,b],它的左儿子表示的区间为[a,(a+b)/2],右儿子表示的区间为[(a+b)/2+1,b]。因此线段树是平衡二叉树,最后的子节点数目为N,即整个线段区间的长度。
使用线段树可以快速的查找某一个节点在若干条线段中出现的次数,时间复杂度为O(logN)。

也许你会觉得这个解释对于你有些晦涩难懂,没事,下面我们通过一个例子,进一步体验线段树的魅力(雾


一个简单的引例

问题描述(小黑的不稳定数列)

总所周知,小黑有一串十分神奇的不稳定Cys数列。在这串长度为M的不稳定数列中,其中数列的大小固定,但数列中的数据会发生T次乱码及爆炸,在每一次乱码爆炸发生后,数列中的部分元素值会被随机更新,变得面目全非.

现在,小黑想请你帮个忙:即在每一次数列乱码爆炸后,告诉小黑该税额某个区间内的最小值.

  • How to solve this problem?

  • 思路①:对于每一次的数组变化,我们采用遍历数据的区间去寻找数组的最小值。显而意见,该种方法的时间复杂度为O(N),额外的空间复杂度为O(1),而完成T次的寻找泽则需要O(TN)的时间复杂度。作为一种朴素算法,当数据量足够小时,我们显然可以利用这种方法轻松AC(轻松??)。但不可忽视的,当T与N相对大时,即我们要进行的查询操作十分频繁时,耗时可能会不满足需求。另一种解法:使用一个二维数组来保存提前计算好的区间[i,j]内的最小值,那么预处理时间为O(n^2),查询耗时O(1), 但是需要额外的O(n^2)空间,当数据量很大时,这个空间消耗是庞大的,而且当改变了数组中的某一个值时,更新二维数组中的最小值也很麻烦。
  • 思路②:我们可以使用一个二维数组来保存提前计算好的区间[i,j]内的最小值,那么预处理时间复杂度为O(n^2),查询耗时O(1), 但是需要额外的O(n^2)空间,当数据量很大时,这个空间消耗是庞大的,而且当改变了数组中的某一个值时,更新二维数组中的最小值也是很耗时且麻烦的。
  • 思路③:我们可以采用线段树来解决这个问题。对于这个问题,我们可以构建一个如图1.1的理想线段树(类似于一棵满二叉树)模型:

     

    图1.1 – 小黑的不稳定数列题图
  • 叶子节点是原始组数Cys中的元素
  • 非叶子节点代表它的所有子孙叶子节点所在区间的最小值

例如对于数列[2, 5, 1, 4, 9, 3],我们可以构造如图1.1的二叉树.特憋需要注意的是,如果你连二叉树都不会,那么请你关闭此帖:

我们可以用线段树来解决这个问题:预处理耗时O(n),查询、更新操作O(logn),需要额外的空间O(n)。根据这个问题我们构造如下的二叉树(背景为白色表示叶子节点,非叶子节点的值是其对应数组区间内的最小值,例如根节点表示数组区间Cys0[0..5]内的最小值是1)

如果你学过树的话,那么你容易发现:线段树近似一棵满二叉树,那么如果要存储线段树,那么需要的空间复杂度是O(n),预处理耗时O(n)。

知道这些后,那么问题来了——对于线段树的操作:创建线段树、查询、节点更新又是如何运作的呢?


About·如何建立线段树

[ccen_cpp]
#include  
using namespace std; 
 
const int maxind = 256; 
int segTree[maxind * 4 + 10]; 
int array[maxind]; 
/* 构造函数,得到线段树 */ 
void build(int node, int begin, int end) 
{ 
 if (begin == end) 
 segTree[node] = array[begin]; /* 只有一个元素,节点记录该单元素 */ 
 else 
 { 
 /* 递归构造左右子树 */ 
 build(2*node, begin, (begin+end)/2); 
 build(2*node+1, (begin+end)/2+1, end); 
 
 /* 回溯时得到当前node节点的线段信息 */ 
 if (segTree[2 * node] <= segTree[2 * node + 1]) 
 segTree[node] = segTree[2 * node]; 
 else 
 segTree[node] = segTree[2 * node + 1]; 
 } 
} 
 
int main() 
{ 
 array[0] = 1, array[1] = 2,array[2] = 2, array[3] = 4, array[4] = 1, array[5] = 3; 
 build(1, 0, 5); 
 for(int i = 1; i<=20; ++i) 
 cout<< "seg"<< i << "=" <

About·如何进行区间查询

int Read(){
	
    int x = 0;
    char c = getchar();
    while(c < '0' || c > '9') c = getchar();
    while(c >= '0' && c <= '9') x = x*10 + c-'0' , c = getchar();
    return x;

}

工欲善其事  必先利其器

你可能感兴趣的:(Noip竞赛历程)