您的位置:首页 > 其它

树和树结构(4): 线段树(部分转载)

2015-12-27 13:07 295 查看
原文来自/article/8334307.html, 有改动

使用tyvj1039_忠诚2 作为测试题目: http://www.tyvj.cn/p/1039

源码下载 Tyvj1039_忠诚2.cpp

一:线段树基本概念

0: 图片



1:概述

线段树,类似区间树,是一个完全二叉树,它在各个节点保存一条线段(数组中的一段子数组),主要用于高效解决连续区间的动态查询问题,由于二叉结构的特性,它基本能保持每个操作的复杂度为O(lgN)!

性质:父亲的区间是[a,b],(c=(a+b)/2)左儿子的区间是[a,c],右儿子的区间是[c+1,b],线段树需要的空间为数组大小的四倍

个人感觉: 其实是将区间二分, 预处理出一部分区间值. 这样在查询某一个区间最小值时, 可以直接调用其中一部分. 例如在1..10的区间内求2..6的最小值, 实质上可以变为求2..(1+10)/2最小值和(1+10)/2+1..6的最小值. 前者又可以划分为2..3和4..5等等等, 以此类推. 读者可以用手算下1..100区间内求31..78的最小值过程, 可以理解线段树高效的原因.

首先建立线段树的数据结构:

#include <iostream>
#include <cstring>
#include <cstdio>
#define INF 100000001
#define half(x) ((x)>>(1))
using namespace std;

int nums[100005];
int n, q;

typedef struct node;
typedef node *tree;
int init(tree &t, int from, int to);
struct node {
tree lc, rc;
//左右子树
int left, right, value;
//线段
node() {
lc = rc = NULL;
left = right = value = 0;
}
}*root;


2:基本操作(demo用的是查询区间最小值)

线段树的主要操作有:

(1):线段树的构造 void init(node, begin, end);

主要思想是递归构造,如果当前节点记录的区间只有一个值,则直接赋值,否则递归构造左右子树,最后回溯的时候给当前节点赋值

注意: 为了节约空间, 这里给出的代码只将区间划分为i..i+1.

int init(tree &t, int from, int to)
{
if (to < from) return INF;
if (from == to) return nums[from];
//到达边界
if (t == NULL)
t = new node;
t->left = from;
t->right = to;
return t->value = min(
init(t->lc, from, half(from+to)),
init(t->rc, half(from+to)+1, to)
);
//递归构建左右子树
}


(2):区间查询int ask_min(tree n, int from, int to)

(其中n为当前查询节点,from, to为此次query所要查询的区间)

主要思想是把所要查询的区间[a,b]划分为线段树上的节点,然后将这些节点代表的区间合并起来得到所需信息

比如前面一个图中所示的树,如果询问区间是[0,2],或者询问的区间是[3,3],不难直接找到对应的节点回答这一问题。但并不是所有的提问都这么容易回答.

询问有且只有五种情况:

0 线段长度为0

1 和此节点重合

2 被此节点的左子树包含

3 被此节点的右子树包含

4 可分成左右两部分

据此很容易写出子程序

int ask_min(tree n, int from, int to)
{
if (from == to) return nums[from];           //0
if (to < from || !n || (n->left > from && n->right < to)) return INF;
//剪去无意义的
if (n->left == from && n->right == to)
return n->value;                     //1
if (n->left <= from && half(n->left+n->right) >= to)
return ask_min(n->lc, from, to);     //2
if (half(n->left+n->right)+1 <= from && n->right >= to)
return ask_min(n->rc, from, to);     //3
return min(
ask_min(n->lc, from, half(n->left+n->right)),
ask_min(n->rc, half(n->left+n->right)+1, to)
);                                           //4
}


(3):节点的更新 动态维护void update(tree &n, int num)

由顶自下更新, 再返回来向上更新. 先写一个获取此节点值的函数:

int value(tree n) {
if (!n->lc)
return min(nums[n->left], nums[n->right]);
//左子树为空, 说明左右都为空
if (!n->rc)
return min(n->lc->value, nums[n->right]);
//右子树为空, 左子树不为空
return min(n->lc->value, n->rc->value);
//都不为空
}


再写更新(这个需要好好理解递归构树):

void update(tree &n, int num)
{
if (!n)
return; //到底部了
if (num <= half(n->left+n->right)) {
update(n->lc, num);
n->value = value(n);
//向左子树更新
} else {
update(n->rc, num);
n->value = value(n);
//向右子树更新
}
}


最后呼之欲出的main!

int main()
{
scanf ("%d%d", &n, &q);
for (int i=1; i<=n; i++)
scanf ("%d", &nums[i]);
init(root, 1, n);
for (int i=1; i<=q; i++) {
int order, f, t;
scanf ("%d%d%d", &order, &f, &t);
if (order == 1)
printf ("%d ", ask_min(root,f,t));
if (order == 2) {
nums[f] = t;
update (root, f);
}
}
return 0;
}


通过这个练习, 对树结构有了更深刻的理解. 十分欣赏树结构的简洁, 美观, 高效!

明天就要期中考了, 还是忍不住来发个文. 对原文做了一些NOIp难度的改动, 希望对大家有些帮助.

发技术文好累23333
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: