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