KDtree


KDtree

What is KDtree?

KDtree(K dimensional tree) 是一個支持多維空間的數據結構,主要是將空間內的點進行區域划分,快速維護有關空間點的操作,如空間的最遠(近)點對,區間搜索。KDtree的結構與線段樹類似,只是線段樹是對一維空間的操作,而KDtree是多維操作的,這也導致了KDtree的靈活性沒有線段樹高。

樹上每個點維護的信息:

  1. 兩個兒子
  2. 該點表示的空間范圍(超長方體,2D為矩形,3D為長方體)
  3. 中位點(坐標等信息)

Operations(Base on 2D)

Build

因為是空間划分,所以要交錯地用平行與\(x, y\)軸的直線進行划分。

\(n\)個點\(p_i\)
假設現在要用平行於\(y\)軸的直線划分區間\([L, R]\)(\(p_i\)),首先初始化該點的空間范圍,然后求出\([L, R]\)\(x\)坐標從小到大排序時的中位點,這個可以用nth_element來算,記錄中位點(\(mid\)),這就將\([L, R]\)分成了\([L, mid-1], [mid+1, R]\)兩部分,然后遞歸兩個區間,而這兩個區間要用平行於\(x\)軸的直線進行划分,以此類推。

void build(sKDtree *&cur, int L, int R, int type)
//type==0時平行於$y$軸,type==1時平行於$x$軸
{
	if (L>R) return;
	cur=mem++; //新建一個點

//求空間范圍
	int le, ri, down, up;
	le=down=inf, ri=up=0;
	for (int i=L; i<=R; ++i)
	{
		le=min(le, p[i].x);
		ri=max(ri, p[i].x);
		down=min(down, p[i].y);
		up=max(up, p[i].y);
	}
	
//求中位點
	int mid=(L+R)>>1;
	if (type) nth_element(p+L, p+mid, p+R+1, cmpy);
	else nth_element(p+L, p+mid, p+R+1, cmpx);
//點初始化
	cur->init(type, le, ri, down, up);
	cur->p=p[mid];

	build(cur->son[0], L, mid-1, type^1);
	build(cur->son[1], mid+1, R, type^1);
}

ask(以查找歐拉距離最遠點為例)

假設要找離\(p_0\)歐拉距離最遠的點。優先遞歸答案較優的區間,然后在遞歸另一個區間,這樣剪枝的時候就能減掉更多的區間。例如:假設\(A\)是當前的最遠點,則灰色區間是不用遞歸的。我們選擇區間的四個角作為區間的代表。

inline int calc_maxdis(sKDtree *cur, Point &p0)
//選擇四個角中離p0最遠的那個點作為區間的代表
{
    if (!cur) return 0;
    int ans=sqr(cur->x1-p0.x)+sqr(cur->y1-p0.y);
    ans=max(ans, sqr(cur->x1-p0.x)+sqr(cur->y2-p0.y));
    ans=max(ans, sqr(cur->x2-p0.x)+sqr(cur->y1-p0.y));
    ans=max(ans, sqr(cur->x2-p0.x)+sqr(cur->y2-p0.y));
    return ans;
}
void ask(sKDtree *&cur, Point &p0, int &dis)
{
    if (!cur) return;
    if (calc_maxdis(cur, p0)<=dis) return; //dis是當前最遠距離

    dis=max(dis, sqr(cur->p.x-p0.x)+sqr(cur->p.y-p0.y));
    
    //判斷哪個區間更優
    int nid=1;
    int d[2];
    d[0]=calc_maxdis(cur->son[0], p0);
    d[1]=calc_maxdis(cur->son[1], p0);
    if (d[0]>d[1]) nid^=1;

    //大於當前最優答案的區間進行搜索
    if (d[nid]>dis) ask(cur->son[nid], p0, dis);
    if (d[nid^1]>dis) ask(cur->son[nid^1], p0, dis);
}

k近鄰

\(k\)近鄰是指找到第\(k\)近的點,查找的時候與找最近鄰類似,只不過要維護一個大根堆,維護當前\(k\)個點中的最遠距離,如果當前點比最遠距離要小,則更新大根堆,而且利用最遠距離可以減掉那些不在當前第\(k\)距離內的區間。

注意:由於建樹方式的特殊性,使得KDtree難以支持插入操作。

附上模板:Base Stations

#include <bits/stdc++.h>
using namespace std;

const int inf=int(1e9);
const int maxn=int(1e5)+100;
const int maxm=105;

