Networkx:在二分图中查找仅由一组中的节点组成的所有最小切割

时间:2017-11-06 22:16:23

标签: python networkx

在networkx python包中,有没有办法找到最小尺寸的所有节点剪切,只包含二分图中一组的节点?例如,如果二分图的两边是A和B,我怎样才能找到完全来自集合B的节点组成的所有最小节点切割?我有以下代码,但速度非常慢:

def get_one_sided_cuts(G, A, B):
    #get all cuts that consist of nodes exclusively from B which disconnect
    #nodes from A
    one_sided_cuts = []
    seen = []

    l = list(combinations(A, 2))

    for x in l:
        s = x[0]
        t = x[1]

        cut = connectivity.minimum_st_node_cut(G, s, t)
        if set(cut).issubset(B) and (cut not in seen):
            one_sided_cuts.append(cut)
        seen.append(cut)

    #find minimum cut size
    cur_min = float("inf")
    for i in one_sided_cuts:
        if len(i) < cur_min:
            cur_min = len(i)

    one_sided_cuts = [x for x in one_sided_cuts if len(x) == cur_min]

    return one_sided_cuts

请注意,这实际上只会检查是否存在最小剪切,如果删除,则仅断开A中的两个节点。如果您的解决方案执行此操作(而不是找到将任何两个节点分开的剪切),那也没关系。关于如何更有效地做到这一点的任何想法?

1 个答案:

答案 0 :(得分:4)

如评论中所述,对“最小尺寸的所有节点切割仅包含来自二分图中的一个节点的节点”有几种解释。它要么意味着

  1. 将限制剪切限制在一组二分图中的所有节点最小尺寸切割,或
  2. 所有节点在不受约束的意义上(包括来自A或B的节点)切割,恰好完全位于B中。
  3. 从您感兴趣的代码示例2.根据文档,有一种方法可以加快此计算,并从配置文件结果中有所帮助。每个图形都有辅助结构,用于确定最小节点切割。根据{{​​3}}中的算法9,每个节点由2个节点替换,添加额外的有向边等。 我们可以重用这些结构,而不是在紧密循环中重构它们:

    案例2的改进:

    from networkx.algorithms.connectivity import (
        build_auxiliary_node_connectivity)
    from networkx.algorithms.flow import build_residual_network
    
    from networkx.algorithms.flow import edmonds_karp
    
    def getone_sided_cuts_Case2(G, A, B):
        # build auxiliary networks
        H = build_auxiliary_node_connectivity(G)
        R = build_residual_network(H, 'capacity')
    
    
        # get all cutes that consist of nodes exclusively from B which disconnet
        # nodes from A
        one_sided_cuts = []
        seen           = []
    
        l = list(combinations(A,2))
    
        for x in l:
            s = x[0]
            t = x[1]
    
        cut = minimum_st_node_cut(G, s, t, auxiliary=H, residual=R)
        if set(cut).issubset(B):
            if cut not in seen:
                one_sided_cuts.append(cut)
        seen.append(cut)
    
        # Find minimum cut size
        cur_min = float('inf')
        for i in one_sided_cuts:
            if len(i) < cur_min:
                curr_min = len(i)
    
        one_sided_cuts = [x for x in one_sided_cuts if len(x) == cur_min]
    
        return one_sided_cuts
    

    为了进行性能分析,您可以使用以下或Networkx中的一个内置二分图生成器:

    def create_bipartite_graph(size_m, size_n, num_edges):
        G = nx.Graph()
    
        edge_list_0 = list(range(size_m))
        edge_list_1 = list(range(size_m,size_m+size_n))
        all_edges = []
    
        G.add_nodes_from(edge_list_0, bipartite=0)
        G.add_nodes_from(edge_list_1, bipartite=1)
    
        all_edges = list(product(edge_list_0, edge_list_1))
        num_all_edges = len(all_edges)
    
        edges = [all_edges[i] for i in random.sample(range(num_all_edges), num_edges)]
        G.add_edges_from(edges)
    
        return G, edge_list_0, edge_list_1
    

    使用%timeit,第二个版本的运行速度提高约5-10%。

    对于案例1,逻辑更复杂一些。我们需要考虑仅在B内部节点的最小切割。这需要以下列方式更改为minimum_st_node_cut。然后将解决方案中的minimum_st_node_cut的所有出现替换为rest_minimum_st_node_cut或上面给出的案例2解决方案,注意新函数还需要指定集合AB ,必然:

    def rest_build_auxiliary_node_connectivity(G,A,B):
        directed = G.is_directed()
    
        H = nx.DiGraph()
    
        for node in A:
            H.add_node('%sA' % node, id=node)
            H.add_node('%sB' % node, id=node)
            H.add_edge('%sA' % node, '%sB' % node, capacity=1)
    
        for node in B:
            H.add_node('%sA' % node, id=node)
            H.add_node('%sB' % node, id=node)
            H.add_edge('%sA' % node, '%sB' % node, capacity=1)        
    
        edges = []
        for (source, target) in G.edges():
            edges.append(('%sB' % source, '%sA' % target))
            if not directed:
                edges.append(('%sB' % target, '%sA' % source))
        H.add_edges_from(edges, capacity=1)
    
        return H
    
    def rest_minimum_st_node_cut(G, A, B, s, t, auxiliary=None, residual=None, flow_func=edmonds_karp):
    
        if auxiliary is None:
            H = rest_build_auxiliary_node_connectivity(G, A, B)
        else:
            H = auxiliary
    
        if G.has_edge(s,t) or G.has_edge(t,s):
            return []
        kwargs = dict(flow_func=flow_func, residual=residual, auxiliary=H)
    
        for node in [x for x in A if x not in [s,t]]:
            edge = ('%sA' % node, '%sB' % node)
            num_in_edges = len(H.in_edges(edge[0]))
            H[edge[0]][edge[1]]['capacity'] = num_in_edges
    
        edge_cut = minimum_st_edge_cut(H, '%sB' % s, '%sA' % t,**kwargs)
    
        node_cut = set([n for n in [H.nodes[node]['id'] for edge in edge_cut for node in edge] if n not in A])
    
        return node_cut - set([s,t])
    

    然后我们有:

    In [1]: G = nx.Graph()
            # A = [0,1,2,3], B = [4,5,6,7]
    In [2]: G.add_edges_from([(0,4),(0,5),(1,6),(1,7),(4,1),(5,1),(6,3),(7,3)])
    In [3]: minimum_st_node_cut(G, 0, 3)
               {1}
    In [4]: rest_minimum_st_node_cut(G,A,B,0,3)
               {6, 7}
    

    最后请注意,如果两个节点相邻,minimum_st_edge_cut()函数将返回[]。有时,约定是在这种情况下返回一组n-1个节点,除源或接收器之外的所有节点。无论如何,使用空列表约定,并且因为案例2的原始解决方案在A中的节点对上循环,您可能会获得[]作为大多数配置的返回值,除非{{1}中没有节点1}}是相邻的,比如说。

    修改

    OP遇到了二分图的问题,其中集合A,B包含整数和str类型的混合。在我看来,A将这些str节点转换为导致冲突的整数。我重写了上面的内容,我认为这样可以解决它。我在build_auxiliary_node_connectivity文档中没有看到任何关于此内容的内容,因此请使用所有整数节点或使用上面的networkx内容。