zoukankan      html  css  js  c++  java
  • 树和树结构(4): 线段树(部分转载)

    原文来自http://blog.csdn.net/metalseed/article/details/8039326, 有改动
    使用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

  • 相关阅读:
    Zabbix实战-简易教程(7)--监控第一台host
    Zabbix实战-简易教程(6)--Server端高可用
    Zabbix实战-简易教程(5)--Proxy和Agent端(源码和yum方式)
    HDFS“慢节点”监控分析功能
    遇见InterruptedException异常,怎么办?
    遇见InterruptedException异常,怎么办?
    Hadoop Erasure Coding结构分析
    Hadoop Erasure Coding结构分析
    聊聊错误注入技巧
    聊聊错误注入技巧
  • 原文地址:https://www.cnblogs.com/ljt12138/p/6684400.html
Copyright © 2011-2022 走看看