1. 程式人生 > >K-D tree入門【更新ing】

K-D tree入門【更新ing】

sdoi 信息 AR 二叉樹 sdoi2010 由於 搜索 如果 ble

久仰K-D tree大名已久,終於在合適的時候遇見了合適的水題入了坑入了門

K-D tree是什麽

K-D tree是什麽? 按名字上翻譯來就是K維的樹,就是一個用來維護K維空間的點的平衡二叉樹

K-D tree有什麽用

K-D tree可以進行空間上的操作,最經典的就是查詢最近/最遠 點對
還有很多我不知道

K-D tree的原理與實現

K-D tree,又有一個名字叫做劃分樹,與其原理相聯系

類似於普通的平衡樹,對於普通的平衡樹的節點u,其左右子樹分別是權值小於u的和權值大於u的,該節點就相當於在一個值域上在u的值處進行了分割

而kd-tree對於多維空間進行分割,一個節點儲存著以下信息:

struct
node{ int d[K],s[2],x[2],y[2],......; }e[maxn];

d就是該節點儲存的點的K維坐標
s儲存著其左右兒子
剩余的若幹數組儲存著以該節點為根的子樹的各維度的最值
也就是說,一棵子樹實際上對應著一個空間區域,而根節點將該空間區域劃分為左右兩部分
而該空間的信息就儲存在子樹的根節點中

但是這是在一個多維空間,將空間區域劃分有多種方式
一般地,kd-tree垂直於其中一個坐標軸將平面劃分開
以2維為例,如下圖所示
技術分享圖片
如圖圓圈表示節點,將區域進行劃分
一般遵循以下規律構造:
①各層節點交替劃分各維空間
根節點劃分x坐標
其兒子劃分y坐標
其孫子劃分z坐標
......

②每一層的區域中,按該層劃分的坐標排序,選取其中位數作為劃分點進行劃分
切點作為父節點,左邊的點劃分到左子樹中,右邊的點劃分到右子樹中

③逐層劃分,直至劃分區域無節點

在C++的STL中,有一個函數nth_element()可以在\(O(n)\)時間內將一個數組第k大找出,並將小於的放在左邊,大於的放在右邊
具體實現類似快排
對於K維空間,建樹復雜度\(O(Knlogn)\)
二維建樹代碼如下:

#define ls e[u].s[0]
#define rs e[u].s[1]
#define cmin(x,y) (x > y ? x = y : x)
#define cmax(x,y) (x < y ? x = y : x)
struct point{int d[2];}a[maxn]; struct node{int d[2],s[2],x[2],y[2];}e[maxn]; int n,rt,D,x,y; bool operator <(const point& a,const point& b){ return a.d[D] == b.d[D] ? a.d[D ^ 1] < b.d[D ^ 1] : a.d[D] < b.d[D]; } void pup(int u){ if (ls){ cmin(e[u].x[0],e[ls].x[0]); cmax(e[u].x[1],e[ls].x[1]); cmin(e[u].y[0],e[ls].y[0]); cmax(e[u].y[1],e[ls].y[1]); } if (rs){ cmin(e[u].x[0],e[rs].x[0]); cmax(e[u].x[1],e[rs].x[1]); cmin(e[u].y[0],e[rs].y[0]); cmax(e[u].y[1],e[rs].y[1]); } } int build(int l,int r,int d){ D = d; int u = l + r >> 1; nth_element(a + l,a + u,a + r + 1); e[u].d[0] = e[u].x[0] = e[u].x[1] = a[u].d[0]; e[u].d[1] = e[u].y[0] = e[u].y[1] = a[u].d[1]; if (l < u) ls = build(l,u - 1,d ^ 1); if (r > u) rs = build(u + 1,r,d ^ 1); pup(u); return u; }

查詢最近/遠點對

以最近為例
與普通的暴力不同,在KDtree中查詢最近點對,預期復雜度為\(O(logn)\),可以被卡為\(O(\sqrt{N})\)
我們到一個節點時,用該節點更新答案,並計算左右子樹的估價函數

由於每棵子樹都對應一個區域,可以由此計算出每棵子樹的最近值
如果最近的點的貢獻都比當前答案大,那麽就不用訪問該子樹了
以2維為例,估價函數可以這樣寫:

#define getd(u) (max(x - e[u].x[1],0) + max(e[u].x[0] - x,0) + max(y - e[u].y[1],0) + max(e[u].y[0] - y,0))
#define getdx(u) (max(abs(e[u].x[0] - x),abs(e[u].x[1] - x)) + max(abs(e[u].y[0] - y),abs(e[u].y[1] - y)))

實際上就是求與四個頂點距離的最值

由此可以寫出搜索函數:

