k-d tree入门

暑期集训开始了
本篇博客将会简单讲一下k-d tree的原理以及实现

首先大家要先了解一下二叉搜索树

二叉搜索树是一个很简单常见的数据结构,他符合以下两个特征
1.一个节点若有左子树,则左子树上的点全部小于该节点;若有右子树,则右子树上的节点全部大于该节点。
2.它的左右子树也符合这个性质。

大家可以发现,二叉搜索树可以对一个一维数组进行维护,每次查询时只要看需要查询的数值比当前节点大还是小就行了,大就去右子树找,小就去左子树找。(相等的话当然就找到了)

将它形象解释一下就是在数轴上的一些点中,选取其中一个点作为中间节点,左边的点都保存到该节点左子树中,右边的节点都保存到右子树中。然后对于左边的所有点以及右边的所有点再分别做这个处理,直到不能再划分为止。

以上是二叉搜索树的部分。

那么k-d tree又是什么呢?
k-d 是 k-dimensional 的缩写,也就是k维树,换句话说就是说这个树是维护一个k维元素的树,这个树上的节点有k个分量。一个k维的二叉搜索树就是k-d tree了。

1.建树
问题来了,既然每个元素有k个分量,我们以哪一个分量作为标准去划分左右子树呢?
对每个分量依次轮流划分当然是可以的。
更好的是每次对跨度最大的那一个分量进行划分。

怎么理解?
我们假设现在k=2,有n个2维的点(x,y),我们要将其建成一个k-d tree。

如果每个分量轮流作为度量那就是以下情况:
现在这些点都在一个矩形中,我们先将这个正方形竖着切一刀,这样相当于对x划分了一下,左边的点都进左子树,右边的点都进右子树。
然后对于左边的矩形和右边的矩形我们再分别横着切一刀,将上下两边的点分别进左右子树。
以此类推……

而每次选跨度大的分量划分则是每次看所要划分的矩形究竟是水平的长度长还是垂直的长度长。那么大家就可以脑补了。

现在另一个问题来了,确定了我们每次划分的维度之后,我们究竟以哪一个结点作为划分呢?(相当于究竟在矩形什么位置切一刀?)
我们当然是希望左右子树的节点数越接近越好,所以答案就显而易见了,我们只要在中位数的位置划分左右子树就行了。

以下是图片演示:
k-d tree入门_第1张图片
其中
黄色:第一层
蓝色:第二层
棕色:第三层
绿色:第四层

2.查询
k-d tree常常用来处理的问题是查询k维空间里一个点最邻近的点。
做法就是从根节点出发,判断每个点能否更新当前的最近距离。然后判断所查询的点在左子树还是右子树,进入相应的子树继续更新最近距离。

但是有个问题,查询的点不在的那个区间也有可能有最近的点在,所以我们需要在回溯的时候判断需不需要递归到另一个区间内查询。
判断的条件就是当前维护的最近距离比目标点到分割两个区间的超平面的距离大,或者换句话说就是以目标点为球心,当前最近距离为半径的超球面与被分割出的另一部分相交。

查询操作最好情况是O(logn) 最坏情况是O(n)

以下是hdu2966AC代码
hdu2966

#include
#define INF 9223372036854775807
using namespace std;

const int maxn = 100055;
int K;
const int maxK = 5;
int now;
int n,m;
struct node
{
    long long int d[maxK];
    int id;
}tree[maxn*4],a[maxn],b[maxn];
bool cmp(node a,node b)
{
    return a.d[now]long long int ans;
int key[maxn*4];
double var[maxn*4];
long long int dis(node a,node b)
{
    long long int ans = 0;
    for(int i=1;i<=K;i++)
    {
        ans+=(a.d[i]-b.d[i])*(a.d[i]-b.d[i]);
    }
    return ans;
}
int cnt = 0;
void build(int rt,int l,int r)
{
    if(l>r)
        return;
    now = 1;
    key[rt]=1;
    for(int i=1;i<=K;i++)
    {
        double ave = 0.0;
        var[i]=0.0;
        for(int j=l;j<=r;j++)
        {
            ave+=tree[j].d[i];
        }
        ave/=(r-l+1);

        for(int j=l;j<=r;j++)
        {
            var[i]+=(ave-tree[j].d[i])*(ave-tree[j].d[i]);
        }
        var[i]/=(r-l+1);
        if(var[i]>var[key[rt]])
        {
            key[rt]=i;
            now=i;
        }
    }
    int mid = (l+r)/2;
    nth_element(a+l,a+mid,a+r+1,cmp);
    tree[rt]=a[mid];
    build(rt*2,l,mid-1);
    build(rt*2+1,mid+1,r);
}
void query(int n,int l,int r)
{
    if(l>r)
        return ;
    int mid=(l+r)/2;
    long long int dist=dis(q,tree[n]);
    if(q.id!=tree[n].id)
        if(distint kkey=key[n];
    long long int ra=(q.d[kkey]-tree[n].d[kkey])*(q.d[kkey]-tree[n].d[kkey]);
    if(q.d[kkey]2,l,mid-1);
        if(ra<=ans)
            query(n*2+1,mid+1,r);
    }
    else
    {
        query(n*2+1,mid+1,r);
        if(ra<=ans)
            query(n*2,l,mid-1);
    }
}
int main()
{
    int t;
    K=2;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d",&n);
        for(int i=1;i<=n;i++)
        {
            scanf("%lld%lld",&a[i].d[1],&a[i].d[2]);
            a[i].id=i;
            b[i]=a[i];
        }
        build(1,1,n);
        for(int i=1;i<=n;i++)
        {
            q=b[i];
            ans=INF;
            query(1,1,n);
            printf("%lld\n",ans);
        }
    }
    return 0;
}


你可能感兴趣的:(ACM-k-d,tree)