KD树小结
很久之前我就想过怎么快速在二维平面上查找一个区域的信息,思考许久无果,只能想到几种优秀一点的暴力。
KD树就是干上面那件事的。
别的不多说,赶紧把自己的理解写下来,免得凉了。
KD树的组成
以维护k维空间(x,y,……)内的KD树为例,主要由一下三部分组成:
- p[k],代表树上这个结点所储存的点(在题目中给出的/你自己加上的点集中的一个点)。
- ch[2],表示它的子结点(没错,KD树是一棵二叉树)
- mi[k]与mx[k],mi/mx[i]代表KD树这个结点统辖的所有点的第i-1范围。比如说mi[1]=2,mx[1]=4,就代表这棵树统辖的点的y坐标都在[2,4]内。
不看mi和mx,长得就和splay/trie树一样,一个p维护当前节点,一个ch[2]记录左右儿子。
不看p[k],长得就和线段树一样,有左右儿子和区间信息。
没错,KD树功能如线段树,结点维护区域信息;形态如splay/trie树,每个结点有实际的值和意义。
KD树的构建
一般题目都是二维平面。下面就以二维平面KD树的构建为例。
读入把点存进结构体数组a中,坐标分别为a[x].p[i]。
inline void build(int &x,int l,int r,int type){
x=(l+r)>>;now=type;
nth_element(a+l,a+x,a+r+,cmp);
nd=a[x];newnode(x);
if(l<x)build(ch[x][],l,x-,type^);else ch[x][]=;
if(x<r)build(ch[x][],x+,r,type^);else ch[x][]=;
pushup(x);
} build(kd.root,,n,);
非常优美……对type、now作用不明的同学请继续阅读……你要现在就明白就奇怪了
系统函数nth_element(a+l,a+x,a+r+1),头文件algorithm,需定义<或cmp函数。
作用:把排序后第x大的放到第x位,比它小的放进左边,比它大的放进右边(两边无序)。
注意区间开闭:左闭右开,中间也是闭合的。
复杂度:平均,期望是O(n)?可以接受。
下面给出cmp、newnode、pushup代码。
struct Node{int p[],mi[],mx[];}a[N];
inline bool cmp(const Node &a,const Node &b){return a.p[now]<b.p[now];}
inline void Min(int &x,int y){x=x<y?x:y;}
inline void Max(int &x,int y){x=x>y?x:y;}
inline void pushup(int x){
int ls=ch[x][],rs=ch[x][];
if(ls){
Min(T[x].mi[],T[ls].mi[]);Max(T[x].mx[],T[ls].mx[]);
Min(T[x].mi[],T[ls].mi[]);Max(T[x].mx[],T[ls].mx[]);
}
if(rs){
Min(T[x].mi[],T[rs].mi[]);Max(T[x].mx[],T[rs].mx[]);
Min(T[x].mi[],T[rs].mi[]);Max(T[x].mx[],T[rs].mx[]);
}
} inline void newnode(int x){
T[x].p[]=T[x].mi[]=T[x].mx[]=nd.p[];
T[x].p[]=T[x].mi[]=T[x].mx[]=nd.p[];
}
不要问我为什么辣么长,为了减常冲榜,把循环展开了……
聪明的读者已经发现KD树的构建巧妙之处。它不是纯粹按照x维,或者某一维排序,而是先按x维,再按y维,再按z维,再……最后又回到x维……
这样分割的区域更加整齐划一更加均匀紧缩,不像上面的按照某一维划分,到最后变成一条条长条,KD树划分到底的图案还是很好看的。
这样分割有什么好处呢?等你真正领悟了KD树的精髓之后你就会发现……嘿嘿嘿……
(就是为了把这个暴力数据结构剪枝剪更多跑更快)
KD树的操作
1.往KD树上插点
插点可以分为插新点和插老点。如果有老点,特判一句,把信息覆盖即可。
inline void insert(int &x,int type){
if(!x){x=++cnt,newnode(cnt);return;}
if(nd.p[]==T[x].p[] && nd.p[]==T[x].p[]){
……(自行维护);return;
}
if(nd.p[type]<T[x].p[type])insert(ch[x][],type^);
else insert(ch[x][],type^);
pushup(x);
}
依然非常的美妙……等等有什么不对?
我们能估计出一棵刚建好的KD树深度是O(log)的。
但你这么随便乱插……有道题叫HNOI2017 spaly 插入不旋转的单旋spaly见过?T成苟。
这都不是问题!知不知道有一种数据结构叫做替罪羊树哇?
知道替罪羊树怎么保证复杂度的吗?
重构!大力重构!自信重构!不爽就重构!
为了省事大概每插入10000次就重构一次好了……
if(kd.cnt==sz){
for(int i=;i<=sz;++i)a[i]=kd.T[i];
kd.rebuild(kd.root,,sz,);sz+=;
}
2.在KD树上查询
- 如果是单点(给定点)查询:
- 太简单啦!把插入改改就阔以辣!
- 如果是查询距离一个点(x',y')最近的点(曼哈顿距离,|x-x'|+|y-y'|):
- 首先我们看暴力的剪枝:按某一维排序,如果该维的差过大就不管了。
- 而令我们期待的KD树呢?呃不好意思,它也是这么做的……
- 我们维护过两个叫做mi[]和mx[]的东西吧……这个时候就是它派上用场了。
- 具体还请看代码吧:
//查询的点(x',y')储存在nd中。
//这里的l,r就是mi,mx的意思。
inline int dis(Node p,int x,int ans=){
for(int i=;i<;++i)
ans+=max(,t[x].l[i]-p.p[i])+max(,p.p[i]-t[x].r[i]);
return ans;
} inline void query(int x){
Ans=min(Ans,abs(t[x].p[]-nd.p[])+abs(t[x].p[]-nd.p[]));
int dl=ch[x][]?dis(nd,ch[x][]):Inf;
int dr=ch[x][]?dis(nd,ch[x][]):Inf;
if(dl<dr){
if(dl<Ans)query(ch[x][]);
if(dr<Ans)query(ch[x][]);
}
else{
if(dr<Ans)query(ch[x][]);
if(dl<Ans)query(ch[x][]);
}
} - dis():如果当前点在这个区间内就是0,否则就是最极的点到它的距离。
- 聪明绝顶的你已经发现了……这TM就是个暴力。
- 其实这个数据结构就是一个暴力……
- 当暴力有了时间复杂度证明……还叫暴力么?读书人的事,能叫偷么?
- 这么暴力有几个好处:不用枚举所有点;剪枝有效及时。
- 复杂度有保障,大概在O(√n)级别下,主要看数据。
- 如果是区间查询,以区间查询点权和为例(之前就有维护好):
inline bool in(int l,int r,int xl,int xr){return l<=xl && xr<=r;}
inline bool out(int l,int r,int xl,int xr){return xr<l || r<xl;} inline int query(int x,int x1,int y1,int x2,int y2){
int ans=;if(!x)return ans;
if(in(x1,x2,T[x].mi[],T[x].mx[]))
if(in(y1,y2,T[x].mi[],T[x].mx[]))
return T[x].sum;
if(out(x1,x2,T[x].mi[],T[x].mx[]))return ;
if(out(y1,y2,T[x].mi[],T[x].mx[]))return ;
if(in(x1,x2,T[x].p[],T[x].p[]))
if(in(y1,y2,T[x].p[],T[x].p[]))
ans+=T[x].val;
return ans+query(ch[x][],x1,y1,x2,y2)+query(ch[x][],x1,y1,x2,y2);
}- 别看代码长又看起来复杂,写起来跟线段树似的,还是一样的暴力搞。
KD树的基本姿势大概就是这个样子……好写不好写错,基本上都是个板子。
附上学长的一(三)句话,从各方面进行了深度总结:
“能不写最好还是不要写吧,轻松被卡→_→,也许可以出奇制胜?如果要写,重新构树是个不错的选择。发现大数据跑不过,多半是剪枝挂了。”
还是给个链接……MashiroSky大爷。
upd:以当前坐标差最大的来做type应该比轮换type更优秀……
例题有"SJY摆棋子"、"简单题"等,在此就不做赘述了。
比较有意思的应用就是【bzoj3489】 A simple rmq problem,正如上面所言,KD树解决传统数据结构题出奇制胜。
附上"BZOJ4066简单题"代码一份,操作是单点修改+矩形求和在线。
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <vector>
#include <cstring>
#include <queue>
#include <complex>
#include <stack>
#define LL long long int
#define dob double
#define FILE "bzoj_4066"
//#define FILE "简单题"
using namespace std; const int N = ;
int n,lst,now,sz=; inline int gi(){
int x=,res=;char ch=getchar();
while(ch>''||ch<''){if(ch=='-')res*=-;ch=getchar();}
while(ch<=''&&ch>='')x=x*+ch-,ch=getchar();
return x*res;
} inline void Min(int &x,int y){x=x<y?x:y;}
inline void Max(int &x,int y){x=x>y?x:y;}
struct Node{int p[],mi[],mx[],val,sum;}a[N];
inline bool cmp(const Node &a,const Node &b){return a.p[now]<b.p[now];}
struct KD_Tree{
int ch[N][],root,cnt;
Node T[N],nd; inline void pushup(int x){
int ls=ch[x][],rs=ch[x][];
if(ls){
Min(T[x].mi[],T[ls].mi[]);Max(T[x].mx[],T[ls].mx[]);
Min(T[x].mi[],T[ls].mi[]);Max(T[x].mx[],T[ls].mx[]);
}
if(rs){
Min(T[x].mi[],T[rs].mi[]);Max(T[x].mx[],T[rs].mx[]);
Min(T[x].mi[],T[rs].mi[]);Max(T[x].mx[],T[rs].mx[]);
}
T[x].sum=T[x].val;
if(ls)T[x].sum+=T[ls].sum;
if(rs)T[x].sum+=T[rs].sum;
} inline void newnode(int x){
T[x].p[]=T[x].mi[]=T[x].mx[]=nd.p[];
T[x].p[]=T[x].mi[]=T[x].mx[]=nd.p[];
T[x].sum=T[x].val=nd.val;
} inline void insert(int &x,int type){
if(!x){x=++cnt,newnode(cnt);return;}
if(nd.p[]==T[x].p[] && nd.p[]==T[x].p[]){
T[x].val+=nd.val;T[x].sum+=nd.val;
return;
}
if(nd.p[type]<T[x].p[type])insert(ch[x][],type^);
else insert(ch[x][],type^);
pushup(x);
} inline void rebuild(int &x,int l,int r,int type){
x=(l+r)>>;now=type;
nth_element(a+l,a+x,a+r+,cmp);
nd=a[x];newnode(x);
if(l<x)rebuild(ch[x][],l,x-,type^);else ch[x][]=;
if(x<r)rebuild(ch[x][],x+,r,type^);else ch[x][]=;
pushup(x);
} inline bool in(int l,int r,int xl,int xr){return l<=xl && xr<=r;}
inline bool out(int l,int r,int xl,int xr){return xr<l || r<xl;} inline int query(int x,int x1,int y1,int x2,int y2){
int ans=;if(!x)return ans;
if(in(x1,x2,T[x].mi[],T[x].mx[]))
if(in(y1,y2,T[x].mi[],T[x].mx[]))
return T[x].sum;
if(out(x1,x2,T[x].mi[],T[x].mx[]))return ;
if(out(y1,y2,T[x].mi[],T[x].mx[]))return ;
if(in(x1,x2,T[x].p[],T[x].p[]))
if(in(y1,y2,T[x].p[],T[x].p[]))
ans+=T[x].val;
return ans+query(ch[x][],x1,y1,x2,y2)+query(ch[x][],x1,y1,x2,y2);
} }kd; int main()
{
freopen(FILE".in","r",stdin);
freopen(FILE".out","w",stdout);
n=gi();
while(){
int type=gi();if(type==)break;
int x1=gi()^lst,y1=gi()^lst;
if(type==){
int A=gi()^lst;
kd.nd.p[]=x1;kd.nd.p[]=y1;
kd.nd.sum=kd.nd.val=A;
kd.insert(kd.root,);
if(kd.cnt==sz){
for(int i=;i<=sz;++i)a[i]=kd.T[i];
kd.rebuild(kd.root,,sz,);sz+=;
}
}
if(type==){
int x2=gi()^lst,y2=gi()^lst;
lst=kd.query(kd.root,x1,y1,x2,y2);
printf("%d\n",lst);
}
}
fclose(stdin);fclose(stdout);
return ;
}
简单题
KD树小结的更多相关文章
- 02-17 kd树
目录 kd树 一.kd树学习目标 二.kd树引入 三.kd树详解 3.1 构造kd树 3.1.1 示例 3.2 kd树搜索 3.2.1 示例 四.kd树流程 4.1 输入 4.2 输出 4.3 流程 ...
- 利用KD树进行异常检测
软件安全课程的一次实验,整理之后发出来共享. 什么是KD树 要说KD树,我们得先说一下什么是KNN算法. KNN是k-NearestNeighbor的简称,原理很简单:当你有一堆已经标注好的数据时,你 ...
- 2016 ICPC青岛站---k题 Finding Hotels(K-D树)
题目链接 http://acm.hdu.edu.cn/showproblem.php?pid=5992 Problem Description There are N hotels all over ...
- kd树和knn算法的c语言实现
基于kd树的knn的实现原理可以参考文末的链接,都是一些好文章. 这里参考了别人的代码.用c语言写的包括kd树的构建与查找k近邻的程序. code: #include<stdio.h> # ...
- PCL点云库:Kd树
Kd树按空间划分生成叶子节点,各个叶子节点里存放点数据,其可以按半径搜索或邻区搜索.PCL中的Kd tree的基础数据结构使用了FLANN以便可以快速的进行邻区搜索.FLANN is a librar ...
- KNN算法与Kd树
最近邻法和k-近邻法 下面图片中只有三种豆,有三个豆是未知的种类,如何判定他们的种类? 提供一种思路,即:未知的豆离哪种豆最近就认为未知豆和该豆是同一种类.由此,我们引出最近邻算法的定义:为了判定未知 ...
- k临近法的实现:kd树
# coding:utf-8 import numpy as np import matplotlib.pyplot as plt T = [[2, 3], [5, 4], [9, 6], [4, 7 ...
- 从K近邻算法谈到KD树、SIFT+BBF算法
转自 http://blog.csdn.net/v_july_v/article/details/8203674 ,感谢july的辛勤劳动 前言 前两日,在微博上说:“到今天为止,我至少亏欠了3篇文章 ...
- bzoj 3489: A simple rmq problem k-d树思想大暴力
3489: A simple rmq problem Time Limit: 10 Sec Memory Limit: 512 MBSubmit: 551 Solved: 170[Submit][ ...
随机推荐
- window.location.href跳转至空白页
现象:window.location.href = "XXX"调到了空白页,但是将XXX在窗口地址栏输入就会可以访问到. 原因:就是XXX前缀没有加上"http://&q ...
- [C#]使用GroupJoin将两个关联的集合进行分组
本文为原创文章.源代码为原创代码,如转载/复制,请在网页/代码处明显位置标明原文名称.作者及网址,谢谢! 本文使用的开发环境是VS2017及dotNet4.0,写此随笔的目的是给自己及新开发人员作为参 ...
- 两天快速开发一个自己的微信小程序
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px "Songti SC" } p.p2 { margin: 0.0px 0. ...
- canvas画一个时钟
效果图如下 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF ...
- openstack pike 创建vxlan网络
#openstack pike 创建vxlan网络 openstack pike 集群高可用 安装部署 汇总 http://www.cnblogs.com/elvi/p/7613861.html # ...
- QQ空间掉帧率优化实战
商业转载请联系腾讯WeTest获得授权,非商业转载请注明出处. WeTest 导读 空间新业务需求日益增多,在业务开发阶段的疏忽,或者是受到其他业务的影响(比如一些非空间的业务网络回包或者逻辑在主线程 ...
- yii2 邮件发送
修改配置文件mail-local.php 'mailer' => [ 'class' => 'yii\swiftmailer\Mailer', 'useFileTransport' =&g ...
- Python创建二维数组(关于list的一个小坑)
0.目录 1.遇到的问题 2.创建二维数组的办法 3.1 直接创建法 3.2 列表生成式法 3.3 使用模块numpy创建 1.遇到的问题 今天写Python代码的时候遇到了一个大坑,差点就耽误我交作 ...
- Android测试:Testing Apps on Android
原文:https://developer.android.com/training/testing/index.html 测试你的App是开发过程中的重要组成部分.通过对应用程序持续的运行测试,你可以 ...
- CSS中line-height与vertical-align
参考文章: 深入了解CSS的line-height属性 Vertical-Align: 你需要知道的所有事[译] Vertical-Align: All You Need To Know 1.什么是行 ...