转:

数据结构-PHP 线段树的实现

1.线段树介绍

线段树是基于区间的统计查询,线段树是一种 二叉搜索树,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。使用线段树可以快速的查找某一个节点在若干条线段中出现的次数,时间复杂度为O(logN),线段树是一颗 平衡二叉树

2.线段树示意图

如下图所示,数组 E中,假设区间 0-9 一共 10 个元素,每个儿子节点区间元素的个数都是父亲节点元素个数的一半,若出现 奇数 的情况,则右儿子元素区间比 左儿子 元素区间多一个:

Tips:如图所示的中节点中区间指的是数组 E 的索引值。

3.线段树需要空间分析

假设我们把 线段树 看做是一颗 满二叉树,并且不考虑添加元素的情况(即区间固定),对于区间有 n 个元素的数组若 n=2^k(k是正整数) 则需要 2n 的空间,最差的情况是若 n=2^k+1 则需要 4n 的空间,如下图所示,最下面一层没有元素的节点使用 null 填充:

Tips: 若索引是从 i=0 开始的,左儿子 left(i) = 2*i+1,右儿子 right(i) = 2*i+2parent(i) = (i-1)/2 取整

对于满二叉树来说,需要的节点数如下:

若当 n=2^k+1 需要的的空间数:

Tips:对于区间有 n 个元素的数组若 n=2^k(k是正整数) 则需要 2n 的空间,最差的情况是若 n=2^k+1 则需要 4n 的空间就足够了。

4.定义 SegmentTree 线段树类

其中定义了 leftSon($i) 方法,表示求某个节点左儿子节点索引值的方法,rightSon($i) 表示求某个节点右儿子节点 索引值 的方法:

data[$i] = $arr[$i];
}
//若是静态语言需要开 4n 空间来表示 $this->tree
}
public function getSize() {
return count($this->data);
}
public function get(int $index) {
if ($index < 0 || $index >= count($this->data)) {
echo "索引错误";
exit;
}
return $this->data[$index];
}
/**
* 获取某个节点儿子节点索引,若索引是从 i=0 开始的,左儿子 left(i) = 2*i+1
* @param $i
* @return int
*/
private function leftSon($i): int {
return $i * 2 + 1;
}
/**
* 获取某个节点右儿子节点索引,若索引是从 i=0 开始的,右儿子 left(i) = 2*i+2
* @param $i
* @return int
*/
private function rightSon($i): int {
return $i * 2 + 2;
}
}

5.创建线段树

接下来使用递归思想去 创建线段树,下面给出递归函数 PHP 代码:

 if ($left == $right) {
$this->tree[$i] = $this->data[$left]; //处理递归到叶子节点时 并赋值最原始的 $data 对应的索引值
} else {
$leftSon = $this->leftSon($i); //左儿子索引
$rightSon = $this->rightSon($i); //右儿子索引
$mid = $left + ceil(($right - $left) / 2);//求区间中值
$this->buildSegmentTree($leftSon, $left, $mid - 1); //递归左儿子树
$this->buildSegmentTree($rightSon, $mid, $right); //递归右儿子树
$this->tree[$i] = $this->merge->operate($this->tree[$leftSon], $this->tree[$rightSon]); //这里是根据业务来定节点需要存储的元素
}

Tips:其中节点元素存储的值需要根据业务来定,如上面代码表示的是每个节点存储的是 区间求和 的值,很显然这种方式不灵活,用户在实例化该类的时候可以传入一个 merge 对象用于元素操作的。

6.节点元素计算规则

上述SegmentTree类中可以在 __construct() 方法中传入一个 $merge 对象,$merge 中可以定义一个 operate() 方法计算得出节点元素值,如下:

merge = $merge;
for ($i = 0; $i < count($arr); $i++) {
$this->data[$i] = $arr[$i];
}
//若是静态语言需要开 4n 空间来表示 $this->tree
//递归创建线段树
$this->buildSegmentTree(0, 0, count($this->data) - 1);
}
private function buildSegmentTree(int $i, int $left, int $right) {
if ($left == $right) {
$this->tree[$i] = $this->data[$left]; //处理递归到叶子节点时 并赋值最原始的 $data 对应的索引值
} else {
$leftSon = $this->leftSon($i); //左儿子索引
$rightSon = $this->rightSon($i); //右儿子索引
$mid = $left + ceil(($right - $left) / 2);//求区间中值
$this->buildSegmentTree($leftSon, $left, $mid - 1); //递归左儿子树
$this->buildSegmentTree($rightSon, $mid, $right); //递归右儿子树
$this->tree[$i] = $this->merge->operate($this->tree[$leftSon], $this->tree[$rightSon]); //这里是根据业务来定节点需要存储的元素
}
}
public function getSize() {
return count($this->data);
}
public function get(int $index) {
if ($index < 0 || $index >= count($this->data)) {
echo "索引错误";
exit;
}
return $this->data[$index];
}
/**
* 获取某个节点儿子节点索引,若索引是从 i=0 开始的,左儿子 left(i) = 2*i+1
* @param $i
* @return int
*/
private function leftSon($i): int {
return $i * 2 + 1;
}
/**
* 获取某个节点右儿子节点索引,若索引是从 i=0 开始的,右儿子 left(i) = 2*i+2
* @param $i
* @return int
*/
private function rightSon($i): int {
return $i * 2 + 2;
}
}
6.1 Merge 类定义

如下定义就可以很灵活的处理每个节点的计算规则:

class Merge{
public funcrion operate($left,$right){
//这里可以定义需要操作的规则
return $left+$right; //如求平均值,这里可以 return ($left+$right)/2;
}
}

7. 求和演示

若是各个线段区间存储的是区间求和,则 Merge 类中的 operate() 方法返回是两个元素的,代码如下:

输出如下:

此时线段树的节点元素值示意图如下:

8. 线段树的区间查询

这里以查询 [2-6] 区间为例,若要查询区间 [2-6] 的求和需要根据区间来寻找需要求的值,示意图如下:

PHP 代码使用递归思想实现如下:

 public function query($qleft, $qright) {
if ($qleft < 0 || $qright >= count($this->data) || $qright < $qleft) {
echo "索引范围错误";
exit;
}
return $this->recursionQuery(0, 0, count($this->data) - 1, $qleft, $qright);
}
/**
* 递归查询区间
* @param $left 当前节点区间左端值
* @param $right 当前节点区间右端值
* @param $qleft 需要查询的区间左端值
* @param $qright 需要查询的区间右端值
*/
private function recursionQuery($i, $left, $right, $qleft, $qright) {
$mid = $left + ceil(($right - $left) / 2);//求区间中值向上取整
//先处理满足区间条件的情况
if ($qleft == $left && $qright == $right) { //查询左右端和当前节点左右端重合
return $this->tree[$i];
} elseif ($qright < $mid) { //查询左右端在中值左边,那么结果区间在左儿子树
return $this->recursionQuery($this->leftSon($i), $left, $mid - 1, $qleft, $qright);
} elseif ($qleft >= $mid) { //查询左右端在中值右边,那么结果区间在右儿子树
return $this->recursionQuery($this->rightSon($i), $mid, $right, $qleft, $qright);
} else { //中值在查询左右端中间 将区间分成两边,结果在左右儿子树上都有
$leftSon = $this->recursionQuery($this->leftSon($i), $left, $mid - 1, $qleft, $mid - 1);
$righttSon = $this->recursionQuery($this->rightSon($i), $mid, $right, $mid, $qright);
return $this->merge->operate($leftSon, $righttSon);
}
}

输出如下:

9.完整 PHP 代码

9.1 SegmentTree 类

