【问题标题】:All possible maximum matchings of a bipartite graph二分图的所有可能的最大匹配
【发布时间】:2016-05-10 16:43:43
【问题描述】:

我正在使用networkx 来查找二分图的maximum cardinality matching

匹配的边对于特定图来说不是唯一的。

有没有办法让我找到所有最大匹配项?

对于下面的例子,下面的所有边都可以是最大匹配:

{1: 2, 2: 1}{1: 3, 3: 1}{1: 4, 4: 1}

import networkx as nx
import matplotlib.pyplot as plt

G = nx.MultiDiGraph()
edges = [(1,3), (1,4), (1,2)]

nx.is_bipartite(G)
True

nx.draw(G, with_labels=True)
plt.show()

不幸的是,

nx.bipartite.maximum_matching(G)

只返回

{1: 2, 2: 1}

有没有办法我也可以获得其他组合?

【问题讨论】:

  • @chthonicdaemon 这里描述了一种适用于二分图的算法(我不知道它是否在 networkx 中实现):Tassa, Tamir (2012), "Finding all maximum-matchable edges in a二分图”,理论计算机科学 423:50-58,doi:10.1016/j.tcs.2011.12.071。如果你或其他人写了它,我建议将它添加到 networkx。
  • 我不确定我是否理解符号 {1: 2, 2: 1}。它看起来像一组两条边,但由于顶点不是不相交的,所以它不是匹配的。如果这就是您的意思,则无需两次列出无向边。也就是说,您找到解决此问题的方法了吗?

标签: python graph-theory networkx


【解决方案1】:

Takeaki Uno 的论文“Algorithms for Enumerating All Perfect, Maximum and Maximal Matchings in Bipartite Graphs”对此有一个算法。 http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.107.8179&rep=rep1&type=pdf

定理 2 说 “二部图中的最大匹配可以在 O(mn^1/2+) 中枚举 nNm) 时间和 O(m) 空间,其中 Nm 是 G 中的最大匹配数。"

