树和树结构(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的最小值过程, 可以理解线段树高效的原因.
首先建立线段树的数据结构:
2:基本操作(demo用的是查询区间最小值)
线段树的主要操作有:
(1):线段树的构造 void init(node, begin, end);
主要思想是递归构造,如果当前节点记录的区间只有一个值,则直接赋值,否则递归构造左右子树,最后回溯的时候给当前节点赋值
注意: 为了节约空间, 这里给出的代码只将区间划分为i..i+1.
(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 可分成左右两部分
据此很容易写出子程序
(3):节点的更新 动态维护void update(tree &n, int num)
由顶自下更新, 再返回来向上更新. 先写一个获取此节点值的函数:
再写更新(这个需要好好理解递归构树):
最后呼之欲出的main!
通过这个练习, 对树结构有了更深刻的理解. 十分欣赏树结构的简洁, 美观, 高效!
明天就要期中考了, 还是忍不住来发个文. 对原文做了一些NOIp难度的改动, 希望对大家有些帮助.
发技术文好累23333
使用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
相关文章推荐
- 间隙锁(Next-Key锁)
- js与java交互
- 用Windows Live Writer离线写博客
- 查找里面的字符串的位置
- 用Windows Live Writer离线写博客
- C#_Dictionary<TKey, TValue>的使用
- LeetCode之Math题目汇总
- 【leetcode】217. Contains Duplicate
- Collection集合总结
- 使用Qt编写模块化插件式应用程序
- 【C#】 多态
- 【Unity Shaders】使用CgInclude让你的Shader模块化——Unity内置的CgInclude文件
- next_permutation生成全排列打表法解决问题
- 颜色名词典 Color-Name Dictionaries
- JavaWeb学习遇到的几个小问题
- ffmepg中swscale的用法
- 树和树结构(3) : Trie树(代码原创)
- iOS - 关于登陆后获取访问服务器通行证 (Token / session)
- C#实现数据库的递归查询
- 有需求就点进来