merge = $merge;
for ($i = 0; $i < count($arr); $i++) {
$this->data[$i] = $arr[$i];
}
//若是静态语言需要开 4n 空间来表示 $this->tree
//递归创建线段树
$this->buildSegmentTree(0, 0, count($this->data) - 1);
}
public function query($qleft, $qright) {
if ($qleft < 0 || $qright >= count($this->data) || $qright < $qleft) {
echo "索引范围错误";
exit;
}
return $this->recursionQuery(0, 0, count($this->data) - 1, $qleft, $qright);
}
/**
* 递归查询区间
* @param $left 当前节点区间左端值
* @param $right 当前节点区间右端值
* @param $qleft 需要查询的区间左端值
* @param $qright 需要查询的区间右端值
*/
private function recursionQuery($i, $left, $right, $qleft, $qright) {
$mid = $left + ceil(($right - $left) / 2);//求区间中值向上取整
//先处理满足区间条件的情况
if ($qleft == $left && $qright == $right) { //查询左右端和当前节点左右端重合
return $this->tree[$i];
} elseif ($qright < $mid) { //查询左右端在中值左边,那么结果区间在左儿子树
return $this->recursionQuery($this->leftSon($i), $left, $mid - 1, $qleft, $qright);
} elseif ($qleft >= $mid) { //查询左右端在中值右边,那么结果区间在右儿子树
return $this->recursionQuery($this->rightSon($i), $mid, $right, $qleft, $qright);
} else { //中值在查询左右端中间 将区间分成两边,结果在左右儿子树上都有
$leftSon = $this->recursionQuery($this->leftSon($i), $left, $mid - 1, $qleft, $mid - 1);
$righttSon = $this->recursionQuery($this->rightSon($i), $mid, $right, $mid, $qright);
return $this->merge->operate($leftSon, $righttSon);
}
}
private function buildSegmentTree(int $i, int $left, int $right) {
if ($left == $right) {
$this->tree[$i] = $this->data[$left]; //处理递归到叶子节点时 并赋值最原始的 $data 对应的索引值
} else {
$leftSon = $this->leftSon($i); //左儿子索引
$rightSon = $this->rightSon($i); //右儿子索引
$mid = $left + ceil(($right - $left) / 2);//求区间中值
$this->buildSegmentTree($leftSon, $left, $mid - 1); //递归左儿子树
$this->buildSegmentTree($rightSon, $mid, $right); //递归右儿子树
$this->tree[$i] = $this->merge->operate($this->tree[$leftSon], $this->tree[$rightSon]); //这里是根据业务来定节点需要存储的元素
}
}
public function getSize() {
return count($this->data);
}
public function get(int $index) {
if ($index < 0 || $index >= count($this->data)) {
echo "索引错误";
exit;
}
return $this->data[$index];
}
/**
* 获取某个节点儿子节点索引,若索引是从 i=0 开始的,左儿子 left(i) = 2*i+1
* @param $i
* @return int
*/
private function leftSon($i): int {
return $i * 2 + 1;
}
/**
* 获取某个节点右儿子节点索引,若索引是从 i=0 开始的,右儿子 left(i) = 2*i+2
* @param $i
* @return int
*/
private function rightSon($i): int {
return $i * 2 + 2;
}
}

9.2 输出演示代码

query(2,6);

代码仓库 :https://gitee.com/love-for-po...

扫码关注爱因诗贤

转:

数据结构-PHP 线段树的实现

