我有两个数据文件,每个文件包含大量的三维点(文件A存储大约50,000个点,文件B存储大约500,000个点)。我的目标是为文件A中的每个点(a)找到文件B中与(a)的距离最小的点(b)。我将这些点存储在两个列表中:
列出节点:
(ID X Y Z)
[ ['478277', -107.0, 190.5674, 128.1634],
['478279', -107.0, 190.5674, 134.0172],
['478282', -107.0, 190.5674, 131.0903],
['478283', -107.0, 191.9798, 124.6807],
... ]
列出B 数据:
(X Y Z Data)
[ [-28.102, 173.657, 229.744, 14.318],
[-28.265, 175.549, 227.824, 13.648],
[-27.695, 175.925, 227.133, 13.142],
...]
我的第一种方法是简单地使用嵌套循环遍历第一个和第二个列表,并计算每个点之间的距离,如下所示:
outfile = open(job[0] + '/' + output, 'wb');
dist_min = float(job[5]);
dist_max = float(job[6]);
dists = [];
for node in nodes:
shortest_distance = 1000.0;
shortest_data = 0.0;
for entry in data:
dist = math.sqrt((node[1] - entry[0])**2 + (node[2] - entry[1])**2 + (node[3] - entry[2])**2);
if (dist_min <= dist <= dist_max) and (dist < shortest_distance):
shortest_distance = dist;
shortest_data = entry[3];
outfile.write(node[0] + ', ' + str('%10.5f' % shortest_data + '\n'));
outfile.close();
我认识到Python必须运行的循环量太大(~25,000,000,000),所以我必须紧固我的代码。我试图首先使用列表推导来计算所有距离,但代码仍然太慢:
p_x = [row[1] for row in nodes];
p_y = [row[2] for row in nodes];
p_z = [row[3] for row in nodes];
q_x = [row[0] for row in data];
q_y = [row[1] for row in data];
q_z = [row[2] for row in data];
dx = [[(px - qx) for px in p_x] for qx in q_x];
dy = [[(py - qy) for py in p_y] for qy in q_y];
dz = [[(pz - qz) for pz in p_z] for qz in q_z];
dx = [[dxxx * dxxx for dxxx in dxx] for dxx in dx];
dy = [[dyyy * dyyy for dyyy in dyy] for dyy in dy];
dz = [[dzzz * dzzz for dzzz in dzz] for dzz in dz];
D = [[(dx[i][j] + dy[i][j] + dz[i][j]) for j in range(len(dx[0]))] for i in range(len(dx))];
D = [[(DDD**(0.5)) for DDD in DD] for DD in D];
说实话,在这一点上,我不知道这两种方法中的哪一种更好,无论如何,两种可能性中的任何一种似乎都不可行。我甚至不确定是否可以编写一个在可接受的时间内计算所有距离的代码。在没有计算所有距离的情况下,还有其他方法可以解决我的问题吗?
编辑:我忘了提到我在Python 2.5.1上运行,不允许安装或添加任何新库...
答案 0 :(得分:0)
我花了一些心思,但最后我想我找到了一个解决方案。 您的问题不在您编写的代码中,而是在它实现的算法中。 有一种称为Dijkstra算法的算法,以下是它的要点:https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm 现在你需要做的是以巧妙的方式使用这个算法:
创建一个节点S(代表源)。 现在将S的边缘链接到B组中的所有节点。 完成后,您应该将B中每个点b的边连接到A中的每个点a。 您应该将链接的成本从源设置为0,将另一个链接的成本设置为2点之间的距离(仅限3D)。 现在,如果我们将使用Dijkstra算法,我们将获得的输出将是从S行进到图中每个点的成本(我们只关注到A组中点的距离)。 因此,由于B中的每个点b的成本为0,并且S仅连接到B中的点,因此到A中的任何点a的道路必须包括B中的节点(实际上恰好是一个节点,因为点之间的最短距离是单个线)。
我不确定这是否会固定您的代码,但据我所知,在不计算所有距离的情况下解决此问题的方法不存在,此算法是人们可能希望的最佳时间复杂度。
答案 1 :(得分:0)
以防万一有人对解决方案感兴趣:
我找到了一种通过不计算所有距离来加速整个过程的方法:
我创建了一个3D列表,表示给定3D空间中的网格,以给定的步长(例如(最大 - 最小)/ 1,000)划分为X,Y和Z.然后我迭代每个3D点将其放入我的网格中。之后,我再次遍历集合A的点,查看同一个立方体中是否有来自B的点,如果不是,我会增加搜索半径,因此该过程在相邻的26个立方体中查找点。半径增加,直到找到至少一个点。结果列表相对较小,可以在短时间内订购,并找到最近的点。
处理时间缩短到几分钟,工作正常。
p_x = [row[1] for row in nodes];
p_y = [row[2] for row in nodes];
p_z = [row[3] for row in nodes];
q_x = [row[0] for row in data];
q_y = [row[1] for row in data];
q_z = [row[2] for row in data];
min_x = min(p_x + q_x);
min_y = min(p_y + q_y);
min_z = min(p_z + q_z);
max_x = max(p_x + q_x);
max_y = max(p_y + q_y);
max_z = max(p_z + q_z);
max_n = max(max_x, max_y, max_z);
min_n = min(min_x, min_y, max_z);
gridcount = 1000;
step = (max_n - min_n) / gridcount;
ruler_x = [min_x + (i * step) for i in range(gridcount + 1)];
ruler_y = [min_y + (i * step) for i in range(gridcount + 1)];
ruler_z = [min_z + (i * step) for i in range(gridcount + 1)];
grid = [[[0 for i in range(gridcount)] for j in range(gridcount)] for k in range(gridcount)];
for node in nodes:
loc_x = self.abatemp_get_cell(node[1], ruler_x);
loc_y = self.abatemp_get_cell(node[2], ruler_y);
loc_z = self.abatemp_get_cell(node[3], ruler_z);
if grid[loc_x][loc_y][loc_z] is 0:
grid[loc_x][loc_y][loc_z] = [[node[1], node[2], node[3], node[0]]];
else:
grid[loc_x][loc_y][loc_z].append([node[1], node[2], node[3], node[0]]);
for entry in data:
loc_x = self.abatemp_get_cell(entry[0], ruler_x);
loc_y = self.abatemp_get_cell(entry[1], ruler_y);
loc_z = self.abatemp_get_cell(entry[2], ruler_z);
if grid[loc_x][loc_y][loc_z] is 0:
grid[loc_x][loc_y][loc_z] = [[entry[0], entry[1], entry[2], entry[3]]];
else:
grid[loc_x][loc_y][loc_z].append([entry[0], entry[1], entry[2], entry[3]]);
out = [];
outfile = open(job[0] + '/' + output, 'wb');
for node in nodes:
neighbours = [];
radius = -1;
loc_nx = self.abatemp_get_cell(node[1], ruler_x);
loc_ny = self.abatemp_get_cell(node[2], ruler_y);
loc_nz = self.abatemp_get_cell(node[3], ruler_z);
reloop = True;
while reloop:
if neighbours:
reloop = False;
radius += 1;
start_x = 0 if ((loc_nx - radius) < 0) else (loc_nx - radius);
start_y = 0 if ((loc_ny - radius) < 0) else (loc_ny - radius);
start_z = 0 if ((loc_nz - radius) < 0) else (loc_nz - radius);
end_x = (len(ruler_x) - 1) if ((loc_nx + radius + 1) > (len(ruler_x) - 1)) else (loc_nx + radius + 1);
end_y = (len(ruler_y) - 1) if ((loc_ny + radius + 1) > (len(ruler_y) - 1)) else (loc_ny + radius + 1);
end_z = (len(ruler_z) - 1) if ((loc_nz + radius + 1) > (len(ruler_z) - 1)) else (loc_nz + radius + 1);
for i in range(start_x, end_x):
for j in range(start_y, end_y):
for k in range(start_z, end_z):
if not grid[i][j][k] is 0:
for grid_entry in grid[i][j][k]:
if not isinstance(grid_entry[3], basestring):
neighbours.append(grid_entry);
dists = [];
for n in neighbours:
d = math.sqrt((node[1] - n[0])**2 + (node[2] - n[1])**2 + (node[3] - n[2])**2);
dists.append([d, n[3]]);
dists = sorted(dists);
outfile.write(node[0] + ', ' + str(dists[0][-1]) + '\n');
outfile.close();
获取点位置的函数:
def abatemp_get_cell(self, n, ruler):
for i in range(len(ruler)):
if i >= len(ruler):
return False;
if ruler[i] <= n <= ruler[i + 1]:
return i;
gridcount 变量为一个人提供了一个固定过程的机会,一个小的 gridcount 将点分类到网格的过程非常快,但是搜索循环中的邻居变大,这部分过程需要更多时间。使用大的 gridcount 开头需要更多时间,但循环运行得更快。
我现在唯一的问题是这样一个事实,即有一些过程找到了邻居,但还有其他一些点尚未找到,但更接近这一点(见picture)。到目前为止,我已经通过在已经有邻居的情况下再次增加搜索半径来解决这个问题。然而,我仍然有更近但不在邻居名单中的分数,尽管它的数量非常少(约为100,000的92分)。我可以通过在找到邻居后两次增加半径来解决这个问题,但这个解决方案似乎不是很聪明。也许你们有个主意......
这是该流程的第一份工作草案,我认为可以进一步改进它,只是为了让您了解它是如何工作的......
答案 2 :(得分:0)
看看这个通用的3D数据结构:
https://github.com/m4nh/skimap_ros
它具有非常快速的RadiusSearch功能,随时可以使用。这个解决方案(类似于八叉树但更快)避免了你首先创建常规网格(你不必沿每个轴修复MAX / MIN大小)并节省大量内存