struct base
{
    int x, y, type;

    bool operator < (const base b) const
    {
        return type<b.type;
    }
};

struct sKDtree
{
    sKDtree *son[2];
    base p;
    int sum[maxm];
    int total;
    int x1, x2, y1, y2;
    int type;

    inline void init(int _type=0, int _x1=0, int _x2=0, int _y1=0, int _y2=0)
    {
        total=0;
        for (int i=0; i<maxm; ++i) sum[i]=0;
        son[0]=son[1]=NULL;
        x1=_x1; y1=_y1; x2=_x2; y2=_y2;
        type=_type;
    }

    void updata()
    {
        if (son[0])
        {
            for (int i=0; i<maxm; ++i)
                sum[i]+=son[0]->sum[i];
            total+=son[0]->total;
        }
        if (son[1])
        {
            for (int i=0; i<maxm; ++i)
                sum[i]+=son[1]->sum[i];
            total+=son[1]->total;
        }
    }
};

int n;
base station[maxn];
sKDtree memory[maxn*2];
sKDtree *mem=memory;
sKDtree *KDtree;

inline int sqr(int x)
{
    return x*x;
}
void read()
{
    for (int i=1; i<=n; ++i)
    {
        base &cur=station[i];
        scanf("%d%d%d", &cur.x, &cur.y, &cur.type);
    }
}
inline bool cmp0(base &b, base &c)
{
    return b.x<c.x;
}
inline bool cmp1(base &b, base &c)
{
    return b.y<c.y;
}
void build(sKDtree *&cur, int L, int R, int type)
{
    if (L>R) return;
    cur=mem++;

    int le, ri, down, up;
    le=down=inf, ri=up=0;
    for (int i=L; i<=R; ++i)
    {
        base &cur=station[i];
        le=min(le, cur.x);
        ri=max(ri, cur.x);
        down=min(down, cur.y);
        up=max(up, cur.y);
    }
    int mid=(L+R)>>1;
    if (type) nth_element(station+L, station+mid, station+R+1, cmp1);
    else nth_element(station+L, station+mid, station+R+1, cmp0);

    cur->init(type, le, ri, down, up);
    cur->p=station[mid];
    cur->sum[station[mid].type]++;
    cur->total=1;
    build(cur->son[0], L, mid-1, type^1);
    build(cur->son[1], mid+1, R, type^1);
    cur->updata();
    if (cur->son[0])
    {
        for (int i=0; i<maxm; ++i)
            cur->sum[i]+=cur->son[0]->sum[i];
        cur->total+=cur->son[0]->total;
    }
    if (cur->son[1])
    {
        for (int i=0; i<maxm; ++i)
            cur->sum[i]+=cur->son[1]->sum[i];
        cur->total+=cur->son[1]->total;
    }
}
inline int calc_maxdis(sKDtree *cur, base &psta)
{
    if (!cur) return 0;
    int ans=sqr(cur->x1-psta.x)+sqr(cur->y1-psta.y);
    ans=max(ans, sqr(cur->x1-psta.x)+sqr(cur->y2-psta.y));
    ans=max(ans, sqr(cur->x2-psta.x)+sqr(cur->y1-psta.y));
    ans=max(ans, sqr(cur->x2-psta.x)+sqr(cur->y2-psta.y));
    return ans;
}
void ask(sKDtree *&cur, base &psta, int &dis)
{
    if (!cur) return;
    if (cur->total==cur->sum[psta.type]) return;
    if (calc_maxdis(cur, psta)<=dis) return;

    if (cur->p.type!=psta.type)
        dis=max(dis, sqr(cur->p.x-psta.x)+sqr(cur->p.y-psta.y));
    
    int nid=1;
    int d[2];
    d[0]=calc_maxdis(cur->son[0], psta);
    d[1]=calc_maxdis(cur->son[1], psta);
    if (d[0]>d[1]) nid^=1;

    if (d[nid]>dis) ask(cur->son[nid], psta, dis);
    if (d[nid^1]>dis) ask(cur->son[nid^1], psta, dis);
}
void solve()
{
    for (int i=0; i<=n; ++i) (memory+i)->init();
    mem=memory;
    KDtree=NULL;

    int ans=0;
    build(KDtree, 1, n, 0);

    for (int i=1; i<=n; ++i)
        ask(KDtree, station[i], ans);
    
    printf("%d\n", ans);
}
int main()
{
    while(scanf("%d", &n)==1 && n)
    {
        read();
        solve();
    }
    return 0;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM