【KD树】 学习资料(转自木子日匀大神)

KD树是一种能在 O(nlog(n)) 时间内把平面划分成若干个区域,然后在均摊 O(n+k) 的时间内找到某个区域内所有点的数据结构。

其思想是,每次把当前处理的区域按照点数分成两部分,然后对两部分进行递归处理。。。

分成两部分有两种策略,一种是横着竖着横着竖着交替划分。。

一种是把坐标跨度大的那一维划分成两部分。

似乎没什么影响。

上图是一种可行的划分方式。每次找到当前处理点集 [L,R] 中的中点 mid ,以这个中点为分界线把区间划分成两部分 [L,mid1] [mid+1,R] 。注意中点是作为分界线不参与下一轮处理。(我写法是这样的)。

查询一个点的最近点时,首先令最近距离为 ,然后在KD树中查找,首先和当前区间的中点求一次距离更新答案,然后再根据该点和中点的关系决定是去左区间还是右区间,如果正好在分界线上那么两边都过去吧。

这里还有个问题,可能点被分到了左区间,但是可能和右区间的某个点比较近。那么怎么办。?

假设这个点到分界线的距离已经是大于等于当前最优答案了,那么另一个区间的所有点到这个点的距离都比当前最优答案远,只有这时候不需要考虑另一个区间

这个看起来蠢爆了好么。但是复杂度分析均摊下来的确是 O(n+k) ,详情可以参观一下《计算几何:算法与应用》那本书。

STL中提供了一个叫做nth_element的函数,可以在O(n)的复杂度下找到序列的第k大数并且把序列以第k大为界分为两半,用这个就能写出很短的建树过程了。

?
代码君
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
bool Div[MaxN];\\记录这个区间是用什么体位划分的
void BuildKD( int l, int r, Point p[])\\记得备份一下p
{
     if (l > r) return ;
     int mid = l + r >> 1;
     int minX, minY, maxX, maxY;
     minX = min_element(p + l, p + r + 1, cmpX)->x;
     minY = min_element(p + l, p + r + 1, cmpY)->y;
     maxX = max_element(p + l, p + r + 1, cmpX)->x;
     maxY = max_element(p + l, p + r + 1, cmpY)->y;
     Div[mid] = (maxX - minX >= maxY - minY);
     nth_element(p + l, p + mid, p + r + 1, Div[mid] ? cmpX : cmpY);
     BuildKD(l, mid - 1, p);
     BuildKD(mid + 1, r, p);
}

查找的时候照着思路写就可以了。

?
代码君
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void Find( int l, int r, Point a, Point p[])
{
     if (l > r)  return ;
     int mid = l + r >> 1;
     long long dist = dist2(a, p[mid]);
     if (dist > 0) //如果有重点不能这样判断
         res = min(res, dist);
     long long d = Div[mid] ? (a.x - p[mid].x) : (a.y - p[mid].y);
     int l1, l2, r1, r2;
     l1 = l, l2 = mid + 1;
     r1 = mid - 1, r2 = r;
     if (d > 0)
         swap(l1, l2), swap(r1, r2);
     Find(l1, r1, a, p);
     if (d * d < res)
         Find(l2, r2, a, p);
}

这份KD树是参照佐倉杏子的代码学习的。Orz。
相关题目:In case of failure
别的用处。。?何老鱼说可以维护一个矩形区域的点的权值和啊之类的问题。还没细想。

你可能感兴趣的:(【KD树】 学习资料(转自木子日匀大神))