我想开发一种范围搜索算法,报告查询点给定距离内的所有点。
这些点由d个整数坐标指定,范围很小,比如每个维度最多6位(范围0..63),总位数不超过60位。
距离度量是曼哈顿或欧几里德(由您决定),即绝对或平方坐标差的总和。在每个维度单个位的特殊情况下,它等于汉明距离。
可能有多达一百万点。
在这种情况下,您是否了解支持快速查询的实用数据结构,例如O(Log²(n)+k)
或类似(带空格O(n)
)?还需要合理的预处理时间(子二次)。
k-D
树是第一种选择,但是他们不会利用坐标的有限性而且很可能在高维度上表现不佳,我担心。
每个坐标一位的情况特别有趣。甚至部分解决方案也是受欢迎的。
答案 0 :(得分:0)
在使用VP树(Vantage Point Tree https://en.wikipedia.org/wiki/Vantage-point_tree)进行一些思考(并由@YvesDaoust刺激)后,可能是最好的解决方案。
VP Tree是一个BSP,左边的节点在距离内,右边的节点在距离之外。这适用于每个维度的单个位和每个维度的多个位(仅距离公式将更改。距离是每个树节点的阈值/半径。查询涉及通过树递归获取当前节点值的距离到查询值并将该结果与查询距离进行比较。
JSFiddle http://jsfiddle.net/fgq1rfLk/
var DIMS = 16;
var BITS = 1;
var MASK = (Math.pow(2, BITS) - 1)|0;
var SIZE = DIMS * BITS;
var list = [];
var tree = null;
//
// set bit count (population count)
function popCnt(x) {
x = x - ((x >> 1) & 0x55555555);
x = (x & 0x33333333) + ((x >> 2) & 0x33333333);
x = (x + (x >> 4)) & 0x0F0F0F0F;
x = x + (x >> 8);
x = x + (x >> 16);
return x & 0x0000003F;
}
//
// manhattan distance
function dist(a, b) {
if(BITS == 1) {
return popCnt(a ^ b);
}
var result = 0;
for(var i=0; i<DIMS; i++) {
var shr = i * BITS;
result += Math.abs(((a >> shr) & MASK) - ((b >> shr) & MASK));
}
return result;
}
//
// Vantage point tree
// max size of tree leaf nodes
VP_LEAF_SIZE = 32;
// need to choose a reasonable maximum distance
VP_DISTANCE = (BITS === 1) ? SIZE : 32;
function VPTree(data) {
this.radius = null;
this.center = null;
this.values = null;
this.inside = null;
this.outside = null;
//
var n = data.length;
var r = data[0];
// leaf node?
if(n <= VP_LEAF_SIZE || n <= 1) {
this.values = [].concat(data);
return this;
}
this.center = r;
// process data for counts at all possible distances
var buckets = Array(VP_DISTANCE + 1);
for(var i=0; i<=VP_DISTANCE; i++) {
buckets[i] = 0;
}
// distance counts
for(var i=0; i<n; i++) {
var v = data[i];
var d = dist(r, v);
if(d < VP_DISTANCE) {
buckets[d]++;
} else {
buckets[VP_DISTANCE]++;
}
}
// distance offsets
var sum = 0;
for(var i=0; i<=VP_DISTANCE; i++) {
buckets[i] = (sum += buckets[i]);
}
// pivot index
var median = n >> 1;
var pivot = 1;
for(var i=1; i<=VP_DISTANCE; i++) {
if(buckets[i] > median) {
pivot = (i > 1 && median - buckets[i - 1] <= buckets[i] - median) ? i - 1 : i;
break;
}
}
this.radius = pivot;
// parition data into inside and outside
var iCount = buckets[pivot] - buckets[0];
var oCount = (n - buckets[pivot]) - buckets[0];
var iData = Array(iCount);
var oData = Array(oCount);
iCount = oCount = 0;
for(var i=0; i<n; i++) {
var v = data[i];
if(v === r) { continue; };
if(dist(r, v) <= pivot) {
iData[iCount++] = v;
} else {
oData[oCount++] = v;
}
}
// recursively create the rest of the tree
if(iCount > 0) {
this.inside = new VPTree(iData);
}
if(oCount > 0) {
this.outside = new VPTree(oData);
}
return this;
}
VPTree.prototype.query = function(value, distance, result) {
if(result === undefined) {
return this.query(value, distance, []);
}
// leaf node, test all values
if(this.values !== null) {
for(var i=0; i<this.values.length; i++) {
var v = this.values[i];
if(dist(value, v) <= distance) {
result.push(v);
}
}
return result;
}
// recursively test the rest of the tree
var tmpDistance = dist(value, this.center);
// inside
if(tmpDistance <= distance + this.radius) {
if(tmpDistance <= distance) {
result.push(this.center);
}
if(this.inside !== null) {
this.inside.query(value, distance, result);
}
}
// outside
if(tmpDistance + distance > this.radius && this.outside !== null) {
this.outside.query(value, distance, result);
}
return result;
}
编辑这是JSFiddle显示的2d(x,y)(8位,8位)http://jsfiddle.net/fgq1rfLk/1/
答案 1 :(得分:0)
如果点有明确的坐标,如果d不是太大,这似乎是这里的情况,我认为(但我可能错了,需要测试)Kd树比VP-更有效率树,因为它可以从数据(坐标)中获得更多结构,而VP树只能看到&#34;点对点距离。
在ANN [1]中有一个有效的Kd树实现,具有所有需要的范围搜索功能(L2和Manathan度量)(但是,它明确地存储了所有坐标,你可能希望从你的&#34;压缩坐标&#34;表示。
另一种选择是我自己在Geogram中实现KdTree [2],它非常简单(虽然受到ANN的高度启发)并且很可能很容易适应使用你的压缩坐标表示(但它只有k个最近邻搜索使用L2指标)
Referencecs:
[1] https://www.cs.umd.edu/~mount/ANN/
[2] http://alice.loria.fr/software/geogram/doc/html/classGEO_1_1KdTree.html