2016-05-10 17 views
6

के सभी संभावित अधिकतम मिलान एक द्विपक्षीय ग्राफ के maximum cardinality matching को खोजने के लिए मैं networkx का उपयोग कर रहा हूं।द्विपक्षीय ग्राफ

मिलान किए गए किनारे विशेष ग्राफ के लिए अद्वितीय नहीं हैं।

क्या मेरे पास अधिकतम अधिकतम मिलान ढूंढने का कोई तरीका है?

निम्नलिखित उदाहरण के लिए, सभी किनारों नीचे अधिकतम मिलान किया जा सकता है:

{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() 

bipartite graph

दुर्भाग्य से,

केवल

{1: 2, 2: 1} 

क्या कोई अन्य तरीका है जिससे मैं अन्य संयोजन भी प्राप्त कर सकता हूं?

+1

@chthonicdaemon एक एल्गोरिथ्म यहाँ वर्णित है कि द्विपक्षीय ग्राफ के लिए काम करेंगे (अगर यह networkx में implimented हो, तो मुझे पता नहीं है) नहीं है: Tassa, Tamir (2012), "एक द्विपक्षीय ग्राफ में सभी अधिकतम-तुलनीय किनारों ढूँढना ", सैद्धांतिक कंप्यूटर विज्ञान 423: 50-58, डोई: 10.1016/j.tcs.2011.12.071। यदि आप या कोई और इसे लिखता है, तो मैं इसे नेटवर्कएक्स में जोड़ने का सुझाव दूंगा। – Joel

उत्तर

1

मैंने यूनो के काम को पढ़ा है और कार्यान्वयन के साथ आने की कोशिश की है। एक कामकाजी उदाहरण के साथ नीचे मेरा बहुत लंबा कोड है। इस विशेष मामले में 4 "व्यवहार्य" शिखर (यूनो की शब्दावली के अनुसार) हैं, इसलिए प्रत्येक को पहले से कवर किए गए वर्टेक्स के साथ स्विच करना, आपके पास 2^4 = 16 अलग-अलग संभावित अधिकतम मिलान हैं।

मुझे यह मानना ​​है कि मैं ग्राफ सिद्धांत के लिए बहुत नया हूं और मैं यूनो की प्रक्रियाओं का बिल्कुल पालन नहीं कर रहा था, मामूली मतभेद हैं और ज्यादातर मैंने कोई अनुकूलन करने का प्रयास नहीं किया है। मैंने पेपर को समझने में संघर्ष किया क्योंकि मुझे लगता है कि स्पष्टीकरण बिल्कुल सही नहीं हैं और आंकड़ों में त्रुटियां हो सकती हैं। तो कृपया देखभाल के साथ उपयोग करें और यदि आप इसे अनुकूलित करने में मदद कर सकते हैं तो यह बहुत अच्छा होगा!

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) 
+0

