原题:http://coursera.cs.princeton.edu/algs4/assignments/kdtree.html
1. 问题重述
这个问题其实就是Kd-Trees那节课讲的内容,按照老师的思路来实现就好了。
这个问题就是在一个平面上有N个点,需要实现两个功能。
- 在给定一个平面内区域,让你寻找在区域中点的个数。
- 给定一个点,寻找离这个点最近的M个点
2. 分析
题目要求用两种方法求解,一种暴力方法,一种2d-Tree。
2.1 暴力搜索
功能1,将每个点都和需要搜索的区域比较。
功能2,计算给定点和每个点的距离,拿个优先队列存M个。
点的插入、删除使用红黑树,时间就能满足O(logn)。两个功能就是O(N)。
2.2 构建2d-Tree
本以为就按照课程讲的做就是了。结果实际写的过程中遇到了好多好多问题。
那这里就把思路详细的写出来了。
2d-Tree是一个变种的BST。
2.2.1 构造Node
包括Node的坐标、Node所在的矩形、Node的左/下子树、Node的右/上子树。
2.2.2 insert函数
插入的时候使用迭代的思想,将新的点插入到一个节点,然后再节点中向对应的子树插入此节点。当遇到空的子树时,插入新点。
难点1,由于不同层分割区域的方式不同,所以需要在插入函数中加入一个参数i记录所在层,通过i % 2来判断分割方式,选择比较对象x或y。
难点2,插入时存在两个假设,1.对于落在分割线上的点,假设点分在右/上子树;2.相同坐标的点只插入一次。理清楚这两个假设需要费些时间,要分清分割线上的点,和相同的点。
2.2.3 contain函数
contain函数的搜索过程和insert找插入点的过程一致,需要注意的就是区分相同点和落在分割线上的点。另外要注意的是,不要自己写判断点是否包含在矩形中,直接用矩形的成员函数。
2.2.4 range函数
递归调用,每次先判断range矩形是否在当前Node的矩形里,不在则将当前Node和其子树剪枝。若在矩形里,则继续搜索子树。
2.2.5 nearest函数
重头戏来了,坑了我最久时间的函数。
这个问题最难的点在于
- 需要判断query点在Node的哪一侧。因为需要从query点所在的一侧开始搜索。
- 搜索完一侧之后,另一侧是否剪枝。因为Node两侧子树还可能不存在。而且判题系统不允许自己使用distanceTo来判断点到切割线的距离,只能使用子树的矩形的contain函数。就需要先判断两侧子树是否存在。
然后我就把自己绕晕在这几个判断条件里了。
现在来清楚的理一下流程。依旧使用递归的方法。
- 传入节点,将节点和最短节点比较,更新最短节点。这里,第一次使用根节点作为最短节点,并传入根节点。
- 判断点在哪侧。根据切割方向,将Node里的x/y与query点的x/y来比较。
- 若在左/下侧,进入流程3。若在右/上侧,进入流程4。
- 判断左侧是否有节点,如果有节点,将节点传入nearest函数。然后判断右侧是否存在节点,若不存在则直接返回;若存在,则判断左侧距离右侧节点所在矩形的距离是否是否大于最短距离,若大于,则返回,否则搜索右侧。
- 判断右侧是否有节点,如果有节点,将节点传入nearest函数。然后判断左侧是否存在节点,若不存在则直接返回;若存在,则判断右侧距离左侧节点所在矩形的距离是否是否大于最短距离,若大于,则返回,否则搜索左侧。
流程图如下图所示。