考虑传递对等,有效地在无向图中找到连通的分量

时间:2019-01-28 12:30:54

标签: python graph computer-science networkx graph-theory

我有一组节点和一个函数foo(u,v),可以确定两个节点是否相等。 “相等”是指及物对等:  If 1==22==3然后1==3,还有:If 1==21!=4然后2!=4

给定一组节点后,我可以通过将节点的每种可能的组合传递给foo(u,v)(返回预定结果)来找到图中的所有已连接组件仅用于演示目的-这不是真正的功能!)功能并构建所需的边缘。像这样:

import networkx as nx
import itertools
from matplotlib import pyplot as plt


def foo(u, v):
    # this function is simplified, in reality it will do a complex 
    # calculation to determine whether nodes are equal.
    EQUAL_EDGES = {(1, 2), (2, 3), (1, 3), (4, 5)}
    return (u, v) in EQUAL_EDGES


def main():
    g = nx.Graph()
    g.add_nodes_from(range(1, 5 + 1))
    for u, v in itertools.combinations(g.nodes, 2):
        are_equal = foo(u, v)
        print '{u}{sign}{v}'.format(u=u, v=v, sign='==' if are_equal else '!=')
        if are_equal:
            g.add_edge(u, v)

    conn_comps = nx.connected_components(g)
    nx.draw(g, with_labels=True)
    plt.show()
    return conn_comps


if __name__ == '__main__':
    main()

这种方法的问题在于,我要避免很多冗余检查:

1==2  # ok
1==3  # ok
1!=4  # ok
1!=5  # ok
2==3  # redundant check, if 1==2 and 1==3 then 2==3 
2!=4  # redundant check, if 1!=4 and 1==2 then 2!=4 
2!=5  # redundant check, if 1!=5 and 1==2 then 2!=5
3!=4  # redundant check, if 1!=4 and 1==3 then 3!=4
3!=5  # redundant check, if 1!=5 and 1==3 then 3!=5
4==5  # ok

我想避免以O(n ^ 2)的时间复杂度运行。 通过自定义foo(u,v)函数有效地找到所有连接的组件的正确方法是什么(或者可能是任何python库中的现有函数)?

3 个答案:

答案 0 :(得分:1)

尚不清楚您真正要做什么,但是这是一个仅检查每个等效组中一个元素的解决方案:

nodes2place = range(1, 6)
cclist = []

for u in nodes2place:
    node_was_placed=False
    for icc in range(len(cclist)):
        if foo(u, cclist[icc][0]):
            cclist[icc].append(u)
            node_was_placed=True
            break

    # node doesn't fit into existing cc so make a new one
    if not node_was_placed:
        cclist.append([u])

答案 1 :(得分:1)

您可以跟踪两个字典中哪些边在传递上相等或不相等。对于每种边缘组合,您都可以在O(1)时间内进行一些简单的检查,以查看计算是否多余。否则,您将根据第一原理进行计算,然后根据边的相等或不相等,使用必要的信息更新上述字典。您仍然必须进行C(n,2)个相等性检查,因为这是要迭代的组合数量,但是对于其中的许多组合,可以立即做出决定。

equal_edges字典更易于解释,因此让我们开始吧。 1-2个边缘对相等,但是由于1或2都不作为键(字典现在为空),我们创建了{1, 2}集并将其附加到equal_edges[1]和{{1 }}。然后,我们遇到等边线对1-3。由于equal_edges[2]现在存在,因此我们在其传递相等的节点上加3。但是,由于此设置在边1和边2之间共享,因此在两个地方都进行了更新。现在,我们还必须将同一集合附加到equal_edges[1]。所有这三个边都引用内存中的同一集合,即equal_edges[3],因此我们不复制任何数据。现在,当要检查等边线对2-3时,{1, 2, 3}3 in equal_edges[2]允许我们绕过任何繁重的计算。

对于2 in equal_edges[3],其逻辑有些相似,但是对于传递不等式的边,我们还必须参考unequal_edges字典。例如,边缘对1-4是不相等的。但是由于1在传递上等于2和3,所以我们必须有equal_edges。设置unequal_edges[4] = equal_edges[1]unequal_edges[1] = {4}等将是多余的。这是因为可以从unequal_edges[2] = {4}获得此信息。这只是意味着,对于传递不等式的a-b对,我们需要仔细检查,即unequal_edges[4]

a in unequal_edges[b] or b in unequal_edges[a]

此处的打印语句仅用于演示目的。如果您运行

from itertools import combinations

equal_edges = {}
unequal_edges = {}

def update_equal_edges(a, b):
    def update_one(a, b):
        equal_edges[a].add(b)
        equal_edges[b] = equal_edges[a]
    exists_a = a in equal_edges
    exists_b = b in equal_edges
    if not (exists_a or exists_b):
        s = set((a, b))
        equal_edges[a] = s
        equal_edges[b] = s
    elif exists_a and not exists_b:
        update_one(a, b)
    elif exists_b and not exists_a:
        update_one(b, a)

def update_unequal_edges(a, b):
    exists_a = a in equal_edges
    exists_b = b in equal_edges
    if not (exists_a or exists_b):
        s = set((a, b))
        unequal_edges[a] = s
        unequal_edges[b] = s
    elif exists_a and not exists_b:
        unequal_edges[b] = equal_edges[a]
    elif exists_b and not exists_a:
        unequal_edges[a] = equal_edges[b]

def are_equal_edges(a, b):
    if a in equal_edges.get(b, []):
        print('{}=={} # redundant'.format(a, b))
        return True
    if (a in unequal_edges.get(b, [])) or (b in unequal_edges.get(a, [])):
        print('{}!={} # redundant'.format(a, b))
        return False
    # hardcoded equal edges which are the result
    # of some complex computations
    are_equal = (a, b) in {(1, 2), (1, 3), (4, 5)}
    if are_equal:
        update_equal_edges(a, b)
    else:
        update_unequal_edges(a, b)
    print('{}{}{} # ok'.format(a, '==' if are_equal else '!=', b))
    return are_equal

您得到以下结果

for a, b in combinations(range(1, 6), 2):
    are_equal_edges(a, b)

答案 2 :(得分:0)

您可以使用0表示相等性,使用math.inf表示不平等性作为边缘权重。然后,对于每个节点对u, v,您可以计算从uv的路径长度,并根据结果确定是否需要调用(重)节点检查:

g = nx.Graph()
g.add_nodes_from(range(1, 6))
for u, v in it.combinations(g.nodes, 2):
    try:
        path = nx.shortest_path(g, u, v)
    except nx.NetworkXNoPath:
        new_weight = 0 if func(u, v) else math.inf
    else:
        weights = list(x['weight'] for x in it.starmap(g.get_edge_data, zip(path[:-1], path[1:])))
        if min(weights) == math.inf:
            new_weight = 0 if func(u, v) else math.inf
        elif max(weights) == math.inf:
            new_weight = math.inf
        else:
            new_weight = 0
    g.add_edge(u, v, weight=new_weight)

如果您不喜欢图形中的这些无限边,则可以:

  • 在构建图形后将其删除,
  • 或保持与无穷大图平行的最终图,最后只保留最后一个。