数据结构-PHP 线段树的实现的更多相关文章

  1. 【Foreign】数据结构C [线段树]

    数据结构C Time Limit: 20 Sec  Memory Limit: 512 MB Description Input Output Sample Input Sample Output H ...

  2. 【数据结构】线段树(Segment Tree)

    假设我们现在拿到了一个非常大的数组,对于这个数组里面的数字要反复不断地做两个操作. 1.(query)随机在这个数组中选一个区间,求出这个区间所有数的和. 2.(update)不断地随机修改这个数组中 ...

  3. 数据结构1 线段树查询一个区间的O(log N) 复杂度的证明

    线段树属于二叉树, 其核心特征就是支持区间加法,这样就可以把任意待查询的区间$[L, R]$分解到线段树的节点上去,再把这些节点的信息合并起来从而得到区间$[L,R]$的信息. 下面证明在线段树上查询 ...

  4. 数据结构(线段树):Educational Codeforces Round 6 620E. New Year Tree

    E. New Year Tree time limit per test 3 seconds memory limit per test 256 megabytes input standard in ...

  5. 数据结构(线段树):BZOJ 1568 [JSOI2008]Blue Mary开公司

    1568: [JSOI2008]Blue Mary开公司 Time Limit: 15 Sec  Memory Limit: 162 MBSubmit: 602  Solved: 214[Submit ...

  6. 牛客练习赛28 B数据结构(线段树)

    链接:https://www.nowcoder.com/acm/contest/200/B来源:牛客网 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 262144K,其他语言5242 ...

  7. 2018.10.12 NOIP模拟 数据结构(线段树)

    传送门 sb线段树题居然还卡常. 修改操作直接更新区间最小值和区间标记下传即可. 询问加起来最多5e65e65e6个数. 因此直接询问5e65e65e6次最小值就行了. 代码

  8. 【uoj#228】基础数据结构练习题 线段树+均摊分析

    题目描述 给出一个长度为 $n$ 的序列,支持 $m$ 次操作,操作有三种:区间加.区间开根.区间求和. $n,m,a_i\le 100000$ . 题解 线段树+均摊分析 对于原来的两个数 $a$ ...

  9. 数据结构习题 线段树&树状数组

    说明:这是去年写了一半的东西,一直存在草稿箱里,今天整理东西的时候才发现,还是把它发表出来吧.. 以下所有题目来自Lrj的<训练指南> LA 2191 单点修改,区间和  Fenwick直 ...

随机推荐

  1. hdu 6703 array(权值线段树)

    Problem Description You are given an array a1,a2,...,an(∀i∈[1,n],1≤ai≤n). Initially, each element of ...

  2. 2019牛客暑期多校训练营(第五场)H.subsequence 2(拓扑)

    题意:给你一个字符串的长度n 现在询问了m*(m-1)/2次 每次都可以询问两个字符 然后 会告诉你只留下这两个字符后 字符串的样子 现在问你能不能还原字符串 如果能就输出字符串 否则输出-1 思路: ...

  3. P3164 [CQOI2014]和谐矩阵(高斯消元 + bitset)

    题意:构造一个$n*m$矩阵 使得每个元素和上下左右的xor值=0 题解:设第一行的每个元素值为未知数 可以依次得到每一行的值 然后把最后一行由题意条件 得到$m$个方程 高斯消元解一下 bitset ...

  4. hdu4686 Arc of Dream

    Time Limit: 2000/2000 MS (Java/Others)    Memory Limit: 65535/65535 K (Java/Others) Total Submission ...

  5. CodeForces - 1201B Zero Array

    You are given an array a1,a2,-,ana1,a2,-,an. In one operation you can choose two elements aiai and a ...

  6. [视频] 使用 JavaCV 来显示和保存来自摄像头的视频

    目录 JavaCV 预览本机摄像头视频图像的简单实现 预览的同时,保存为FLV文件 JavaCV javacv开发包是用于支持java多媒体开发的一套开发包,可以适用于本地多媒体(音视频)调用以及音视 ...

  7. 实战交付一套dubbo微服务到k8s集群(4)之dubbo微服务底包镜像制作

    1.下载jre镜像 在运维主机(mfyxw50.mfyxw.com)操作 [root@mfyxw50 ~]# docker pull registry.cn-hangzhou.aliyuncs.com ...

  8. 牛年 dotnet云原生技术趋势

    首先祝大家:新年快乐,牛年大吉,牛年发发发! 2020年的春节,新冠疫情使得全球业务停滞不前,那时候,没有人知道会发生什么,因此会议被取消,合同被搁置,项目被推迟,一切似乎都停止了.但是我们却见证了I ...

  9. Eazfuscator.NET(.net混淆工具)

    软件功能 调试支持: 在你的程序集被Eazfuscator.NET混淆后,它不会成为不可调试混乱的砖块.你总是可以得到一个行号,出现未处理的异常,查看可读的堆栈跟踪,甚至附加调试器来遍历你的模糊代码. ...

  10. 实现基于股票收盘价的时间序列的统计(用Python实现)

    时间序列是按时间顺序的一组真实的数字,比如股票的交易数据.通过分析时间序列,能挖掘出这组序列背后包含的规律,从而有效地预测未来的数据.在这部分里,将讲述基于时间序列的常用统计方法. 1 用rollin ...