线段树

来源:互联网 发布:模拟美股软件 编辑:程序博客网 时间:2024/04/30 07:57

2016.10.19

//经过看他人的博客学习到了有关于线段树的某些知识

2017.3.10

//今天终于明白了,之前我的线段树不标准导致超时等一系列猥琐的东西。


一、线段树是什么

1.1 二叉搜索树

线段树是一种二叉搜索树,与区间数差不多。在我的理解里,他就是一棵树,知识每个节点存的是一个区间及其对应的数据。

使用线段树可以快速的查找某一个节点在若干条线段中出现的次数,时间复杂度为O(logn)。

1.2 一些性质 

对于一个父亲节点,他的区间为[a,b],他的左节点的区间是[a,(a+b)/2],右儿子的区间是[(a+b)/2+1,b],也就是把一个区间平均分成两个区间。

二、了解线段树实际运用

2.1 题目

给你一串数字,长度为n,在给定m个区间[x,y],求这m个区间中一个最小值。

2.2 普通解题思路:

(1)对于每一个区间,可以线性枚举。O(nm),但是n非常大的时候,是不可行的。

(2)预处理出x~y的最小值,时间复杂度是O(n²),但是n也有可能很大,会爆掉。

2.3 线段树解题思路

预处理耗时O(n),对于每次查找需要O(logn),空间是(2n),下面是解题思路:

输入的数字为[2,5,1,4,9,3]时,可绘制如下线段树:

       

由于线段树的父节点区间是平均分割到左右子树,因此线段树是完全二叉树,对于包含n个叶子节点(黄色)的完全二叉树,它一定有n-1个非叶节点(橙色),总共2n-1个节点,因此存储线段是需要的空间复杂度是O(2n)。那么线段树的操作:创建线段树、查询、节点更新 是如何运作的呢?

三、实现线段树

3.1 构造线段树

构建一颗线段树,就像上面那幅图片一样。

怎么构建呢?我们可以用递归来实现

每次递归他的左子树和右子树,用一个tree数组,表示第i个节点所储存的数是多少,在这题中,是最小值。

代码如下

CODE PASCAL

procedure build(root,l,r:longint);//root表示当前节点标号,l,r表示区间[l,r]begin        tree_l[root]:=l;//存取区间的左边        tree_r[root]:=r;//存区间的右边        tree_s[root]:=0;//这个是这个区间对应的数据,就像上面的最小值一样        if (l<>r) then//如果是叶子节点,也就是区间只有1个数字,就不需要递归了        begin                build(2*root,l,(l+r) div 2);//按照如上讲解及其线段树性质递归                build(2*root+1,(l+r) div 2+1,r);        end;end;
3.2 赋予线段树每个节点一个值

看到这里,不妨看看下面的图:

 

观察到了?一个节点和他的两个子节点有什么规律?就是两个节点最小值?没错,因为你分成了一个个区间,那么合起来,这个区间的最小值就是你分成若干个区间,每个区间的最小值。

CODE PASCAL

procedure num(root:longint);//root仍然表示序号begin        if tree_l[root]=tree_r[root] then//如果是叶子节点                tree_s[root]:=a[tree_l[root]]//他的值只能等于自己了        else        begin                num(root*2);                num(root*2+1);//往下递归两个子节点                tree_s[root]:=min(tree_s[root*2],tree_s[root*2+1]);//递归完两个子节点,知道他们的值,当前节点就是两个子节点的最小值        end;end;

为了方便理解,分成了两段程序,其实是可以合并起来的,请选手自行操作。

3.3 查询线段树

既然已经构造好了线段树,那么如何用这个生成了的线段树,寻找一个区间的最小值呢?

因为有些区间是查找不到的,例如[1,5]是找不到的。但是,我们可以通过[1,3]和[4,5]得知[1,5]的最小值为min(1,4)=1。总而言之,言而总之,只要是包括在[1,5]的区间,都寻找一次,找出最小值即可。

因此,线段树适合解决"相邻的区间信息可以被合并成两个区间的并区间的信息"的问题。

具体见如下代码实现:

CODE PASCAL

procedure find(root:longint);//root表示当前序号为root的线段树节点。find_l,find_r表示当前要找的[find_l,find_r]区间最小值begin        if tree_l[root]=tree_r[root] then                exit;//叶子节点直接退出        if (tree_l[root]<=find_l)and(tree_r[root]>=find_r) then//如果当前寻找到的区间被需要寻找的区间完全覆盖        begin                if tree_s[root]<min then                        min:=tree_s[root];//去最小值                exit;//下面必定是这个区间里的区间,不必寻找了        end;        dg(root*2);        dg(root*2+1);//递归end;

                 若不理解,可以画图理解一下。

          3.4 单节点更新

              就是某个叶子节点更新了值,那么他这个东西,只跟他的老爸父亲,爷爷,爷爷的爸爸,爷爷的爷爷,夜夜夜的爷爷也有关,这个差不多是跟堆的维护一样,所以我们递归回去他的父亲,若他的父亲因为他而光荣,呸是与他同一代的叔叔小(大)的话,那么他老爸也要更新,如此即可。
              CODE PASCAL

procedure num(root:longint);//root表示序号,jd表示你要更新的是第几个数,更新成numbegin        if (tree_l[root]=tree_r[root])and(tree_l[root]=jd) then//你找到一个叶子节点,表示的正是jd                tree_s[root]:=num;//更新为要搞的        num(root*2);        num(root*2+1);//往下递归两个子节点        tree_s[root]:=min(tree_s[root*2],tree_s[root*2+1]);//递归完两个子节点,知道他们的值,当前节点就是两个子节点的最小值end;
          3.5 区间更新 

               







1 0
原创粉丝点击