【讨论】:

    【解决方案2】:

    我阅读了 Uno 的作品并试图提出一个实施方案。下面是我非常冗长的代码和一个工作示例。在这种特殊情况下,有 4 个“可行的”顶点(根据 Uno 的术语),因此使用已经覆盖的顶点切换每个顶点,您将拥有2^4 = 16 不同的可能最大匹配。

    我不得不承认,我对图论很陌生,而且我并没有完全遵循 Uno 的流程,存在细微的差异,而且我大多没有尝试进行任何优化。我确实很难理解这篇论文,因为我认为解释不是很完美,而且这些数字可能有错误。所以请谨慎使用,如果你能帮助优化它,那就太好了!

    import networkx as nx
    from networkx import bipartite
    
    def plotGraph(graph):
        import matplotlib.pyplot as plt
        fig=plt.figure()
        ax=fig.add_subplot(111)
    
        pos=[(ii[1],ii[0]) for ii in graph.nodes()]
        pos_dict=dict(zip(graph.nodes(),pos))
        nx.draw(graph,pos=pos_dict,ax=ax,with_labels=True)
        plt.show(block=False)
        return
    
    
    def formDirected(g,match):
        '''Form directed graph D from G and matching M.
    
        <g>: undirected bipartite graph. Nodes are separated by their
             'bipartite' attribute.
        <match>: list of edges forming a matching of <g>. 
    
        Return <d>: directed graph, with edges in <match> pointing from set-0
                    (bipartite attribute ==0) to set-1 (bipartite attrbiute==1),
                    and the other edges in <g> but not in <matching> pointing
                    from set-1 to set-0.
        '''
    
        d=nx.DiGraph()
    
        for ee in g.edges():
            if ee in match or (ee[1],ee[0]) in match:
                if g.node[ee[0]]['bipartite']==0:
                    d.add_edge(ee[0],ee[1])
                else:
                    d.add_edge(ee[1],ee[0])
            else:
                if g.node[ee[0]]['bipartite']==0:
                    d.add_edge(ee[1],ee[0])
                else:
                    d.add_edge(ee[0],ee[1])
    
        return d
    
    
    def enumMaximumMatching(g):
        '''Find all maximum matchings in an undirected bipartite graph.
    
        <g>: undirected bipartite graph. Nodes are separated by their
             'bipartite' attribute.
    
        Return <all_matches>: list, each is a list of edges forming a maximum
                              matching of <g>. 
        '''
    
        all_matches=[]
    
        #----------------Find one matching M----------------
        match=bipartite.hopcroft_karp_matching(g)
    
        #---------------Re-orient match arcs---------------
        match2=[]
        for kk,vv in match.items():
            if g.node[kk]['bipartite']==0:
                match2.append((kk,vv))
        match=match2
        all_matches.append(match)
    
        #-----------------Enter recursion-----------------
        all_matches=enumMaximumMatchingIter(g,match,all_matches,None)
    
        return all_matches
    
    
    def enumMaximumMatchingIter(g,match,all_matches,add_e=None):
        '''Recurively search maximum matchings.
    
        <g>: undirected bipartite graph. Nodes are separated by their
             'bipartite' attribute.
        <match>: list of edges forming one maximum matching of <g>.
        <all_matches>: list, each is a list of edges forming a maximum
                       matching of <g>. Newly found matchings will be appended
                       into this list.
        <add_e>: tuple, the edge used to form subproblems. If not None,
                 will be added to each newly found matchings.
    
        Return <all_matches>: updated list of all maximum matchings.
        '''
    
        #---------------Form directed graph D---------------
        d=formDirected(g,match)
    
        #-----------------Find cycles in D-----------------
        cycles=list(nx.simple_cycles(d))
    
        if len(cycles)==0:
    
            #---------If no cycle, find a feasible path---------
            all_uncovered=set(g.node).difference(set([ii[0] for ii in match]))
            all_uncovered=all_uncovered.difference(set([ii[1] for ii in match]))
            all_uncovered=list(all_uncovered)
    
            #--------------If no path, terminiate--------------
            if len(all_uncovered)==0:
                return all_matches
    
            #----------Find a length 2 feasible path----------
            idx=0
            uncovered=all_uncovered[idx]
            while True:
    
                if uncovered not in nx.isolates(g):
                    paths=nx.single_source_shortest_path(d,uncovered,cutoff=2)
                    len2paths=[vv for kk,vv in paths.items() if len(vv)==3]
    
                    if len(len2paths)>0:
                        reversed=False
                        break
    
                    #----------------Try reversed path----------------
                    paths_rev=nx.single_source_shortest_path(d.reverse(),uncovered,cutoff=2)
                    len2paths=[vv for kk,vv in paths_rev.items() if len(vv)==3]
    
                    if len(len2paths)>0:
                        reversed=True
                        break
    
                idx+=1
                if idx>len(all_uncovered)-1:
                    return all_matches
    
                uncovered=all_uncovered[idx]
    
            #-------------Create a new matching M'-------------
            len2path=len2paths[0]
            if reversed:
                len2path=len2path[::-1]
            len2path=zip(len2path[:-1],len2path[1:])
    
            new_match=[]
            for ee in d.edges():
                if ee in len2path:
                    if g.node[ee[1]]['bipartite']==0:
                        new_match.append((ee[1],ee[0]))
                else:
                    if g.node[ee[0]]['bipartite']==0:
                        new_match.append(ee)
    
            if add_e is not None:
                for ii in add_e:
                    new_match.append(ii)
    
            all_matches.append(new_match)
    
            #---------------------Select e---------------------
            e=set(len2path).difference(set(match))
            e=list(e)[0]
    
            #-----------------Form subproblems-----------------
            g_plus=g.copy()
            g_minus=g.copy()
            g_plus.remove_node(e[0])
            g_plus.remove_node(e[1])
    
            g_minus.remove_edge(e[0],e[1])
    
            add_e_new=[e,]
            if add_e is not None:
                add_e_new.extend(add_e)
    
            all_matches=enumMaximumMatchingIter(g_minus,match,all_matches,add_e)
            all_matches=enumMaximumMatchingIter(g_plus,new_match,all_matches,add_e_new)
    
        else:
            #----------------Find a cycle in D----------------
            cycle=cycles[0]
            cycle.append(cycle[0])
            cycle=zip(cycle[:-1],cycle[1:])
    
            #-------------Create a new matching M'-------------
            new_match=[]
            for ee in d.edges():
                if ee in cycle:
                    if g.node[ee[1]]['bipartite']==0:
                        new_match.append((ee[1],ee[0]))
                else:
                    if g.node[ee[0]]['bipartite']==0:
                        new_match.append(ee)
    
            if add_e is not None:
                for ii in add_e:
                    new_match.append(ii)
    
            all_matches.append(new_match)
    
            #-----------------Choose an edge E-----------------
            e=set(match).intersection(set(cycle))
            e=list(e)[0]
    
            #-----------------Form subproblems-----------------
            g_plus=g.copy()
            g_minus=g.copy()
            g_plus.remove_node(e[0])
            g_plus.remove_node(e[1])
            g_minus.remove_edge(e[0],e[1])
    
            add_e_new=[e,]
            if add_e is not None:
                add_e_new.extend(add_e)
    
            all_matches=enumMaximumMatchingIter(g_plus,match,all_matches,add_e_new)
            all_matches=enumMaximumMatchingIter(g_minus,new_match,all_matches,add_e)
    
        return all_matches
    
    if __name__=='__main__':
        g=nx.Graph()
        edges=[
                [(1,0), (0,0)],
                [(1,1), (0,0)],
                [(1,2), (0,2)],
                [(1,3), (0,2)],
                [(1,4), (0,3)],
                [(1,4), (0,5)],
                [(1,5), (0,2)],
                [(1,5), (0,4)],
                [(1,6), (0,1)],
                [(1,6), (0,4)],
                [(1,6), (0,6)]
                ]
    
        for ii in edges:
            g.add_node(ii[0],bipartite=0)
            g.add_node(ii[1],bipartite=1)
    
        g.add_edges_from(edges)
        plotGraph(g)
    
        all_matches=enumMaximumMatching(g)
    
        for mm in all_matches:
            g_match=nx.Graph()
            for ii in mm:
                g_match.add_edge(ii[0],ii[1])
            plotGraph(g_match)
    

    【讨论】:

    • 对上述内容的一些次要update,我使用邻接矩阵来进行一些计算,从而提高速度。循环检测部分还是依赖networkx的方法。
    • 看起来有人将此代码提升到他们的 gitHub 存储库 github.com/Xunius/bipartite_matching/blob/master/…
    • @AbhijitSarkar 那是我...
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-05-09
    • 2011-04-26
    • 1970-01-01
    • 2013-01-27
    • 1970-01-01
    • 2016-04-03
    • 1970-01-01
    相关资源
    最近更新 更多