zoukankan      html  css  js  c++  java
  • kd 树总结


    kd 树是一种分割 k 维数据空间的数据结构。
    它通常被用来解决 k 维空间中的距离最值 ( 第 k 小值 ) 问题。
    当然,它也能解决其它问题。

    建树的方法:
    假设我们的平面上的点的序列为 [l,r] 。
    我们先选定一个维度为基准,不妨假设是 x 维度。
    然后我们找出 [l,r] 这些点按照 x 坐标排序,找到 x 坐标是中位数的那个点,把它加到树上。
    之后递归建该点的左儿子 ->( 在 [l,mid-1] 这些点中,以 y 维度为基准 ) 。
    再递归建该点的右儿子 ->( 在 [mid+1,r] 这些点中,以 y 维度为基准 ) 。
    其实这个基准就是一层按 x 维度,这层的下一层按 y 维度,再下一层按 x 维度 .....
    在建树的同时我们要知道树上某点及其子树确定的矩形范围:只需记录一个点的子树中,x,y坐标的最小,最大值即可。
    空间复杂度是线性的。
    KD树有两种常见应用:

    一、查询距离最值

    比如:现在给定二维空间下的 n 个点,我们多次询问这些点到给定某点的欧几里得/曼哈顿距离的最小值。

    从根查询,查询到树上的某个点后,我们判断该点到树上该点确定的矩形范围的距离值,并且通过该值与目前答案比较,进行剪枝。
    如果左儿子的距离优于右儿子,那么按照左儿子,右儿子的顺序进行求值。反之按照右儿子,左儿子的顺序进行求值。

    如果用堆维护,可以把最小值变成前K小值。
    时间复杂度:(O(sqrt{n}))

    例题:

    代码:

    #include <stdio.h>
    #include <algorithm>
    #include <queue>
    using namespace std;
    #define ll long long
    #define min(a,b) ((a)<(b)?(a):(b))
    #define max(a,b) ((a)>(b)?(a):(b))
    #define pf(x) 1ll*(x)*(x)
    #define re register
    ll dis(re int x1,re int y1,re int x2,re int y2)
    {
    	re ll tx=x1-x2,ty=y1-y2;
    	return tx*tx+ty*ty;
    }
    int cl[100010],cr[100010],px[100010],py[100010];
    int lx[100010],rx[100010],ly[100010],ry[100010],root;
    void pushup(int i)
    {
    	lx[i]=rx[i]=px[i];
    	ly[i]=ry[i]=py[i];
    	if(cl[i]!=0)
    	{
    		lx[i]=min(lx[i],lx[cl[i]]);rx[i]=max(rx[i],rx[cl[i]]);
    		ly[i]=min(ly[i],ly[cl[i]]);ry[i]=max(ry[i],ry[cl[i]]);
    	}
    	if(cr[i]!=0)
    	{
    		lx[i]=min(lx[i],lx[cr[i]]);rx[i]=max(rx[i],rx[cr[i]]);
    		ly[i]=min(ly[i],ly[cr[i]]);ry[i]=max(ry[i],ry[cr[i]]);
    	}
    }
    int cmpx(int a,int b)
    {
    	return px[a]<px[b];
    }
    int cmpy(int a,int b)
    {
    	return py[a]<py[b];
    }
    int buiy(int sz[100010],int l,int r);
    int buix(int sz[100010],int l,int r)
    {
    	if(l>r)return 0;
    	int m=(l+r)>>1;
    	nth_element(sz+l,sz+m,sz+r+1,cmpx);
    	int rt=sz[m];
    	cl[rt]=buiy(sz,l,m-1);
    	cr[rt]=buiy(sz,m+1,r);
    	pushup(rt);
    	return rt;
    }
    int buiy(int sz[100010],int l,int r)
    {
    	if(l>r)return 0;
    	int m=(l+r)>>1;
    	nth_element(sz+l,sz+m,sz+r+1,cmpy);
    	int rt=sz[m];
    	cl[rt]=buix(sz,l,m-1);
    	cr[rt]=buix(sz,m+1,r);
    	pushup(rt);
    	return rt;
    }
    inline ll getmax(re int x,re int y,re int i)
    {
    	return max(pf(x-lx[i]),pf(x-rx[i]))+max(pf(y-ly[i]),pf(y-ry[i]));
    }
    struct SJd
    {
    	int i;
    	ll z;
    	SJd(){}
    	SJd(int I,ll Z)
    	{
    		i=I;z=Z;
    	}
    };
    bool operator<(const SJd a,const SJd b)
    {
    	if(a.z==b.z)
    		return a.i<b.i;
    	return a.z>b.z;
    }
    priority_queue<SJd> pq;
    void dfs(re int u,re int x,re int y)
    {
    	re SJd t=SJd(u,dis(x,y,px[u],py[u]));
    	if(t<pq.top())
    	{
    		pq.pop();
    		pq.push(t);
    	}
    	ll d1=getmax(x,y,cl[u]),d2=getmax(x,y,cr[u]);
    	if(d1>d2)
    	{
    		if(cl[u]!=0&&d1>=pq.top().z)
    			dfs(cl[u],x,y);
    		if(cr[u]!=0&&d2>=pq.top().z)
    			dfs(cr[u],x,y);
    	}
    	else
    	{
    		if(cr[u]!=0&&d2>=pq.top().z)
    			dfs(cr[u],x,y);
    		if(cl[u]!=0&&d1>=pq.top().z)
    			dfs(cl[u],x,y);
    	}
    }
    int getans(int x,int y,int k)
    {
    	while(!pq.empty())
    		pq.pop();
    	for(int i=0;i<k;i++)
    		pq.push(SJd(-1,-1));
    	dfs(root,x,y);
    	return pq.top().i;
    }
    int sz[100010];
    int main()
    {
    	int n,m;
    	scanf("%d",&n);
    	for(int i=1;i<=n;i++)
    		scanf("%d%d",&px[i],&py[i]);
    	scanf("%d",&m);
    	for(int i=1;i<=n;i++)
    		sz[i]=i;
    	root=buix(sz,1,n);
    	for(int i=0;i<m;i++)
    	{
    		int x,y,k;
    		scanf("%d%d%d",&x,&y,&k);
    		printf("%d
    ",getans(x,y,k));
    	}
    	return 0;
    }
    

    二、矩形修改,矩形查询

    比如:区间连边的最短路,半平面覆盖等。
    从根出发,查询到树上的某个点后,若这个点的矩形区域完全包含在修改区域中,则直接打标记。
    若这个点的矩形区域和修改区域没有交集,则直接返回。
    查询和修改类似,注意尽量剪枝。
    有点类似线段树。
    但要注意,遍历到一个点时,要把这个点单独考虑一下。
    时间复杂度未知,但通常很快。

    例题1:

    例题2:
    每次删去一个半平面中的点,问每个点的第一次删除的时间。
    因为每个点只考虑第一次,因此暴力删除即可。
    可以添加一个剪枝:记录子树中还有几个点。
    这样,如果子树空了就直接返回。

    代码:

    #include <stdio.h>
    #include <stdlib.h>
    #include <math.h>
    #define eps 1e-7
    double max(double a,double b)
    {
    	return a>b?a:b;
    }
    double min(double a,double b)
    {
    	return a<b?a:b;
    }
    int sgn(double x)
    {
    	if(x>eps)
    		return 1;
    	else if(x<-eps)
    		return -1;
    	return 0;
    }
    struct SPx
    {
    	double x,y;
    	int i;
    };
    int cmpx(const void*a,const void*b)
    {
    	return sgn(((SPx*)a)->x-((SPx*)b)->x);
    }
    int cmpy(const void*a,const void*b)
    {
    	return sgn(((SPx*)a)->y-((SPx*)b)->y);
    }
    double lx[100010],ly[100010],rx[100010],ry[100010],X[100010],Y[100010];
    int cl[100010],cr[100010],si[100010],ans[100010];
    int buiy(SPx sz[100010],int l,int r);
    void up(int rt)
    {
    	si[rt]=si[cl[rt]]+si[cr[rt]]+(ans[rt]==-1);
    }
    void pushup(int rt)
    {
        if(cl[rt]!=0)
        {
            lx[rt]=min(lx[rt],lx[cl[rt]]);rx[rt]=max(rx[rt],rx[cl[rt]]);
            ly[rt]=min(ly[rt],ly[cl[rt]]);ry[rt]=max(ry[rt],ry[cl[rt]]);
        }
        if(cr[rt]!=0)
        {
            lx[rt]=min(lx[rt],lx[cr[rt]]);rx[rt]=max(rx[rt],rx[cr[rt]]);
            ly[rt]=min(ly[rt],ly[cr[rt]]);ry[rt]=max(ry[rt],ry[cr[rt]]);
        }
    	up(rt);
    }
    int buix(SPx sz[100010],int l,int r)
    {
        if(l>=r)return 0;
        qsort(sz+l,r-l,sizeof(SPx),cmpx);
        int m=(l+r-1)>>1,rt=sz[m].i;
        lx[rt]=rx[rt]=sz[m].x;
        ly[rt]=ry[rt]=sz[m].y;
        cl[rt]=buiy(sz,l,m);
        cr[rt]=buiy(sz,m+1,r);
    	pushup(rt);
        return rt;
    }
    int buiy(SPx sz[100010],int l,int r)
    {
        if(l>=r)return 0;
        qsort(sz+l,r-l,sizeof(SPx),cmpy);
        int m=(l+r-1)>>1,rt=sz[m].i;
        lx[rt]=rx[rt]=sz[m].x;
        ly[rt]=ry[rt]=sz[m].y;
        cl[rt]=buix(sz,l,m);
        cr[rt]=buix(sz,m+1,r);
    	pushup(rt);
        return rt;
    }
    SPx px[100010];
    bool inside(double x,double y,double k,double a)
    {
    	return sgn(y-(k-x*a))>0;
    }
    bool inside(int i,double k,double a)
    {
    	return inside(lx[i],ly[i],k,a);
    }
    bool outside(int i,double k,double a)
    {
    	return !inside(rx[i],ry[i],k,a);
    }
    void mark(int u,int x)
    {
    	if(u==0)
    		return;
    	if(ans[u]==-1)
    		ans[u]=x;
    	si[u]=0;
    	mark(cl[u],x);
    	mark(cr[u],x);
    }
    void fugai(int u,double k,double a,int x)
    {
    	if(si[u]==0||outside(u,k,a))
    		return;
    	if(inside(u,k,a))
    	{
    		mark(u,x);
    		return;
    	}
    	if(ans[u]==-1&&inside(X[u],Y[u],k,a))
    		ans[u]=x;
    	fugai(cl[u],k,a,x);fugai(cr[u],k,a,x);
    	up(u);
    }
    int main()
    {
    	freopen("point.in","r",stdin);
    	freopen("point.out","w",stdout);
    	int n,m;
    	scanf("%d%d",&n,&m);
    	for(int i=1;i<=n;i++)
    	{
    		double x,y;
    		scanf("%lf%lf",&x,&y);
    		X[i]=px[i].x=log(x);
    		Y[i]=px[i].y=log(y);
    		ans[i]=-1;px[i].i=i;
    	}
    	int ro=buix(px,1,n+1);
    	for(int i=1;i<=m;i++)
    	{
    		double k,a;
    		scanf("%lf%lf",&k,&a);
    		k=log(k);
    		fugai(ro,k,a,i);
    	}
    	for(int i=1;i<=n;i++)
    		printf("%d
    ",ans[i]);
    	return 0;
    }
    
  • 相关阅读:
    基于jQuery的自定义插件:实现整屏分页转换的功能
    蝶恋花
    js中面向对象编程
    移动端web页面列表类上拉加载,查看详情,iframe嵌套第三方页面遇到的问题以及解决办法
    控制使用jquery load()方法载入新页面中的元素
    bootstrap-daterangepicker双日历控件开始日期选择问题
    点击select下拉框获取option的属性值
    bootstrap table表格前台分页,点击tab选项,重新刷新表格
    jquery中使元素显示和隐藏方法之间的区别
    jquery对象和DOM对象的相互转换
  • 原文地址:https://www.cnblogs.com/lnzwz/p/12819554.html
Copyright © 2011-2022 走看看