代码改变世界

线段树入门

2013-10-19 23:07  youxin  阅读(628)  评论(0编辑  收藏  举报

线段树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