• 线段树入门


    线段树Segment Tree)(也叫区间树)是一种二叉搜索树,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点

    对于线段树中的每一个非叶子节点[a,b],它的左子树表示的区间为[a,(a+b)/2],右子树表示的区间为[(a+b)/2+1,b]。因此线段树是平衡二叉树。叶节点数目为N,即整个线段区间的长度。

    使用线段树可以快速的查找某一个节点在若干条线段中出现的次数,时间复杂度为O(logN)。而未优化的空间复杂度为2N,因此有时需要离散化让空间压缩。

    另外还有如zkw线段树的特种线段树。

    定义1:长度为1的线段称为元线段。
    定义2:一棵树被称为线段树,当且仅当这棵树满足如下条件:
        (1)该树是一棵二叉树;
        (2)树中的每一个结点都对应一条线段[a,b];
        (3)树中的结点是叶子结点,当且仅当它所代表的线段是元线段;
        (4)树中非叶子结点都有左右两棵子树,左子树树根对应线段[a,(a+b)/2],右子树树根对应线段[(a+b)/2,b]。
    性质1:长度范围为[1,L]的一棵线段树的深度不超过log2(L-1)+1;
    性质2:线段树上的结点个数不超过2L个;
    性质3:线段树把区间上的任意一条线段都分成不超过2log2L条线段。
    存储方式:
    (1)链表实现:
           Node=^TNode;
           TNode=record
                           Left,Right:integer;
                           LeftChild,RightChild:Node;
                        end;
    (2)数组模拟链表:
    Left,Right,LeftChild,RightChild:array[1..n] of integer;
    (3)堆的结构:
    根节点为1,对于结点x,其左孩子为2x,右孩子为2x+1

     线段树构造

    首先介绍构造线段树的方法:让根节点表示区间[0,N-1],即所有N个数所组成的一个区间,然后,把区间分成两半,分别由左右子树表示。不难证明,这样的线段树的节点数只有2N-1个,是O(N)级别的,如图:

    线段树的维护:

    #include<iostream>
    using namespace std;
    
    typedef struct Node{
        int left,right;
        struct Node* leftChild;
        struct Node* rightChild;
    
        int min,max,sum;
        Node():min(0),max(0),sum(0){}
    
    }Node;
    
    void build(Node* curNode,int l,int r)
    {
        curNode->left=l,curNode->right=r;
        if(l==r)
        {
            curNode->leftChild=curNode->rightChild=NULL;
        }
        else
        {
            curNode->leftChild=new Node();
            curNode->rightChild=new Node();
            int mid=(l+r)/2;
            build(curNode->leftChild,l,mid);
            build(curNode->rightChild,mid+1,r);
        }
    }
    
    void query(Node* curNode,int l,int r)
    {
        // // 如果当前结点的区间包含在查询区间内
        if(l<=curNode->left &&  curNode->right<=r)
        {
            cout<<curNode->left<<"--->"<<curNode->right<<endl;
        }
        else
        {
            int mid=(curNode->left+curNode->right)/2;
            if(l<=mid)// 和左孩子有交集,考察左子结点
                query(curNode->leftChild,l,r);
            if(r>mid)
                query(curNode->rightChild,l,r);
        }
    }
    
    void modify(Node* curNode,int x,int num)
    {
        Node* LC=curNode->leftChild;
        Node* RC=curNode->rightChild;
    
        if(curNode->left==curNode->right)//对叶节点进行处理
        {
            curNode->min=num;
            curNode->max=num;
            curNode->sum=num;
        }
        else
        {
            int mid=(curNode->left+curNode->right)/2;
            if(x<=mid) modify(LC,x,num);
            else if(x>mid) modify(RC,x,num);
    
            curNode->sum=LC->sum+RC->sum;//上推
            if(LC->max > RC->max)
                curNode->max=LC->max;
            else
                curNode->max=RC->max;
    
            if(LC->min < RC->min)
                curNode->min=LC->min;
            else
                curNode->min=RC->min;
    
        }
    }
    
    int querySum(Node* curNode,int l,int r)
    {
        Node* LC=curNode->leftChild;
        Node* RC=curNode->rightChild;
        int ret=0;
    
        if(l<=curNode->left && curNode->right<=r)
            ret=curNode->sum;
        else
        {
            int mid=(curNode->left+curNode->right)/2;
            if(l<=mid)
                ret += querySum(LC,l,r);
            if(r>mid)
                ret += querySum(RC,l,r);
    
        }
        return ret;
    }
    
    int main()
    {
        int n=5;
        Node* root=new Node();
        
        build(root,0,n-1);
        modify(root,0,100);
       
        query(root,2,3);
    }

    输出

    2--->2
    3--->3
    请按任意键继续. . .

    如果是:

    modify(root,0,100);

    modify(root,2,100);
    modify(root,3,100);

    cout<<querySum(root,0,2)<<endl;结果为:200.

    线段树上的参数通常有两种维护方法:
    (1)一类参数表达了结点的性质,通常具有树型的递推性质,可以从下向上进行递推计算;(如sum,max,min)
    (2)一类参数表达了子树的性质,维护的时候可以先打上标记,在需要进一步访问其子结点的时候从上向下传递。(如delta,en)
    线段树上维护或者询问过程的一般过程:
    对于当前区间[l,r]
    if 达到某种边界条件(如叶结点或被整个区间完全包含)then
        对维护或是询问作相应处理
    else
        将第二类标记传递下去(注意,在查询过程中也要处理)
        根据区间的关系,对两个孩子结点递归处理
        利用递推关系,根据孩子结点的情况维护第一类信息
    区间重合问题解决办法;
    #include<iostream>
    using namespace std;
    struct Node
    {
        int   left,right;  //区间左右值
        Node   *leftChild;
        Node   *rightChild; 
        int cover;
        Node():cover(0){}
    };
    
    void build(Node* node,int l,int r)
    {
        node->left=l;
        node->right=r;
        if(l==r)
            node->leftChild=node->rightChild=NULL;
        else
        {
            node->leftChild=new Node();
            node->rightChild=new Node();
            int mid=(l+r)/2;
            build(node->leftChild,l,mid);
            build(node->rightChild,mid+1,r);
        }
    }
    
    void insert(Node* node,int l,int r)
    {
        if(l<=node->left && node->right<=r)
            node->cover++;
        else
        {
            int mid=(node->left+node->right)/2;
            if(l<=mid) insert(node->leftChild,l,r);
            if(r>mid) insert(node->rightChild,l,r);
        }
    }
    /*
    给线段树每个节点增加一个域cover。cover=1表示该结点所对应的区间被完全覆盖,cover=0表示该结点所对应的区间未被完全覆盖。
    */
    void insert2(Node* node,int l,int r)
    {
        if(node!=NULL && node->cover==0)
        {
            int mid=(node->left+node->right)/2;
            if(l==node->left && r==node->right)
                node->cover=1;
            else if(r<=mid) insert2(node->leftChild,l,r);
            else if(l>=mid) insert2(node->rightChild,l,r);
            else
            {
                insert2(node->leftChild,l,r);
                insert2(node->rightChild,l,r);
            }
        }
    }
    
    int count(Node* node)
    {
        int m,n;
        if(node->cover==1)
            return (node->right-node->left+1);
        else if(node->right-node->left==1) return 1;
    
        m=count(node->leftChild);
        n=count(node->rightChild);
    
        return m+n;
    }
        
    
    
    void deleteLine(Node* node,int l,int r)
    {
        if(l<=node->left && node->right<=r)
            node->cover--;
        else
        {
            int mid=(node->left+node->right)/2;
            if(l<=mid) deleteLine(node->leftChild,l,r);
            if(r>mid)  deleteLine(node->rightChild,l,r);
        }
    }
    
    int main()
    {
        int n=8;
        Node * node=new Node();
        build(node,1,8);
         insert2(node,1,4);
        insert2(node,2,5);
        insert2(node,7,8);
    
        cout<<count(node)<<endl;
    }

    上面的insert和insert2不同,这里判断区间重合我们用到了insert2。

    count看cover有没有设置为1.则直接返回node.right - node.left+1,其子树不用管. 

    更多;

    http://blog.csdn.net/metalseed/article/details/8039326

    http://blog.csdn.net/tianshuai1111/article/details/7828961

    http://blog.csdn.net/yangtrees/article/details/8262594

    http://www.notonlysuccess.com/index.php/segment-tree-complete/

    http://www.wypblog.com/archives/144

  • 相关阅读:
    Java的 Annotation 新特性
    Java 枚举
    Java 泛型
    Linux kali信息探测以及 Nmap 初体验
    静态导入 ()
    Java foreach循环
    Java 可变参数
    炫酷的CSS3响应式表单
    关于CSS选择器连续性的问题
    简述ECMAScript6新增特性
  • 原文地址:https://www.cnblogs.com/youxin/p/3378578.html
Copyright © 2020-2023  润新知