时间序列线段树

可以以O(log2(n))的时间复杂度处理任何日期序列的区间点修改和统计问题,例如维护区间和、区间内最大最小值平均数、区间内最大公因数、区间内的逆序数个数。以下为一个维护时间序列RMQ(区间最小值)的例子

<?php

/**
 * Description: 时间序列线段树
 *
 */

define("INFINITE", pow(2, 64));

class TSSegTree {

    private $begin;
    private $end;
    private $segTree = array();
    private $delay_mark = array();

    public static $debug_count = 0;

    public function __construct($begin, $end, $init_arr=array()) {

        $this->begin = $begin;
        $this->end   = $end;
        $this->build(0, $this->begin, $this->end, $init_arr);
    }

    private function lchild($father) {
        return $father * 2 + 1;
    }

    private function rchild($father) {
        return $father * 2 + 2;
    }

    // 取两个日期的中间日期
    private function middate($begin, $end) {
        $interval = (strtotime($end)-strtotime($begin)) / 86400;
        $interval = intval($interval / 2);
        return $this->nextnday($begin, $interval);
    }

    private function nextnday($date, $n=1) {
        $operator = $n >= 0? '+' : '-';
        return date('Y-m-d', strtotime("{$date} {$operator}{$n} day"));
    }

    // 更新当前节点值
    public function pushUp($root) {
        $lchild = $this->lchild($root);
        $rchild = $this->rchild($root);
        $this->segTree[$root] = min($this->segTree[$lchild], $this->segTree[$rchild]);
    }

    // 构建线段树
    public function build($root = 0, $begin, $end, $init_arr=array()) {

        $this->delay_mark[$root] = 0;
        if (strtotime($begin) == strtotime($end)) {
            $this->segTree[$root] = $init_arr[$begin];
        } else {
            $mid = $this->middate($begin, $end);
            $this->build($root * 2 + 1, $begin, $mid, $init_arr);
            $this->build($root * 2 + 2,  $this->nextnday($mid), $end, $init_arr);
            $this->pushUp($root);
        }
    }

    // 区间查询
    public function query($root, $nbegin, $nend, $qbegin, $qend) {

        if (strtotime($qbegin) > strtotime($nend) ||
              strtotime($qend) < strtotime($nbegin) ) {
                return INFINITE;
        }
        if (strtotime($qbegin) <= strtotime($nbegin) &&
              strtotime($qend) >= strtotime($nend) ) {
              return $this->segTree[$root];
        }
        $this->pushDown($root);
        $mid = $this->middate($nbegin, $nend);
        return min($this->query($this->lchild($root), $nbegin, $mid, $qbegin, $qend),
              $this->query($this->rchild($root), $this->nextnday($mid), $nend, $qbegin, $qend ));
    }

    // 单节点更新
    public function updateNode($root, $nbegin, $nend, $index, $val) {
        if (strtotime($nbegin) == strtotime($nend)) {
            if (strtotime($index) == strtotime($nbegin)) {
                $this->segTree[$root] += $val;
                // or other update method
            }
            return;
        }
        $mid = $this->middate($nbegin, $nend);
        if (strtotime($index) <= strtotime($mid)) {
            $this->updateNode($this->lchild($root), $nbegin, $mid, $index, $val);
        } else {
            $this->updateNode($this->rchild($root), $this->nextnday($mid), $nend, $index, $val);
        }
        $this->pushUp($root);
        return;
    }

    // 向子节点更新标志域
    public function pushDown($root) {
        if ($this->delay_mark[$root] != 0) {
            $this->delay_mark[$this->lchild($root)] += $this->delay_mark[$root];
            $this->delay_mark[$this->rchild($root)] += $this->delay_mark[$root];
            // change value
            $this->segTree[$this->lchild($root)] += $this->delay_mark[$root];
            $this->segTree[$this->rchild($root)] += $this->delay_mark[$root];
            $this->delay_mark[$root] = 0;
        }
    }

    // 区间更新
    public function updateRange($root, $nbegin, $nend, $ubegin, $uend, $val) {
        if ($ubegin > $nend || $uend < $nbegin) {
            return;
        }
        if ($ubegin <= $nbegin && $uend >= $nend) {
            $this->segTree[$root] += $val;
            $this->delay_mark[$root] += $val;
            return;
        }
        $this->pushDown($root);
        $mid = $this->middate($nbegin, $nend);
        $this->updateRange($this->lchild($root), $nbegin, $mid, $ubegin, $uend, $val);
        $this->updateRange($this->rchild($root), $this->nextnday($mid), $nend, $ubegin, $uend, $val);
        $this->pushUp($root);
    }

    // 输出值
    public function dumpTree($root, $begin, $end) {
        echo "{$begin} ~ {$end} : root: {$root}, val: {$this->segTree[$root]} ";
        echo "\r\n";
        if (strtotime($begin) == strtotime($end)) {
            return;
        } else {
            $mid = $this->middate($begin, $end);
            $this->dumpTree($this->lchild($root), $begin, $mid);
            $this->dumpTree($this->rchild($root), $this->nextnday($mid), $end);
        }
        return;
    }


}


function test() {
    $dataset = array(
        '2017-01-01' => 6,
        '2017-01-02' => 2,
        '2017-01-03' => 5,
        '2017-01-04' => 8,
        '2017-01-05' => 12,
        '2017-01-06' => 9,
        '2017-01-07' => 10,
        '2017-01-08' => 11
    );
    $tree = new TSSegTree('2017-01-01', '2017-01-08', $dataset);
    $tree->updateNode(0, '2017-01-01', '2017-01-08', '2017-01-04', 2);
    $result = $tree->query(0, '2017-01-01', '2017-01-08', '2017-01-03', '2017-01-06');
    echo $result;

    $tree->dumpTree(0, '2017-01-01', '2017-01-08');
    $tree->updateRange(0, '2017-01-01', '2017-01-08', '2017-01-03', '2017-01-06', -1);
    $result = $tree->query(0, '2017-01-01', '2017-01-08', '2017-01-03', '2017-01-06');
    echo $result;
    echo "\r\n";
    $tree->dumpTree(0, '2017-01-01', '2017-01-08');

}
test();

?>


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值