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

来源:互联网 发布:linux中cat的用法 编辑:程序博客网 时间:2024/06/02 07:32

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

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

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

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

似乎没什么影响。

qq20120806224159

上图是一种可行的划分方式。每次找到当前处理点集[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
boolDiv[MaxN];\\记录这个区间是用什么体位划分的
voidBuildKD(intl, intr, Point p[])\\记得备份一下p
{
    if(l > r) return;
    intmid = l + r >> 1;
    intminX, 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
voidFind(intl, intr, Point a, Point p[])
{
    if(l > r)  return;
    intmid = l + r >> 1;
    longlong dist = dist2(a, p[mid]);
    if(dist > 0)//如果有重点不能这样判断
        res = min(res, dist);
    longlong d = Div[mid] ? (a.x - p[mid].x) : (a.y - p[mid].y);
    intl1, 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
别的用处。。?何老鱼说可以维护一个矩形区域的点的权值和啊之类的问题。还没细想。

0 0