zoukankan      html  css  js  c++  java
  • K-D Tree简单介绍

    K-D Tree

    简介

    K-D Tree全称 K-Dimensional Tree,也就是 (K) 维树,是一种高效的树形结构

    K-D Tree与平衡树(平衡二叉查找树)比较类似,不同在于平衡树每个节点仅仅维护一个值,而K-D Tree所维护的信息可能是 (2) 维甚至更高的,那么怎么样才能保证均摊复杂度呢?下面就来说一说

    实现

    存储与维护

    K-D Tree一般需要存储多维点信息和树上节点信息,下面是存储多维点的例子

    struct point
    {
        int d[k];
        const bool operator < (point p)const
        {
            return d[wd]<p.d[wd];
    	}
    }
    

    树上节点一般维护子树中每一维信息的最大值和最小值,以及左右儿子编号还有自己对应的一个节点的信息,下面是K-D Tree树上节点的例子

    struct Tree
    {
    	int ch[2];
    	point cur,mn,mx;
    };
    

    维护方法就是与线段树类似的pushup,可操作性很强,从左右儿子更新自己的信息,有点区别的就是需要注意更新时要加上自己节点所对应的信息,下面是维护节点信息(子树多维信息最大值最小值)的pushup例子

    void pushup(Tree p)
    {
    	for(int i=0;i<2;++i)//枚举左右儿子
    		if(p.ch[i])//如果子节点存在
    			for(int j=0;j<2;++j)//就更新信息
    				p.mn.d[j]=min(p.mn.d[j],kd[p.ch[i]].mn.d[j]),//更新第j维最小值
    				p.mx.d[j]=max(p.mx.d[j],kd[p.ch[i]].mx.d[j]);//更新第j维最大值
    }
    

    建树

    K-D Tree主要通过建树来保证树的形态较为平衡,对于树上的每一层,我们都选择一个维度进行划分,以选择的维度为分辨器来分割节点,主要有 轮转法随机法最大方差法,选择之后只要使用函数 nth_element() 来选择中位数,以中位数为划分点,小于中位数的为左儿子,大于中位数的为右儿子,递归建树即可,下面来说说维度划分

    轮转法 利用取模运算,每一层一次选择不同的维度进行划分,例如一共有 (k) 个维度,那么,第一层用第 (1) 维,第 (2) 层用第 (2) 维……第 (i) 层用第 (i\%k)

    随机法 顾名思义利用随机数随机选择一个维度进行划分

    最大方差法 计算当前要建树所有点在各个维度的方差,选择方差最大的维度进行划分

    建树的复杂度为 (Theta(nlog n))

    下面是一个例子,二维的数据以轮转法划分

    void build(int &pos,int l,int r,int dep)
    {
    	pos=0;//先赋值为0让父节点的子节点编号为0
    	if(l>r)return;//如果为空直接返回
    	int mid=(l+r)>>1;//找到最中间的位置
    	pos=mid;//以最中间位置的下标为当前节点的下标
    	wd=dep&1;//维度划分
    	nth_element(node+l,node+mid,node+r+1);//找到中位数
    	build(kdt[pos].ch[0],l,mid-1,dep+1);//建左儿子
    	build(kdt[pos].ch[1],mid+1,r,dep+1);//建右儿子
    	pushup(pos);//更新维护的数据,根据具体题目调整
    }
    

    查询

    K-D Tree的查询操作也很多样,我认为我讲的不够清晰,推荐大家看 Luogu 上@光与影的彼岸的一篇博客,讲的非常详细清晰 浅谈偏序问题与K-D Tree - 光与影的彼岸 - 洛谷博客

    我就只简单说一下求平面最近点和最远点的方法

    首先考虑朴素算法,将每个节点和所有节点比较,求出所有点对的距离,时间复杂度 (Theta(n^2))

    怎么优化呢?我们可以像A*算法一样设计一个估价函数,根据K-D Tree的划分性质和节点维护的信息,每个节点对应的子树最大值最小值可以看做一个一个的矩形,估价函数则是估计当前点到矩形的最短距离(注意这里估价函数同样需要满足任何估计代价必须小于实际代价),如果当前找到的最短距离都比估价要小,则可以放弃继续查询这棵子树,节省了大量的时间

    查询的期望复杂度为 (Theta(nsqrt n))

    这也是为什么大多数K-D Tree都是作为骗分的算法,但是由于建树方法多种多样,所以卡K-D Tree也并不是那么轻松

    下面给出求平面最近点的例子

    inline int sqr(int x){return x*x;}//平方
    inline int dis2(point x,point y){return sqr(x.d[0]-y.d[0])+sqr(x.d[1]-y.d[1]);}//欧几里得距离平方
    inline int fmin(point x,Tree y)//估价函数
    {
    	int f=0;
    	for(int i=0;i<2;++i)
    		f+=sqr(max(y.mn.d[i]-x.d[i],0.0))+sqr(max(x.d[i]-y.mx.d[i],0.0));
    	return f;
    }
    int maxans=-1;
    inline void querymin(Tree x,point y)
    {
    	if(x.cur!=y) minans=min(minans,dis2(x.cur,y));//最近点要注意不能判断到自己,否则最近点就都是自身了
    	int fl=INF,fr=INF;//估价初始化为正无穷,保证在没有儿子的时候不会继续往下进入死循环
    	if(x.ch[0]) fl=fmin(y,kd[x.ch[0]]);//左儿子估价
    	if(x.ch[1]) fr=fmin(y,kd[x.ch[1]]);//右儿子估价
    	if(fl>=fr)//优先走估价更小的子节点
    	{
    		if(fr<minans)querymin(kd[x.ch[1]],y);//估价满足就走右儿子
    		if(fl<minans)querymin(kd[x.ch[0]],y);//估价满足就走左儿子
    	}
    	else
    	{
    		if(fl<minans)querymin(kd[x.ch[0]],y);
    		if(fr<minans)querymin(kd[x.ch[1]],y);
    	}
    }
    

    插入删除

    插入操作从根节点开始一直往被划分的方向向下走,直到找到一个相同的点或者找到的节点没有对应子节点,如果找到一个相同的点就维护一个 (cnt) 累加即可,若找到的节点没有子节点就申请一个新节点,并且把新的节点编号作为找到节点的左儿子或者右儿子

    删除操作和替罪羊树的删除操作比较像,用和插入操作相同的方法找到对应节点之后,为节点打上被删除标记,上传信息的时候特判不要加入自己的信息即可

    插入和删除操作结束之后都需要上传标记

    需要注意的是,如果只是单纯插入删除点,在操作次数多了之后K-D Tree可能退化为一条链,导致效率变低,为了避免这种问题,我们需要考虑使用类似平衡树的方法来维持树的平衡,但是Splay,有旋Treap之类的旋转操作显然会破坏K-D Tree的性质,我们这时就需要替罪羊树的重构操作,将树直接拍扁然后重新建子树就能尽可能维护树的平衡

    小结

    K-D Tree能非常方便地处理多维数据,而且可扩展性较强,可以方便地支持各种操作,码量也并不算很大,只是复杂度较高,不过在OI考场上绝对是一种优秀的骗分方法,在大多数情况下都能获得很高的分数,下面给出几道例题,可以尝试一下

    Luogu P1429 平面最近点对(加强版) 枚举每一个点,K-D Tree找最近点,并记录最小值即可,复杂度 (Theta(n^{frac32}))

    Luogu P6247 [SDOI2012]最近最远点对 同上题,只是多了一个最远点

    Luogu P4357 [CQOI2016]K 远点对 注意到 (k) 很小,直接暴力把所有点的最远的 (k) 个点放入大小为 (k) 小根堆,最后堆顶就是答案

    Luogu P4169 天使玩偶/SJY摆棋子 带插入K-D Tree板子,对于每个给定点找最近点曼哈顿距离就行


    该文为本人原创,转载请注明出处

    博客园传送门

    Luogu传送门

  • 相关阅读:
    uestc1307 windy数 ——数位DP入门题
    2013年4月3日 小雨,阴
    hdu1202 The calculation of GPA ——水题
    zoj 3693 Happy Great BG
    hdu 2035 人见人爱A^B ——同余的简单性质
    zoj2913 Bus Pass ——BFS入门题
    一个bug,持续更新……
    zoj 3406 Another Very Easy Task
    poj 1995 Raising Modulo Numbers ——快速幂
    hdu 1059 Dividing ——多重背包复习
  • 原文地址:https://www.cnblogs.com/cmy-blog/p/kdtree.html
Copyright © 2011-2022 走看看