设想这么一个简单的问题,在一个平面上有n个点,给定一个矩形,问位于矩形内的点有哪些。
这个问题的简单思路非常简单,每次遍历所有点,看其是否在给定的矩形中。时间复杂度呢?单次查询的时间就是一次遍历的时间,也就是O(n),但如果给定的点基本不变,但查询量特别大,每次查询都要以O(n)的复杂度。能不能把给定的数据预处理一下,然后以后每次查询的复杂度降低呢?
一个基本的思路是把相邻的点用最小包围矩形包起来,然后再递归地处理这些矩形,以更大的最小包围矩形包围这些相邻的矩形。下图是个简单的例子:
在查询一个矩形内的所有点时,先再矩形和树的根求交,如果没有交集,说明矩形内没有点。否则说明矩形内可能包含有给定的点,于是就把矩形与根的儿子求交,这样递归下去即可。通过这种方法,我们在建树的时候,用了O(n)的时间,再以后每次查询的时候,大概是log(n)的时间
现在的难题是:如果判断两个点是否相邻?用他们之间的距离来作为标准肯定不合适
希尔伯特距离和希尔伯特树(http://en.wikipedia.org/wiki/Hilbert_R-tree)在这里可以得到很好的应用。什么是希尔伯特距离?
我们把一个给定区域按如下方式来划分,划分完成后,每个点到原点处的线段长度就是希尔伯特距离。如在H2中,点2的希尔伯特距离是2,点6的希尔伯特距离是6.
现在我们把给定的所有点的希尔伯特距离都算出来,然后根据希尔伯特距离来将它们作升序排序,然后就可以建树了
如何算希尔伯特距离呢?暴力方法肯定不行,有个比较好的方法如下:
- For all points, find max_x and max_y.
- Find the max of max_x and max_y and call this max_xy.
- If max_xy is a power of two, leave it. Else make it the next higher power of two.
- For each point:
1. Initialize w to be max_xy / 2. Dist = 0.
2. Find the quadrant on a hilbert curve that the point isin.
3. Dist += (quadrant * w * w)
4. w becomes w / 2
5. Calculate xnew and ynew according to the formulas
6. Repeat steps 2 to 5 until w becomes 0
Quadrant |
x_new |
y_new |
0 |
y |
x |
1 |
x |
y - w |
2 |
x - w |
y - w |
3 |
w - y - 1 |
w * 2 - x - 1 |
/** * Find the Hilbert order (=vertex index) for the given grid cell * coordinates. * @param x cell column (from 0) * @param y cell row (from 0) * @param r resolution of Hilbert curve (grid will have Math.pow(2,r) * rows and cols) * @return Hilbert order */ public static int encode(int x, int y, int r) { int mask = (1 << r) - 1; int hodd = 0; int heven = x ^ y; int notx = ~x & mask; int noty = ~y & mask; int temp = notx ^ y; int v0 = 0, v1 = 0; for (int k = 1; k < r; k++) { v1 = ((v1 & heven) | ((v0 ^ noty) & temp)) >> 1; v0 = ((v0 & (v1 ^ notx)) | (~v0 & (v1 ^ noty))) >> 1; } hodd = (~v0 & (v1 ^ x)) | (v0 & (v1 ^ noty)); return interleaveBits(hodd, heven); } /** * Interleave the bits from two input integer values * @param odd integer holding bit values for odd bit positions * @param even integer holding bit values for even bit positions * @return the integer that results from interleaving the input bits * * @todo: I'm sure there's a more elegant way of doing this ! */ private static int interleaveBits(int odd, int even) { int val = 0; // Replaced this line with the improved code provided by Tuska // int n = Math.max(Integer.highestOneBit(odd), Integer.highestOneBit(even)); int max = Math.max(odd, even); int n = 0; while (max > 0) { n++; max >>= 1; } for (int i = 0; i < n; i++) { int bitMask = 1 << i; int a = (even & bitMask) > 0 ? (1 << (2*i)) : 0; int b = (odd & bitMask) > 0 ? (1 << (2*i+1)) : 0; val += a + b; } return val; }
之后的建树和搜索就不用多介绍了