1. 程式人生 > >區間最值與線段樹

區間最值與線段樹

區間最值問題:

有如下無序序列,求任意子區間段的最大值。

 

接著,我們要用分治的思想來快速地解決上面的問題。在解決問題之前,先介紹一些分治的概念。

二分查詢

二分查詢是分治思想的典型運用

我們有如下序列:

A1,A2,A3……An.

要查詢其中等於b的元素。一種方法就是一個個對比,看看是不是相等,時間複雜度為N。還有就是二分查詢。

 

如上圖,通過不斷地與中間元素對比,縮小搜尋區間,最後得到A4==b.時間複雜度為logN.

二叉搜尋樹

在二分查詢中,我們要求佇列是有序地。那麼如果佇列無序又要如何?我們可以仿造二分查詢的方式,構造一個二叉搜尋樹,如下:

 

二叉搜尋樹左節點比根小,右節點比根大。上圖紅色箭頭代表搜尋A4的過程。

線段樹

線段樹也是一種二叉搜尋樹,我們回到區間最值的問題,有如下無序序列

 

現在我們要在上面的序列中,搜尋任意指定區間的最大值。

我們先來解決總的最大值的問題

一個基本的方法就是一個個比較,取出最大值,時間複雜度為N。

我們借用二分搜尋的分治思想。可以把序列劃分為1-5節點的最大值和6-8的最大值,再取這兩個值的較大值。遞迴地,我們再把1-5繼續劃分至只剩一個元素。這樣的時間複雜度為logN .如下面的過程,即線段數搜尋的過程:

 

上面的過程就是選取區間最大值的方式,總的最大值為9.

接著,我們要處理任意指定子序列的最值問題。

根據上圖我們可以看出,根據中間的結果,可以和容易地尋找1-6個節點的最大值,分治為1-4的最大值7,5-6的最大值8,1-6的最大值就為8.

總結一下上述的思想。

首先,有點像總統選舉,我們要尋找一個國家最適合當總統的人,不需要一個個去比較,只需要每個鄉選一個最好的,再在縣裡比較,得出一個縣裡最好的,然後市,然後省,最後我們得到了全國最好的。

然後,我們選舉的過程中,得到了鄉,縣的中間結果,這些又可以用來選取任意小範圍的最好人選。如我們要選總管廣東,廣西和南京鼓樓區第二大街的總管,只需要用到不同級別的中間結果彙總即可。

線段樹最終回到分治的思想上來,其應用與如下領域:

區間最值查詢問題

連續區間修改或者單節點更新的動態查詢問題 

多維空間的動態查詢

線段樹的程式設計實踐

線段樹的節點結構為:

struct node
{
    int left;
    int right;
    int max;
};

其中max儲存當前線段的最大值。

線段樹最基本要有三個函式:

1.遞迴地建立樹:

void buildtree(int index,int left,int right)
{
    tree[index].left=left;
   tree[index].right=right;
   tree[index].max=0;
    if (left==right)
    {
        return;
    }
    int mid=(left+right)>>1;
    buildtree((index<<1)+1,left,mid);
    buildtree((index<<1)+2,mid+1,right);
    return;

}

2.遞迴地插入:
void insert(int index,int left,int right,int k)
{

    int mid=(tree[index].left+tree[index].right)>>1;
    if (tree[index].left==tree[index].right)
    {
        tree[index].max=k;

        return ;
    }
    if (right<=mid)
    {
        if(tree[index].max<=k)
        tree[index].max=k;
        insert((index<<1)+1,left,right,k);
        return;
    }
    else
        if (left>mid)
        {
            if(tree[index].max<=k)
        tree[index].max=k;
            insert((index<<1)+2,left,right,k);
            return;
        }
}

3.遞迴地查詢:
int query(int index,int left,int right)
{
    int mid=(tree[index].left+tree[index].right)>>1;
    if (tree[index].left==tree[index].right)
    {
        return tree[index].max;
    }
    if (right<=mid)
    {
        return query((index<<1)+1,left,right);
    }
    else
        if (left>mid)
        {
            return query((index<<1)+2,left,right);
        }
        else
        {
            return M(query((index<<1)+1,left,mid),query((index<<1)+2,mid+1,right));
        }
}

最後,求解任意區間最值的程式碼如下:
#include<iostream>
using namespace std;
#define maxn 1001
int N,Q;
int M(int a,int b)
{

    return a>=b?a:b;
}
struct node
{
    int left;
    int right;
    int max;
};
struct node tree[maxn*4];

void buildtree(int index,int left,int right)
{
    tree[index].left=left;
   tree[index].right=right;
   tree[index].max=0;
    if (left==right)
    {
        return;
    }
    int mid=(left+right)>>1;
    buildtree((index<<1)+1,left,mid);
    buildtree((index<<1)+2,mid+1,right);
    return;

}

void insert(int index,int left,int right,int k)
{

    int mid=(tree[index].left+tree[index].right)>>1;
    if (tree[index].left==tree[index].right)
    {
        tree[index].max=k;

        return ;
    }
    if (right<=mid)
    {
        if(tree[index].max<=k)
        tree[index].max=k;
        insert((index<<1)+1,left,right,k);
        return;
    }
    else
        if (left>mid)
        {
            if(tree[index].max<=k)
        tree[index].max=k;
            insert((index<<1)+2,left,right,k);
            return;
        }
}

int query(int index,int left,int right)
{
    int mid=(tree[index].left+tree[index].right)>>1;
    if (tree[index].left==tree[index].right)
    {
        return tree[index].max;
    }
    if (right<=mid)
    {
        return query((index<<1)+1,left,right);
    }
    else
        if (left>mid)
        {
            return query((index<<1)+2,left,right);
        }
        else
        {
            return M(query((index<<1)+1,left,mid),query((index<<1)+2,mid+1,right));
        }
}
int main()
{
    int left,right,a;
    cin>>N;
    buildtree(0,1,N);
    for(int i=1;i<=N;i++)
    {
        cin>>a;
        insert(0,i,i,a);
        }
        cin>>Q;
        while(Q--)
        {
            cin>>left>>right;
            cout<<query(0,left,right)<<endl;
            }
    return 0;
}