उपरोक्त चीज़ में कुछ मामूली [अपडेट] (https://github.com/Xunius/bipartite_matching), मैंने कुछ कंप्यूटेशंस करने के बजाय आसन्नता मैट्रिक्स का उपयोग किया जो थोड़ा सा गति बढ़ावा देता है। चक्र पहचान भाग अभी भी नेटवर्कक्स के तरीकों पर निर्भर है। – Jason

0

आंशिक समाधान:

import networkx as nx 
import matplotlib.pyplot as plt 

G = nx.complete_bipartite_graph(2, 3) 
nx.draw(G, with_labels=True) 

edges = G.edges() 
A = [] 
maximum = len(nx.bipartite.eppstein_matching(G)) 
for x in range(len(edges)): 
    b = nx.bipartite.eppstein_matching(G) 
    if len(b) != maximum: 
     break 
    A+=[nx.bipartite.eppstein_matching(G)] 
    G.remove_edge(*edges[x]) 
print(list(eval(x) for x in set(str(x) for x in A))) 

plt.show() 

यह समाधान लाइनों कि पहले से ही आदेश जोड़े खोजने के लिए इस्तेमाल किया गया है की निकालने से कहीं अधिक काम करने के लिए प्रकट होता है। हालांकि, यह उन सभी के लिए काम नहीं करता है, क्योंकि लाइनें जो कई बार जोड़ सकती हैं, जैसे कि 2x3 द्विपक्षीय उदाहरण में, ऐसा करने से पहले हटा दिए जाते हैं।

संपादित करें:

import networkx as nx 
import matplotlib.pyplot as plt 

G = nx.complete_bipartite_graph(2, 3) 
nx.draw(G, with_labels=True) 

def checkAll(G,m): 
    b = nx.bipartite.eppstein_matching(G) # Finds first match 
    c = list(b.keys()) 
    for y in c[int(len(c)/2):]: # Reduces to one occurrence per line 
     b.pop(y) 
    if len(b) != m: # If new size, break 
     return 0 
    return b # Add to list of possibilities 

edges = G.edges() 
A = [] 
m = len(nx.bipartite.eppstein_matching(G))/2 # Create an expected maximum 
for x in range(len(edges)): 
    b = checkAll(G,m) 
    if b: 
     A += [b] 
    else: 
     break 
    keys = list(b.keys()) 
    cache = (keys[0],b[keys[0]]) 
    removed = [] 
    while 1: 
     removed += [(keys[1],b[keys[1]])] 
     G.remove_edge(keys[1],b[keys[1]]) # Remove first option 
     b = checkAll(G,m) 
     if b and cache == (keys[0],b[keys[0]]): 
      A += [b] 
     else: 
      break 
    G.add_edges_from(removed) 
    G.remove_edge(*edges[x]) 
print(list(eval(x) for x in set(str(x) for x in A))) 

plt.show() 

मैं अब यह कई जोड़ियां के लिए हर पंक्ति जाँच बना दिया है, तो और भी अधिक संयोजन फंस गए हैं। मुझे संदेह है कि, हालांकि, पर्याप्त पर्याप्त सेट पूरी तरह से जिम्मेदार नहीं होंगे। फ़ंक्शन checkAll() में, मैंने संयोजनों की अनावश्यकता को कम कर दिया है ताकि उन्हें पढ़ने और गिनने में बहुत आसान हो। आशा है कि ये आपकी मदद करेगा!

+0

हाय @StardustGogeta उत्तर के लिए धन्यवाद। मैंने अभी तक @ एरिक और @ जोएल द्वारा सुझाए गए कागजात नहीं देखे हैं, लेकिन आपके सुझाव पर एक नज़र डाली है। मैं कोड को कैसे समझता हूं कि आप 'एज' सूची से एक ही किनारे को हटाते हैं और अन्य संयोजनों की खोज करते हैं। मैं उस तर्क का पूरी तरह से समझ नहीं पा रहा हूं जहां अधिकतम मिलान में पहला किनारा कैश किया गया है, दूसरा हटा दिया गया है और अब 'चेकअल()' से यह चेक किया गया है यदि नकद नए मिलान में पहले किनारे से मेल खाता है। मुझे भी चिंता है: यदि 2 किनारे _only_ को एक किनारे से कनेक्ट किया गया है, तो यदि यह किनारा जल्दी हटा दिया जाता है तो कोई मेलिंग 'm' – EDWhyte

+0

@ED के बराबर नहीं होगी, मुझे इसके साथ किसी भी गलतफहमी के लिए खेद है, क्योंकि मैंने केवल पहले सीखा इस समस्या का शोध करके द्विपक्षीय ग्राफ के बारे में। मेरी सोच यह थी कि प्रत्येक किनारे के लिए, 'चेकअल()' उस किनारे से शुरू होने वाले सभी संभावित संयोजनों को समाप्त कर देगा। हालांकि, क्योंकि यह एक जटिल विषय है, और मुझे इसके बारे में बहुत कुछ पता नहीं है, मैं इसे पूरी तरह कार्यान्वित नहीं कर सकता। मुझे पता है कि सभी संयोजनों को खोजने के लिए एक आदर्श, पुनरावर्ती समाधान में फैक्टोरियल जटिलता होगी, और इसलिए दूसरों द्वारा वर्णित अनुसार, केवल एक फैक्टरियल-जटिलता अधिकतम मिलान करने वाले एल्गोरिदम को लिखना आसान होगा। – StardustGogeta

4

पेकाकी यूनो द्वारा "द्विपक्षीय ग्राफ में सभी परफेक्ट, अधिकतम और अधिकतम मिलानों का आकलन करने के लिए एल्गोरिदम" इस के लिए एक एल्गोरिदम है। http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.107.8179&rep=rep1&type=pdf

प्रमेय 2 कहते हैं "एक द्विपक्षीय ग्राफ में अधिकतम matchings हे में प्रगणित जा सकता है (MN^1/2 + NNM) समय और हे (एम) अंतरिक्ष, जहां एनएम जी में अधिकतम matchings की संख्या है "

संबंधित मुद्दे