1. 程式人生 > >(一)線段樹入門--區間最值查詢

(一)線段樹入門--區間最值查詢

這是一篇入門文章,不過需要你知道啥是二叉樹,並且知道遞迴,本文會持續更新,時間看作者心情。

線段樹

描述

分類:二叉樹搜尋樹
節點結構:

struct node
{
    int l,r;//範圍【l,r】
}tr[100];

解決問題:區間問題
圖示:(顯示了節點分佈)
tree
葉子結點:樹的最小節點。如【1,1】,【2,2】這些結點。

線段樹是一棵二叉搜尋樹,它的每個結點都包含一個區間【l,r】,葉子結點對應的是一個單位區間,即 L == R 。對於一個非葉子結點,它的左兒子對應的區間為【L,(L+R)/2】,右兒子對應的區間為【(L+R)/2+1,R】。這棵樹包含N個葉子結點,即整個區間的長度。

區間最值查詢問題

給你一個數組,要求你進行兩種操作
(1) 修改一個元素(更新操作)
(2)查詢一段區間的最大值(查詢操作)

用線段樹可以很好解決區間的最值查詢問題,首先我們要確定結點結構。

struct node//結點
{
    int l,r;//範圍【l,r】
    int mx; //區間的最大值,max縮寫
}tr[100];//tree的縮寫

然後我們需要建樹:即建立二叉樹的同時把區間最大值完成。這裡我們採用遞迴的方法
具體操作:每個結點建立它的區間,葉子節點建立區間【l,r】和結點最大值mx,等葉子節點的最大值mx完成,父節點開始建立它的區間最大值mx。

查詢操作和更新操作都是先維護子結點,然後更新父節點。

總結:先維護子結點,然後更新父節點。

這裡寫圖片描述

模板

/**
 * 線段樹
 * d為結點號,每個函式都有
 */
#include<iostream>
#include<stdio.h>
#include<algorithm>

using namespace std;
const int mmax = 100;
int b[mmax] = {0,1,2,3,4,5,6,7,8,9,10};//初始化陣列
struct tree
{
    int l,r;
    int mx;
}tr[mmax*4];//為何是*4呢??(下回揭曉)
//建樹 void build(int d,int l,int r) { tr[d].l = l,tr[d].r = r; if(l == r)//葉子節點處理 { tr[d].mx = b[l]; return; } int mid = (l+r)/2;//這裡的mid,lc,rc,不能定義為全域性變數 int lc = d*2; int rc = d*2+1; build(lc,l,mid); build(rc,mid+1,r); tr[d].mx = max(tr[lc].mx,tr[rc].mx);//處理爸爸們 } //查詢[l,r]最大值 int search(int d,int l,int r) { if(tr[d].l == l && tr[d].r == r)//查到對應範圍 { return tr[d].mx; } int mid = (l+r)/2; int lc = d*2; int rc = d*2+1; if(mid >= r) { return search(lc,l,mid); } else if(mid <= l) { return search(rc,mid+1,r); } else { return max(search(lc,l,mid),search(rc,mid+1,r)); } } //更新,將【pos,pos】結點最大值改為v void modify(int d,int pos, int v) { if(tr[d].l == tr[d].r && tr[d].l == pos) { tr[d].mx = v; return; } int mid = (tr[d].l+tr[d].r)/2; int lc = d*2; int rc = d*2+1; if(pos > mid)//右側無法>=,可以想一想,試一試 { modify(rc, pos, v); } else { modify(lc, pos, v); } tr[d].mx = max(tr[lc].mx,tr[rc].mx); } int main() { build(1,1,4); for(int i = 1; i <= 7; i++) printf("有7個結點,第%d個結點為%d\n",i,tr[i].mx); printf("1-3的最大值%d\n",search(1,1,3)); modify(1,1,10); for(int i = 1; i <= 7; i++) printf("有7個結點,第%d個結點為%d\n",i,tr[i].mx); return 0; }

一棵看上去很唬人的樹

線段樹其實不難,本質二叉樹,處理用遞迴,處理時分葉子結點和父結點分類討論。很多人往往被它的名字所嚇倒,其實靜下心來看,還是可以看懂的。

一些比較酷的操作

在實際程式碼中,經常有一些騷操作,這裡貼一段優化的程式碼。(執行起來實際其實並沒快多少,但裝裝b還是可以的。)大家可以看一看,再複習一下。
這裡寫圖片描述

/**
 * 線段樹,優化裝b版
 */
#include<iostream>
#include<stdio.h>
#include<algorithm>

using namespace std;
//不用定義區域性變量了
#define mid (r+l>>1)
#define lc (d<<1)
#define rc (d<<1|1)
const int mmax = 100;

int b[mmax] = {0,1,2,3,4,5,6,7,8,9,10};//初始化陣列
struct tree
{
    int l,r;
    int mx;
}tr[mmax<<2];//位運算,只是裝裝b,-。-

//建樹
void build(int d,int l,int r)
{
    tr[d].l = l,tr[d].r = r;
    if(l == r)//葉子節點處理
    {
        tr[d].mx = b[l];
        return;
    }
    build(lc,l,mid);
    build(rc,mid+1,r);
    tr[d].mx = max(tr[lc].mx,tr[rc].mx);//處理爸爸們
}

//查詢[l,r]最大值
int search(int d,int l,int r)
{
    if(tr[d].l == l && tr[d].r == r)//查到對應範圍
    {
        return tr[d].mx;
    }
    if(mid >= r)
    {
        return search(lc,l,mid);
    }
    else if(mid <= l)
    {
        return search(rc,mid+1,r);
    }
    else
    {
        return max(search(lc,l,mid),search(rc,mid+1,r));
    }
}

//更新,將【pos,pos】結點最大值改為v
void modify(int d,int pos, int v)
{
    if(tr[d].l == tr[d].r && tr[d].l == pos)
    {
        tr[d].mx = v;
        return;
    }
    int mid2 = (tr[d].l+tr[d].r)/2;
    if(pos > mid2)//右側無法>=,可以想一想,試一試
    {
        modify(rc, pos, v);
    }
    else
    {
        modify(lc, pos, v);
    }
    tr[d].mx = max(tr[lc].mx,tr[rc].mx);
}

int main()
{
    build(1,1,4);
    for(int i = 1; i <= 7; i++)
        printf("有7個結點,第%d個結點為%d\n",i,tr[i].mx);
    printf("1-3的最大值%d\n",search(1,1,3));
    modify(1,1,10);
    for(int i = 1; i <= 7; i++)
        printf("有7個結點,第%d個結點為%d\n",i,tr[i].mx);
    return 0;
}