微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

在 Python 中将无向循环图 (UCG) 转换为有向无环图 (DAG) 的最快方法?

如何解决在 Python 中将无向循环图 (UCG) 转换为有向无环图 (DAG) 的最快方法?

假设我有一个无向循环图(UCG)。所有边的权重都是1。因此,这个UCG可以用一个邻接矩阵A来表示:

import numpy as np

A = np.array([[0,1,1],[1,0],[0,0]])

要可视化 UCG,我们可以简单地将其转换为 networkx.Graph 对象

import networkx as nx

ucg = nx.Graph()
rows,cols = np.where(A == 1)
edges = zip(rows.tolist(),cols.tolist())
ucg.add_edges_from(edges)

UCG 看起来像这样:

enter image description here

我用不同的颜色为节点着色,以显示“最小距离”。橙色节点 {8,9,10} 是起始节点,绿色节点 {0,2,3} 是距离起始节点最小距离为 1 的节点,蓝色节点 {4,5,6,7} 距离起始节点的最小距离为2. 现在我想把它转换成一个有向无环图(DAG),箭头指向从起始节点到距离 1 节点再到距离 2 节点等等。丢弃具有相同“最小距离”的节点之间的边。

预期输出是代表 DAG 的字典:

d = {8: {1,3},9: {1,2},10: {0,0: {4,7},1: {5,2: {4,6},3: {4,7}}

同样,为了可视化 DAG,我们可以通过

将其转换为 networkx.DiGraph 对象
dag = nx.DiGraph()
dag.add_edges_from([(k,v) for k,vs in d.items() in for v in vs])

预期输出 DAG 如下所示:

enter image description here

我想编写一个高效且通用的代码,将具有给定起始节点的给定 UCG 转换为相应的 DAG。

我尝试过的

显然,需要递归。我的想法是使用 BFS 方法为每个起始节点找到 1-distance 节点,然后是它们的 1-distance 节点,递归继续下去。所有访问过的节点都存储在一个集合 prev_starts 中以避免倒退。下面是我的代码

from collections import defaultdict

def ucg2dag(A,starts):
    """Takes the adjacency matrix of a UCG and the indices of the
    starting nodes,returns the dictionary of a DAG."""

    def recur(starts):
        starts = list(set(starts))
        idxs,nbrs = np.where(A[starts] == 1)
        prev_starts.update(starts)

        # Filter out the neighbors that are prevIoUs starts so the
        # arrows do not point backwards
        try:
            idxs,nbrs = zip(*((idx,nbr) for idx,nbr in zip(idxs,nbrs)
                                if nbr not in prev_starts))
        # Terminate if every neighbor is a prevIoUs start.
        except:
            return d

        for idx,nbrs):
            d[starts[idx]].add(nbr)

        return recur(starts=nbrs)

    prev_starts = set()
    d = defaultdict(set)
    return recur(starts)

测试我的代码

d = ucg2dag(A,starts={8,10})
print(d)

编辑:由于@trincot 的评论,在 return 之前添加 recur 后,我能够获得正确的输出

defaultdict(<class 'set'>,{8: {1,7}})
%timeit 37.6 µs ± 591 ns per loop (mean ± std. dev. of 7 runs,10000 loops each)

实际上我有一个更大的图表。我想知道有没有更高效的算法?

解决方法

您已对代码应用了一些修复(部分基于注释),因此现在您拥有了可运行的代码。

剩下的几句话是:

  • BFS 通常不是递归算法(与 DFS 相比):您拥有的递归是尾递归的一种情况。在这种情况下,它可以写成一个循环,这样就可以避免使用堆栈。

  • 很遗憾你必须在邻接矩阵中查找边。最好先把邻接矩阵转换成邻接表,除非图真的很稠密。

  • 输出也可以是一个邻接列表,每个节点都有一个条目,这样它就可以是一个列表而不是字典

  • 使用 zip 的结构重复转换可能不是最有效的(虽然我没有进行基准测试)

如果不使用 numpy,它可能看起来像这样:

def ucg2dag(adj_matrix,starts):
    adj_list = [
        [target for target,is_connected in enumerate(row) if is_connected]
            for row in adj_matrix
    ]

    frontier = starts

    dag = [[] for _ in range(len(adj_list))]

    while frontier:
        for source in frontier:
            dag[source].extend(target for target in adj_list[source] if not target in starts)
        frontier = set(target 
            for source in frontier for target in adj_list[source] if not target in starts
        )
        starts.update(frontier)

    return dag

示例运行:

adj_matrix = [[0,1,1],[1,0],[0,0]]

dag = ucg2dag(adj_matrix,{8,9,10})
print(dag)  

示例运行的输出:

[[4,6,7],[5,[4,5,6],[],3],2],2,3]]

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。