(一)線段樹入門--區間最值查詢
阿新 • • 發佈:2019-02-13
這是一篇入門文章,不過需要你知道啥是二叉樹,並且知道遞迴,本文會持續更新,時間看作者心情。
線段樹
描述
分類:二叉樹搜尋樹
節點結構:
struct node
{
int l,r;//範圍【l,r】
}tr[100];
解決問題:區間問題
圖示:(顯示了節點分佈)
葉子結點:樹的最小節點。如【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;
}