void qmx(int u){
    LL t = equal(u) ? -INF : (abs(e[u].d[0] - x) + abs(e[u].d[1] - y)),d[2];
    if (ls) d[0] = getdx(ls); else d[0] = -INF;
    if (rs) d[1] = getdx(rs); else d[1] = -INF;
    cmax(mx,t); t = d[0] <= d[1];
    if (d[t] > mx) qmx(e[u].s[t]); t ^= 1;
    if (d[t] > mx) qmx(e[u].s[t]);
}
void qmn(int u){
    int t = equal(u) ? INF : (abs(e[u].d[0] - x) + abs(e[u].d[1] - y)),d[2];
    if (ls) d[0] = getd(ls); else d[0] = INF;
    if (rs) d[1] = getd(rs); else d[1] = INF;
    cmin(mn,t); t = d[0] >= d[1];
    if (d[t] < mn) qmn(e[u].s[t]); t ^= 1;
    if (d[t] < mn) qmn(e[u].s[t]);
}

例題

由以上基礎,我們就可以輕松A掉SDOI2010 hideseek了
用每個點搜一次就好

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#define LL long long int
#define Redge(u) for (int k = h[u],to; k; k = ed[k].nxt)
#define REP(i,n) for (int i = 1; i <= (n); i++)
#define BUG(s,n) for (int i = 1; i <= (n); i++) cout<<s[i]<<‘ ‘; puts("");
#define ls e[u].s[0]
#define rs e[u].s[1]
#define cmin(x,y) (x > y ? x = y : x)
#define cmax(x,y) (x < y ? x = y : x)
#define getd(u) (max(x - e[u].x[1],0) + max(e[u].x[0] - x,0) + max(y - e[u].y[1],0) + max(e[u].y[0] - y,0))
#define getdx(u) (max(abs(e[u].x[0] - x),abs(e[u].x[1] - x)) + max(abs(e[u].y[0] - y),abs(e[u].y[1] - y)))
#define equal(u) (e[u].d[0] == x && e[u].d[1] == y)
using namespace std;
const int maxn = 100005,maxm = 100005,INF = 2100000000;
inline int read(){
    int out = 0,flag = 1; char c = getchar();
    while (c < 48 || c > 57){if (c == ‘-‘) flag = -1; c = getchar();}
    while (c >= 48 && c <= 57){out = (out << 3) + (out << 1) + c - 48; c = getchar();}
    return out * flag;
}
struct point{int d[2];}a[maxn];
struct node{int d[2],s[2],x[2],y[2];}e[maxn];
int n,rt,D,x,y; LL mx,mn;
bool operator <(const point& a,const point& b){
    return a.d[D] == b.d[D] ? a.d[D ^ 1] < b.d[D ^ 1] : a.d[D] < b.d[D];
}
void pup(int u){
    if (ls){
        cmin(e[u].x[0],e[ls].x[0]); cmax(e[u].x[1],e[ls].x[1]);
        cmin(e[u].y[0],e[ls].y[0]); cmax(e[u].y[1],e[ls].y[1]);
    }
    if (rs){
        cmin(e[u].x[0],e[rs].x[0]); cmax(e[u].x[1],e[rs].x[1]);
        cmin(e[u].y[0],e[rs].y[0]); cmax(e[u].y[1],e[rs].y[1]);
    }
}
int build(int l,int r,int d){
    D = d; int u = l + r >> 1;
    nth_element(a + l,a + u,a + r + 1);
    e[u].d[0] = e[u].x[0] = e[u].x[1] = a[u].d[0];
    e[u].d[1] = e[u].y[0] = e[u].y[1] = a[u].d[1];
    if (l < u) ls =  build(l,u - 1,d ^ 1);
    if (r > u) rs =  build(u + 1,r,d ^ 1);
    pup(u);
    return u;
}
void qmx(int u){
    LL t = equal(u) ? -INF : (abs(e[u].d[0] - x) + abs(e[u].d[1] - y)),d[2];
    if (ls) d[0] = getdx(ls); else d[0] = -INF;
    if (rs) d[1] = getdx(rs); else d[1] = -INF;
    cmax(mx,t); t = d[0] <= d[1];
    if (d[t] > mx) qmx(e[u].s[t]); t ^= 1;
    if (d[t] > mx) qmx(e[u].s[t]);
}
void qmn(int u){
    int t = equal(u) ? INF : (abs(e[u].d[0] - x) + abs(e[u].d[1] - y)),d[2];
    if (ls) d[0] = getd(ls); else d[0] = INF;
    if (rs) d[1] = getd(rs); else d[1] = INF;
    cmin(mn,t); t = d[0] >= d[1];
    if (d[t] < mn) qmn(e[u].s[t]); t ^= 1;
    if (d[t] < mn) qmn(e[u].s[t]);
}
int main(){
    n = read();
    for (int i = 1; i <= n; i++) a[i].d[0] = read(),a[i].d[1] = read();
    rt = build(1,n,0);
    LL ans = INF;
    for (int i = 1; i <= n; i++){
        x = a[i].d[0]; y = a[i].d[1];
        mx = 0; qmx(rt);
        mn = INF; qmn(rt);
        ans = min(ans,mx - mn);
    }
    printf("%lld\n",ans);
    return 0;
}

K-D tree入門【更新ing】