【问题标题】:Most efficient way to find neighbors of neighbors in python在python中查找邻居邻居的最有效方法
【发布时间】:2021-03-02 07:30:21
【问题描述】:

让我们考虑一下,有两个数组 I 和 J 确定邻居对:

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

这意味着元素0 有两个邻居1 和2。元素1 只有0 作为邻居等等。

创建所有相邻三元组I'、J'、K' 的数组的最有效方法是什么,这样j 是i 的邻居,而k 是j 的邻居,因为条件i、j和k是不同的元素(i != j != k)?

Ip = np.array([0, 0, 2, 3])
Jp = np.array([2, 2, 0, 2])
Kp = np.array([0, 3, 1, 0])

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

【问题讨论】:

  • 你为什么使用数组?这是一个图形问题:使用networkx 或其他图形包。让包担心你的效率。
  • 请明确说明您的用例。你需要生成所有可能的三元组吗?这是按需服务吗?值得记忆吗?
  • @Prune 谢谢,看看networkx包。但不确定所有可能的三元组是什么意思。有没有办法部分得到它?
  • 您说您要创建三元组,其中中间节点连接到每个端节点。但是,您没有具体说明如何决定生成哪些三元组。了解程序规范对于选择设计和实现很重要。
  • 一百万行的邻接矩阵...

标签: python python-3.x numpy tree


【解决方案1】:

我会采用一种非常简单的方法并使用 pandas(I 和 J 是您的 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 依赖于非常快速的底层数值实现。此外,您还可以使计算速度更快,例如通过使用索引进行合并。

为了减少这种方法所需的内存,在合并它们之前减小 df1 和 df2 的大小可能非常有用(例如,通过将它们的列的 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

【讨论】:

  • 这正是我想要的。非常聪明的解决方案!感谢分享。
【解决方案2】:

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

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

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

【讨论】:

  • 谢谢,但是我试图避免在这里列出和循环。基于 itertools 的东西可能或 bincount 之类的技巧。
  • Itertools 只是对您隐藏循环,并用生成器替换显式循环。任何特定的解决方案都将取决于您的数据结构。如果您进行了预期的编码尝试并陷入困境,那么您将有一个很好的问题要发布。这个已经偏离了 Stack Overflow 的章程。
  • 也就是说,你的三元组应该很简单,一个嵌套列表推导式,检查你没有制作 (A, B, A) 三元组。
  • 我编辑了问题以澄清 ABA。重点是效率,将循环从 python 移动到数值库或使用数组而不是列表通常是加速的最佳方法。
【解决方案3】:

您要查找的是图表中的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]]

【讨论】:

    【解决方案4】:

    这是使用 networkx 解决您的问题的初步解决方案,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 算法中,查找所有邻居的所有邻居基本上需要两个步骤。根据图的拓扑,这可能需要最多 O(|V| + |E|),其中 |E| 是图中的边数,| V|是顶点数。

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

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

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

    【讨论】:

    • 谢谢,但这似乎不适用于大量节点。矩阵大小会随着 N^2 的增加而增加。
    • 太糟糕了...看看this thread,他们讨论了一些处理大图的解决方案。
    猜你喜欢
    • 1970-01-01
    • 2020-01-29
    • 2013-11-11
    • 2020-11-05
    • 1970-01-01
    • 2020-03-05
    • 1970-01-01
    • 2022-08-23
    • 1970-01-01
    相关资源
    最近更新 更多