【问题标题】:Calculating the number of graphs created and the number of vertices in each graph from a list of edges从边列表计算创建的图数和每个图中的顶点数
【发布时间】:2017-05-04 02:23:10
【问题描述】:

给定一个边列表,例如边 = [[1,2],[2,3],[3,1],[4,5]]

我需要找出创建了多少个图形,我的意思是这些边创建了多少组组件。然后获取组件组中的顶点数。

但是,我需要能够处理 10^5 条边,我目前无法完成大量边的任务。

我的算法目前正在获取边列表= [[1,2],[2,3],[3,1],[4,5]],如果它们有交集,则将每个列表合并为集合,这将输出一个新列表,该列表现在包含组组件,例如 , graphs = [[1,2,3],[4,5]]

有两个连通分量:[1,2,3] 是连通的,[4,5] 也是连通的。

我想知道是否有更好的方法来完成这项任务。

def mergeList(edges):
    sets = [set(x) for x in edges if x]
    m = 1
    while m:
        m = 0
        res = []
        while sets:
            common, r = sets[0], sets[1:]
            sets = []
            for x in r:
                if x.isdisjoint(common):
                    sets.append(x)
                else:
                    m = 1
                    common |= x
            res.append(common)
        sets = res
    return sets

我想尝试在字典或其他有效的方法中执行此操作,因为这太慢了。

【问题讨论】:

  • 你能发布你的代码吗?
  • 这是我目前使用的方法
  • 请注意,如果您将代码全部缩进 4 个空格,您的代码将正确格式化。您也可以突出显示它并按下编辑页面顶部的“{}”按钮。

标签: python algorithm graph


【解决方案1】:

Python 中的基本迭代图遍历还不错。

import collections


def connected_components(edges):
    # build the graph
    neighbors = collections.defaultdict(set)
    for u, v in edges:
        neighbors[u].add(v)
        neighbors[v].add(u)
    # traverse the graph
    sizes = []
    visited = set()
    for u in neighbors.keys():
        if u in visited:
            continue
        # visit the component that includes u
        size = 0
        agenda = {u}
        while agenda:
            v = agenda.pop()
            visited.add(v)
            size += 1
            agenda.update(neighbors[v] - visited)
        sizes.append(size)
    return sizes

【讨论】:

  • 让我试一试。
  • @Nightmare 现在有尺寸。
  • 这正是我想要的,干得好!而且效率很高
  • 谢谢我的帮助,现在更清楚了
【解决方案2】:

您需要编写自己的算法吗? networkx 已经有这方面的算法。

要获取每个组件的长度尝试

import networkx as nx

G = nx.Graph()
G.add_edges_from([[1,2],[2,3],[3,1],[4,5]])

components = []
for graph in nx.connected_components(G):
  components.append([graph, len(graph)])

components
# [[set([1, 2, 3]), 3], [set([4, 5]), 2]]

【讨论】:

    【解决方案3】:

    你可以使用Disjoint-set数据结构:

    edges = [[1,2],[2,3],[3,1],[4,5]]
    parents = {}
    size = {}
    
    def get_ancestor(parents, item):
        # Returns ancestor for a given item and compresses path
        # Recursion would be easier but might blow stack
        stack = []
        while True:
            parent = parents.setdefault(item, item)
            if parent == item:
                break
            stack.append(item)
            item = parent
    
        for item in stack:
            parents[item] = parent
    
        return parent
    
    
    for x, y in edges:
        x = get_ancestor(parents, x)
        y = get_ancestor(parents, y)
        size_x = size.setdefault(x, 1)
        size_y = size.setdefault(y, 1)
        if size_x < size_y:
            parents[x] = y
            size[y] += size_x
        else:
            parents[y] = x
            size[x] += size_y
    
    print(sum(1 for k, v in parents.items() if k == v)) # 2
    

    在上面的parents 是一个字典,其中顶点是键,祖先是值。如果给定顶点没有父顶点,则该值就是顶点本身。对于列表中的每条边,两个顶点的祖先都设置为相同。请注意,当查询当前祖先时,路径会被压缩,因此可以在 O(1) 时间内完成后续查询。这使得整个算法具有 O(n) 时间复杂度。

    更新

    如果需要组件而不仅仅是数量,则可以迭代生成的 dict 来生成它:

    from collections import defaultdict
    
    components = defaultdict(list)
    for k, v in parents.items():
        components[v].append(k)
    
    print(components)
    

    输出:

    defaultdict(<type 'list'>, {3: [1, 2, 3], 5: [4, 5]})
    

    【讨论】:

    • 是的,这很不错。
    • 现在,我想让它返回每个连接组件的大小。比如 [1,2,3] , 3 和 [4,5] , 2。这个设置可以吗?
    • @Nightmare 但实际上并不是线性时间。
    • @DavidEisenstat 是的,我猜不是
    • @niemmi 事实上,我做到了。这是一个粘贴箱,其中包含一些代码,可用于证明您的算法在具有 3*2^k - 1 条边的图上使用了至少 k*2^(k-1) 次操作,这意味着运行时间为 Omega(n登录 n): pastebin.com/DAyGgj11
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-02-25
    • 1970-01-01
    • 1970-01-01
    • 2017-01-23
    • 2020-07-19
    • 1970-01-01
    • 2021-02-20
    相关资源
    最近更新 更多