在python中查找邻居邻居的最有效方法

时间:2021-03-02 07:30:21

标签: python python-3.x numpy tree

让我们考虑一下,有两个数组 IJ 决定了邻居对:

I = np.array([0, 0, 1, 2, 2, 3])
J = np.array([1, 2, 0, 0, 3, 2])

这意味着元素 0 有两个邻居 12。元素 1 只有 0 作为邻居,依此类推。

创建所有邻居三元组 I'J'K' 的数组的最有效方法是什么,这样 ji 和 { 的邻居{1}} 是 k 的邻居,条件是 jij 是不同的元素 (k)?

i != j != k

当然,一种方法是遍历每个元素。有没有更高效的算法? (处理 10-5 亿个元素)

4 个答案:

答案 0 :(得分:5)

我会采用一种非常简单的方法并使用熊猫(IJ 是您的 numpy 数组):

import pandas as pd

df1 = pd.DataFrame({'I': I, 'J': J})
df2 = df1.rename(columns={'I': 'K', 'J': 'I'})

result = pd.merge(df2, df1, on='I').query('K != J')

优点是 pandas.merge 依赖于非常快速的底层数值实现。此外,您还可以使计算速度更快,例如通过使用索引进行合并。

为了减少这种方法需要的内存,在合并它们之前减小 df1df2 的大小可能非常有用(例如,通过将它们的列的 dtype 更改为适合您的需要)。

以下是如何优化计算速度和内存的示例:

from timeit import timeit
import numpy as np
import pandas as pd

I = np.random.randint(0, 10000, 1000000)
J = np.random.randint(0, 10000, 1000000)

df1_64 = pd.DataFrame({'I': I, 'J': J})
df1_32 = df1_64.astype('int32')
df2_64 = df1_64.rename(columns={'I': 'K', 'J': 'I'})
df2_32 = df1_32.rename(columns={'I': 'K', 'J': 'I'})

timeit(lambda: pd.merge(df2_64, df1_64, on='I').query('K != J'), number=1)
# 18.84
timeit(lambda: pd.merge(df2_32, df1_32, on='I').query('K != J'), number=1)
# 9.28

答案 1 :(得分:1)

没有特别神奇的算法来生成所有的三元组。您可以通过有序搜索避免重新获取节点的邻居,但仅此而已。

  • 创建一个空列表,N,要检查的节点。
  • 将一些起始节点 S 添加到 N
  • 虽然N不为空
    • 从列表中弹出一个节点;称之为A。
    • 制作一组它的邻居,A'。
    • 对于A的每个邻居B
      • 对于 A' 的每个元素 a
        • 生成三元组 (a, A, B)
      • 将 B 添加到要检查的节点列表中(如果尚未检查)。

这有帮助吗?上面的算法还有一些细节需要处理,比如避免重复生成,以及通过派系移动的细节。

答案 2 :(得分:1)

这是使用 networkx(用于图形计算的优化库)对您的问题的初步解决方案:

import numpy as np
import networkx as nx

I = np.array([0, 0, 1, 2, 2, 3])
J = np.array([1, 2, 0, 0, 3, 2])

I_, J_, K_ = [], [], [],
num_nodes = np.max(np.concatenate([I,J])) + 1
A = np.zeros((num_nodes, num_nodes))
A[I,J] = 1
print("Adjacency Matrix:")
print(A)
G = nx.from_numpy_matrix(A)

for i in range(num_nodes):
    first_neighbors = list(G.neighbors(i))

    for j in first_neighbors:
        second_neighbor = list(G.neighbors(j))
        second_neighbor_no_circle = list(filter(lambda node: node != i, second_neighbor))
        num_second_neighbors = len(second_neighbor_no_circle)

        if num_second_neighbors > 0:
            I_.extend(num_second_neighbors * [i])
            J_.extend(num_second_neighbors * [j])
            K_.extend(second_neighbor_no_circle)
            
I_, J_, K_ = np.array(I_), np.array(J_), np.array(K_)
print("result:")
print(I_)
print(J_)
print(K_)

####### Output ####### 
Adjacency Matrix:
[[0. 1. 1. 0.]
 [1. 0. 0. 0.]
 [1. 0. 0. 1.]
 [0. 0. 1. 0.]]
result:
[0 1 2 3]
[2 0 0 2]
[3 2 1 0]

我在上面没有打印语句的代码中使用了 %%timeit 来检查运行时间: 49 µs ± 113 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

复杂度分析: 在Depth First Search算法中查找所有邻居的所有邻居本质上需要2个步骤。根据图的拓扑结构,这可能需要 O(|V| + |E|),其中 |E| 是图中的边数,| V|是顶点数。

据我所知,一般图形上没有更好的算法。 但是,如果您确实知道图的一些特殊属性,则运行时间可能会受到更严格的限制,或者可能会根据这些知识改变当前算法。

例如,如果您知道所有顶点至多有 d 条边,并且图有一个连通分量,则此实现的边界变为 O(2d)如果 d << |E| 会更好。

如果您有任何问题,请告诉我。

答案 3 :(得分:1)

您要查找的是图中的 all paths of length 3。您可以使用以下递归算法简单地实现这一点:

import networkx as nx

def findPaths(G,u,n):
    """Returns a list of all paths of length `n` starting at vertex `u`."""
    if n==1:
        return [[u]]
    paths = [[u]+path for neighbor in G.neighbors(u) for path in findPaths(G,neighbor,n-1) if u not in path]
    return paths

# Generating graph
vertices = np.unique(I)
edges = list(zip(I,J))
G = nx.Graph()
G.add_edges_from(edges)

# Grabbing all 3-paths
paths = [path for v in vertices for path in findPaths(G,v,3)]
paths
>>> [[0, 2, 3], [1, 0, 2], [2, 0, 1], [3, 2, 0]]