portaldacalheta.pt
  • Κύριος
  • Το Μέλλον Της Εργασίας
  • Πίσω Μέρος
  • Ζωή Σχεδιαστών
  • Διαχείριση Έργου
Επιστήμη Δεδομένων Και Βάσεις Δεδομένων

Μέγιστη ροή και πρόβλημα γραμμικής ανάθεσης



Εδώ είναι ένα πρόβλημα: Η εταιρεία σας αναθέτει στους εργολάβους να εκπληρώσουν συμβάσεις. Κοιτάζετε τις λίστες σας και αποφασίζετε ποιοι εργολάβοι είναι διαθέσιμοι για ένα συμβόλαιο διάρκειας ενός μηνός και ελέγχετε τα διαθέσιμα συμβόλαιά σας για να δείτε ποιοι είναι για καθήκοντα διάρκειας ενός μήνα. Δεδομένου ότι γνωρίζετε πόσο αποτελεσματικά μπορεί να εκπληρώσει κάθε συμβόλαιο κάθε συμβόλαιο, πώς αναθέτετε στους εργολάβους να μεγιστοποιήσετε τη συνολική αποτελεσματικότητα για αυτόν τον μήνα;

Αυτό είναι ένα παράδειγμα του προβλήματος ανάθεσης και το πρόβλημα μπορεί να λυθεί με το κλασικό Ουγγρικός αλγόριθμος .



Διμερής σύμπτωση



Ο ουγγρικός αλγόριθμος (επίσης γνωστός ως αλγόριθμος Kuhn-Munkres) είναι ένας πολυώνυμος αλγόριθμος χρόνου, ο οποίος μεγιστοποιεί το βάρος βάρους σε ένα σταθμισμένο διμερές γράφημα. Εδώ, οι εργολάβοι και τα συμβόλαια μπορούν να μοντελοποιηθούν ως ένα διμερές γράφημα, με την αποτελεσματικότητά τους ως το βάρος των άκρων μεταξύ του εργολάβου και των κόμβων των συμβάσεων.



Σε αυτό το άρθρο, θα μάθετε για την εφαρμογή του ουγγρικού αλγορίθμου που χρησιμοποιεί το Αλγόριθμος Edmonds-Karp για να λύσει το πρόβλημα γραμμικής ανάθεσης. Θα μάθετε επίσης πώς ο αλγόριθμος Edmonds-Karp είναι μια μικρή τροποποίηση του Ford-Φούλκερσον και τη σημασία αυτής της τροποποίησης.

Το πρόβλημα αιχμής ροής

Το ίδιο το πρόβλημα ροής αιχμής μπορεί να περιγραφεί ανεπίσημα ως το πρόβλημα της μεταφοράς κάποιου ρευστού ή αερίου, μέσω ενός δικτύου σωλήνων από μία πηγή σε ένα μόνο τερματικό. Αυτό γίνεται με την παραδοχή ότι η πίεση στο δίκτυο είναι επαρκής για να διασφαλιστεί ότι το υγρό ή το αέριο δεν μπορούν να παραμείνουν σε οποιοδήποτε μήκος σωληνώσεων ή σωληνώσεων (τα σημεία όπου συναντώνται διαφορετικά μήκη σωλήνων).



Κάνοντας ορισμένες αλλαγές στο γράφημα, το πρόβλημα κατανομής μπορεί να μετατραπεί σε πρόβλημα αιχμής ροής.

Προκριματικά

Οι ιδέες που απαιτούνται για την επίλυση αυτών των προβλημάτων προκύπτουν σε πολλούς μαθηματικούς και μηχανολογικούς κλάδους, συχνά παρόμοιες έννοιες είναι γνωστές με διαφορετικά ονόματα και εκφράζονται με διαφορετικούς τρόπους (για παράδειγμα, πίνακες γειτνίασης και λίστες γειτνίασης). Δεδομένου ότι αυτές οι ιδέες είναι αρκετά εσωτερικές, γίνονται επιλογές για το πώς αυτές οι έννοιες γενικά θα καθοριστούν για οποιοδήποτε δεδομένο περιβάλλον.



Αυτό το άρθρο δεν θα περιλαμβάνει προηγούμενες γνώσεις πέρα ​​από μια σύντομη εισαγωγική θεωρία.

Οι υλοποιήσεις σε αυτήν την ανάρτηση αντιπροσωπεύουν τα προβλήματα ως κατευθυνόμενα γραφήματα (διάγραμμα).



Ντιράφο

ΕΝΑ digrafo έχει δύο γνωρίσματα , setOfNodes Υ setOfArcs . Και τα δύο αυτά χαρακτηριστικά είναι σκηνικά (ακατάστατες συλλογές). Στα μπλοκ κώδικα σε αυτήν την ανάρτηση, χρησιμοποιώ το Python frozenset , αλλά αυτή η λεπτομέρεια δεν είναι ιδιαίτερα σημαντική.

DiGraph = namedtuple('DiGraph', ['setOfNodes','setOfArcs'])

(Σημείωση: Αυτός και όλοι οι άλλοι κωδικοί σε αυτό το άρθρο είναι γραμμένοι στο Python 3.6.)



Κόμβοι

ΕΝΑ κόμβος n Αποτελείται από δύο χαρακτηριστικά:

  • n.uid: Α Μοναδικό αναγνωριστικό .



    Αυτό σημαίνει ότι για οποιονδήποτε από τους δύο κόμβους x και y,

x.uid != y.uid
  • n.datum: Αυτό αντιπροσωπεύει οποιοδήποτε αντικείμενο δεδομένων.
Node = namedtuple('Node', ['uid','datum'])

Τόξο

ΕΝΑ τόξο a Αποτελείται από τρία χαρακτηριστικά:

  • a.fromNode: Αυτό είναι ένα κόμβος , όπως ορίσαμε παραπάνω.

  • a.toNode: Αυτό είναι ένα κόμβος , όπως ορίσαμε παραπάνω.

  • a.datum: Αυτό αντιπροσωπεύει οποιοδήποτε αντικείμενο δεδομένων.

Arc = namedtuple('Arc', ['fromNode','toNode','datum'])

Το σύνολο των τόξα στο δίφθογγος αντιπροσωπεύει μια δυαδική σχέση στο κόμβοι στο δίφθογγος . Η ύπαρξη του τόξο a υπονοεί ότι υπάρχει σχέση μεταξύ a.fromNode και a.toNode.

Σε ένα κατευθυνόμενο γράφημα (σε αντίθεση με ένα μη κατευθυνόμενο γράφημα), η ύπαρξη σχέσης μεταξύ a.fromNode και a.toNode όχι υπονοεί ότι μια παρόμοια σχέση μεταξύ a.toNode και a.fromNode υπάρχει.

τι είναι συστατικό στο γωνιακό

Αυτό συμβαίνει επειδή σε ένα μη κατευθυνόμενο γράφημα, η σχέση που εκφράζεται δεν είναι απαραίτητα συμμετρική.

Ψηφία

Κόμβοι Υ τόξα μπορεί να χρησιμοποιηθεί για να ορίσει a δίφθογγος , αλλά για ευκολία, στους ακόλουθους αλγόριθμους, a δίφθογγος χρησιμοποιώντας ως λεξικό .

Εδώ είναι μια μέθοδος που μπορεί να μετατρέψει την παραπάνω γραφική αναπαράσταση σε μια αναπαράσταση λεξικού παρόμοια με αυτήν είναι :

def digraph_to_dict(G): G_as_dict = dict([]) for a in G.setOfArcs: if(a.fromNode not in G.setOfNodes): err_msg = 'There is no Node {a.fromNode.uid!s} to match the Arc from {a.fromNode.uid!s} to {a.toNode.uid!s}'.format(**locals()) pdg(G) raise KeyError(err_msg) if(a.toNode not in G.setOfNodes): err_msg = 'There is no Node {a.toNode.uid!s} to match the Arc from {a.fromNode.uid!s} to {a.toNode.uid!s}'.format(**locals()) pdg(G) raise KeyError(err_msg) G_as_dict[a.fromNode] = (G_as_dict[a.fromNode].union(frozenset([a]))) if (a.fromNode in G_as_dict) else frozenset([a]) for a in G.setOfArcs: if(a.fromNode not in G.setOfNodes): err_msg = 'There is no Node {a.fromNode.uid!s} to match the Arc from {a.fromNode.uid!s} to {a.toNode.uid!s}'.format(**locals()) raise KeyError(err_msg) if a.toNode not in G_as_dict: G_as_dict[a.toNode] = frozenset([]) return G_as_dict

Και εδώ είναι ένα άλλο που το μετατρέπει σε λεξικό λεξικών, μια άλλη λειτουργία που θα είναι χρήσιμη:

def digraph_to_double_dict(G): G_as_dict = dict([]) for a in G.setOfArcs: if(a.fromNode not in G.setOfNodes): err_msg = 'There is no Node {a.fromNode.uid!s} to match the Arc from {a.fromNode.uid!s} to {a.toNode.uid!s}'.format(**locals()) raise KeyError(err_msg) if(a.toNode not in G.setOfNodes): err_msg = 'There is no Node {a.toNode.uid!s} to match the Arc from {a.fromNode.uid!s} to {a.toNode.uid!s}'.format(**locals()) raise KeyError(err_msg) if(a.fromNode not in G_as_dict): G_as_dict[a.fromNode] = dict({a.toNode : frozenset([a])}) else: if(a.toNode not in G_as_dict[a.fromNode]): G_as_dict[a.fromNode][a.toNode] = frozenset([a]) else: G_as_dict[a.fromNode][a.toNode] = G_as_dict[a.fromNode][a.toNode].union(frozenset([a])) for a in G.setOfArcs: if(a.fromNode not in G.setOfNodes): err_msg = 'There is no Node {a.fromNode.uid!s} to match the Arc from {a.fromNode.uid!s} to {a.toNode.uid!s}'.format(**locals()) raise KeyError(err_msg) if a.toNode not in G_as_dict: G_as_dict[a.toNode] = dict({}) return G_as_dict

Όταν το άρθρο μιλά για ένα δίφθογγος καθώς το λεξικό το αντιπροσωπεύει, θα αναφέρει G_as_dict για αναφορά.

Μερικές φορές βοηθά να πάρει ένα κόμβος του α δίφθογγος G μέχρι το uid (Μοναδικό αναγνωριστικό):

def find_node_by_uid(find_uid, G): nodes = {n for n in G.setOfNodes if n.uid == find_uid} if(len(nodes) != 1): err_msg = 'Node with uid {find_uid!s} is not unique.'.format(**locals()) raise KeyError(err_msg) return nodes.pop()

Κατά την αναγνώριση γραφικών, οι χρήστες χρησιμοποιούν μερικές φορές τους όρους κόμβος και κορυφή για να αναφέρεται στην ίδια έννοια? το ίδιο λέγεται για τους όρους τόξο και άκρη.

Υπάρχουν δύο γραφικές παραστάσεις στο Python, είναι που χρησιμοποιεί λεξικά και άλλα που χρησιμοποιεί αντικείμενα για την αναπαράσταση γραφικών. Η αναπαράσταση σε αυτήν την ανάρτηση εμπίπτει σε αυτές τις δύο κοινές παραστάσεις.

Αυτή είναι η εκπροσώπησή μου για ένα δίφθογγος . Υπάρχουν πολλά τέτοια, αλλά αυτό είναι δικό μου.

Δρόμοι και διαδρομές

Αφήστε S_Arcs γίνε α αλληλουχία οριστική (παραγγελθείσα συλλογή) του τόξα σε ένα δίφθογγος G τόσο πολύ ώστε αν a είναι οποιοδήποτε τόξο σε S_Arcs εκτός από το τελευταίο, και b ακολουθεί a στη σειρά, τότε πρέπει να υπάρχει ένα κόμβος n = a.fromNode σε G.setOfNodes ως a.toNode = b.fromNode.

Ξεκινώντας από το πρώτο τόξο στο S_Arcs, και συνεχίζοντας στο τελευταίο τόξο στο S_Arcs, συλλέγει (με τη σειρά που βρέθηκε) όλα κόμβοι n καθώς τα ορίζουμε παραπάνω μεταξύ των δύο τόξα διαδοχικά σε S_Arcs. Σημειώστε την παραγγελθείσα συλλογή του κόμβοι συλλέχθηκε κατά τη διάρκεια αυτής της λειτουργίας S_{Nodes}.

def path_arcs_to_nodes(s_arcs): s_nodes = list([]) arc_it = iter(s_arcs) step_count = 0 last = None try: at_end = False last = a1 = next(arc_it) while (not at_end): s_nodes += [a1.fromNode] last = a2 = next(arc_it) step_count += 1 if(a1.toNode != a2.fromNode): err_msg = 'Error at index {step_count!s} of Arc sequence.'.format(**locals()) raise ValueError(err_msg) a1 = a2 except StopIteration as e: at_end = True if(last is not None): s_nodes += [last.toNode] return s_nodes
  • Εάν υπάρχει κόμβος εμφανίζεται περισσότερες από μία φορές στην ακολουθία S_Nodes στη συνέχεια καλέστε S_Arcs ένα Μονοπάτι με δίφθογγος G.

  • Διαφορετικά, καλέστε S_Arcs ένα μέσω από list(S_Nodes)[0] α list(S_Nodes)[-1] στο δίφθογγος G.

Κόμβος πηγής

Κλήση προς κόμβος s ένα κόμβος προέλευσης σε δίφθογγος G αν s είναι σε G.setOfNodes και G.setOfArcs δεν έχει τόξο a ως a.toNode = s.

def source_nodes(G): to_nodes = frozenset({a.toNode for a in G.setOfArcs}) sources = G.setOfNodes.difference(to_nodes) return sources

Τερματικός κόμβος

Κλήση προς κόμβος t ένα τερματικός κόμβος στο δίφθογγος G αν t είναι σε G.setOfNodes και G.setOfArcs δεν έχει τόξο a ως a.fromNode = t.

def terminal_nodes(G): from_nodes = frozenset({a.fromNode for a in G.setOfArcs}) terminals = G.setOfNodes.difference(from_nodes) return terminals

Cortes y Cortes s-t

ΕΝΑ Τομή cut του α δίφθογγος G συνδεδεμένος είναι ένα υποσύνολο από τόξα από G.setOfArcs οι οποίες μέρος το σετ των κόμβοι G.setOfNodes σε G. G είναι συνδεδεμένο εάν το καθένα κόμβος n σε G.setOfNodes έχει τουλάχιστον ένα τόξο a σε G.setOfArcs ως n = a.fromNode ή n = a.toNode, αλλά όχι a.fromNode != a.toNode.

Cut = namedtuple('Cut', ['setOfCutArcs'])

Ο παραπάνω ορισμός αναφέρεται στο υποσύνολο του τόξα , αλλά μπορείτε επίσης να ορίσετε ένα διαμέρισμα του κόμβοι από G.setOfNodes.

Για συναρτήσεις predecessors_of και successors_of, n είναι ένα κόμβος στο σετ G.setOfNodes από δίφθογγος G, και cut είναι ένα Τομή από G:

def cut_predecessors_of(n, cut, G): allowed_arcs = G.setOfArcs.difference(frozenset(cut.setOfCutArcs)) predecessors = frozenset({}) previous_count = len(predecessors) reach_fringe = frozenset({n}) keep_going = True while( keep_going ): reachable_from = frozenset({a.fromNode for a in allowed_arcs if (a.toNode in reach_fringe)}) reach_fringe = reachable_from predecessors = predecessors.union(reach_fringe) current_count = len(predecessors) keep_going = current_count - previous_count > 0 previous_count = current_count return predecessors def cut_successors_of(n, cut, G): allowed_arcs = G.setOfArcs.difference(frozenset(cut.setOfCutArcs)) successors = frozenset({}) previous_count = len(successors) reach_fringe = frozenset({n}) keep_going = True while( keep_going ): reachable_from = frozenset({a.toNode for a in allowed_arcs if (a.fromNode in reach_fringe)}) reach_fringe = reachable_from successors = successors.union(reach_fringe) current_count = len(successors) keep_going = current_count - previous_count > 0 previous_count = current_count return successors

Αφήστε cut Γίνε ένα Τομή απο δίφθογγος G.

def get_first_part(cut, G): firstPartFringe = frozenset({a.fromNode for a in cut.setOfCutArcs}) firstPart = firstPartFringe for n in firstPart: preds = cut_predecessors_of(n,cut,G) firstPart = firstPart.union(preds) return firstPart def get_second_part(cut, G): secondPartFringe = frozenset({a.toNode for a in cut.setOfCutArcs}) secondPart = secondPartFringe for n in secondPart: succs= cut_successors_of(n,cut,G) secondPart = secondPart.union(succs) return secondPart

cut είναι ένα Τομή απο δίφθογγος G αν: (get_first_part(cut, G).union(get_second_part(cut, G)) == G.setOfNodes) y (len(get_first_part(cut, G).intersect(get_second_part(cut, G))) == 0) cut ονομάζεται a x-y περικοπή αν (x in get_first_part(cut, G)) y (y in get_second_part(cut, G) ) y (x != y). Οταν ο κόμβος x σε ένα x-y περικοπή cut είναι ένα κόμβος προέλευσης και το κόμβος y στο x-y περικοπή είναι ένα τερματικός κόμβος , τότε αυτό Τομή ονομάζεται a κόψτε s-t .

STCut = namedtuple('STCut', ['s','t','cut'])

Δίκτυα ροής

Μπορείτε να χρησιμοποιήσετε ένα δίφθογγος G να αντιπροσωπεύει ένα δίκτυο ροής.

Εκχωρήστε το καθένα κόμβος n, όπου n είναι σε G.setOfNodes α n.datum που είναι ένα FlowNodeDatum:

FlowNodeDatum = namedtuple('FlowNodeDatum', ['flowIn','flowOut'])

Εκχωρήστε το καθένα τόξο a, όπου a είναι ένα G.setOfArcs και a.datum που είναι ένα FlowArcDatum.

FlowArcDatum = namedtuple('FlowArcDatum', ['capacity','flow'])

flowNodeDatum.flowIn και flowNodeDatum.flowOut Αυτοί είναι πραγματικοί αριθμοί θετικός .

flowArcDatum.capacity και flowArcDatum.flow είναι επίσης θετικοί πραγματικοί αριθμοί.

τι είναι ένα αρχείο κεφαλίδας c++

Για κάθε κόμβο κόμβος n σε G.setOfNodes:

n.datum.flowIn = sum({a.datum.flow for a in G.Arcs if a.toNode == n}) n.datum.flowOut = sum({a.datum.flow for a in G.Arcs if a.fromNode == n})

ο Δίφθογγος G τώρα αντιπροσωπεύει το δίκτυο ροής .

ο ροή από G αναφέρεται στη ροή a.flow για όλα τα τόξα a σε G.

Εφικτές ροές

Αφησε τον δίφθογγος G αντιπροσωπεύουν ένα δίκτυο ροής .

ο δίκτυο ροής εκπροσωπείται από G έχω εφικτές ροές Ναί:

  1. Για κάθε κόμβος n σε G.setOfNodes εκτός από κόμβοι προέλευσης Υ τερματικοί κόμβοι : n.datum.flowIn = n.datum.flowOut.

  2. Για κάθε τόξο a σε G.setOfNodes: a.datum.capacity <= a.datum.flow.

Η συνθήκη 1 καλείται περιορισμός διατήρησης .

Ο όρος 2 καλείται περιορισμός χωρητικότητας .

Ικανότητα κοπής

ο ικανότητα κοπής του α κόψτε s-t stCut με κόμβος προέλευσης s και ένα τερματικός κόμβος t του α δίκτυο ροής αντιπροσωπεύεται από ένα δίφθογγος G είναι:

def cut_capacity(stCut, G): part_1 = get_first_part(stCut.cut,G) part_2 = get_second_part(stCut.cut,G) s_part = part_1 if stCut.s in part_1 else part_2 t_part = part_1 if stCut.t in part_1 else part_2 cut_capacity = sum({a.datum.capacity for a in stCut.cut.setOfCutArcs if ( (a.fromNode in s_part) and (a.toNode in t_part) )}) return cut_capacity

Κοπή ελάχιστης χωρητικότητας

Αφήστε stCut = stCut(s,t,cut) Γίνε ένα κόψτε s-t του α δίκτυο ροής αντιπροσωπεύεται από ένα δίφθογγος G.

stCut είναι αυτός ελάχιστη ικανότητα κοπής απο δίκτυο ροής εκπροσωπείται από G αν δεν υπάρχει άλλο κόψτε s-t stCutCompetitor σε αυτό δίκτυο ροής όπως και:

cut_capacity(stCut, G)

Απογύμνωση των ροών

Θα ήθελα να αναφερθώ δίφθογγος ποιο θα ήταν το αποτέλεσμα της λήψης ενός δίφθογγος G και εκκαθάριση όλων των δεδομένων ροής από όλα κόμβοι σε G.setOfNodes και επίσης όλα τόξα σε G.setOfArcs.

def strip_flows(G): new_nodes= frozenset( (Node(n.uid, FlowNodeDatum(0.0,0.0)) for n in G.setOfNodes) ) new_arcs = frozenset([]) for a in G.setOfArcs: new_fromNode = Node(a.fromNode.uid, FlowNodeDatum(0.0,0.0)) new_toNode = Node(a.toNode.uid, FlowNodeDatum(0.0,0.0)) new_arc = Arc(new_fromNode, new_toNode, FlowArcDatum(a.datum.capacity, 0.0)) new_arcs = new_arcs.union(frozenset([new_arc])) return DiGraph(new_nodes, new_arcs)

Πρόβλημα αιχμής

ΕΝΑ δίκτυο ροής εκπροσωπείται ως δίφθογγος G, α κόμβος προέλευσης s σε G.setOfNodes και ένα τερματικός κόμβος t σε G.setOfNodes, G μπορεί να αντιπροσωπεύει ένα πρόβλημα αιχμής ροής Ναί:

(len(list(source_nodes(G))) == 1) and (next(iter(source_nodes(G))) == s) and (len(list(terminal_nodes(G))) == 1) and (next(iter(terminal_nodes(G))) == t)

Σημειώστε αυτήν την παράσταση:

MaxFlowProblemState = namedtuple('MaxFlowProblemState', ['G','sourceNodeUid','terminalNodeUid','maxFlowProblemStateUid'])

Όπου sourceNodeUid = s.uid, terminalNodeUid = t.uid, και maxFlowProblemStateUid είναι ένα αναγνωριστικό για την παρουσία προβλήματος.

Λύση αιχμής

Αφήστε maxFlowProblem αντιπροσωπεύουν ένα πρόβλημα αιχμής ροής . Η λύση στο πρόβλημα maxFlowProblem μπορεί να αντιπροσωπεύεται από ένα δίκτυο ροής εκπροσωπείται ως δίφθογγος H.

ο Δίφθογγος H είναι μια λύση εφικτός για εκείνον πρόβλημα αιχμής ροής στην είσοδο python maxFlowProblem Ναί:

  1. strip_flows(maxFlowProblem.G) == strip_flows(H).

  2. H είναι ένα δίκτυο ροής και έχει εφικτές ροές .

Εάν επιπλέον των 1 και 2:

  1. Δεν μπορεί να υπάρχει άλλο δίκτυο ροής εκπροσωπείται από το δίφθογγος K ως strip_flows(G) == strip_flows(K) και find_node_by_uid(t.uid,G).flowIn .

Τότε H είναι επίσης μια λύση άριστος για maxFlowProblem.

Με άλλα λόγια, α εφικτή λύση μέγιστης ροής μπορεί να αναπαρασταθεί με ένα δίφθογγος , ότι:

  1. Είναι πανομοιότυπο με δίφθογγος G του αντίστοιχου πρόβλημα αιχμής ροής με εξαίρεση τη ροή n.datum.flowIn, n.datum.flowOut και η ροή a.datum.flow οποιουδήποτε από τα κόμβοι Υ τόξα μπορεί να είναι διαφορετικά.

  2. Αντιπροσωπεύει ένα δίκτυο ροής ποιο ειναι το ΠΡΟΒΛΗΜΑ ΜΕ ΑΥΤΟ εφικτές ροές .

Και, μπορεί να αντιπροσωπεύει ένα βέλτιστη λύση μέγιστης ροής εάν επιπλέον:

  1. Το flowIn για εκείνον κόμβος που αντιστοιχεί σε τερματικός κόμβος στο πρόβλημα αιχμής ροής είναι όσο το δυνατόν μεγαλύτερο (όταν εξακολουθούν να πληρούνται οι προϋποθέσεις 1 και 2).

Ναί δίφθογγος H αντιπροσωπεύει ένα διάλυμα μέγιστης ροής : find_node_by_uid(s.uid,H).flowOut = find_node_by_uid(t.uid,H).flowIn Αυτό προκύπτει από το μέγιστη ροή, ελάχιστο διάγραμμα διάτμησης (συζητείται παρακάτω). Άτυπα, αφού H έχω βιώσιμες ροές αυτό σημαίνει ότι το ροή δεν μπορεί να δημιουργηθεί (με εξαίρεση το κόμβος προέλευσης s) ούτε «καταστράφηκε» (με εξαίρεση το τερματικός κόμβος t) κατά τη διέλευση οποιουδήποτε (άλλου) κόμβος ( περιορισμοί διατήρησης ).

Από το α πρόβλημα αιχμής ροής περιέχει μόνο έναν κόμβο πηγής s και ένα τερματικός κόμβος t, κάθε ροή «δημιουργείται» σε s πρέπει να «καταστραφεί» σε t κύμα δίκτυο ροής όχι έχω εφικτές ροές (ο περιορισμός διατήρησης βιάστηκε).

Αφησε τον δίφθογγος H αντιπροσωπεύουν ένα εφικτή λύση μέγιστης ροής ; ονομάζεται η παραπάνω τιμή Τιμή ροής s-t από H.

Επιτρέπει:

mfps=MaxFlowProblemState(H, maxFlowProblem.sourceNodeUid, maxFlowProblem.terminalNodeUid, maxFlowProblem.maxFlowProblemStateUid)

Αυτό σημαίνει ότι mfps είναι ένα διάδοχο κράτος από maxFlowProblem, που σημαίνει ότι mfps είναι ακριβώς όπως maxFlowProblem με την εξαίρεση ότι οι τιμές a.flow για τόξα a σε maxFlowProblem.setOfArcs μπορεί να διαφέρει από a.flow για τόξα a σε mfps.setOfArcs.

def get_mfps_flow(mfps): flow_from_s = find_node_by_uid(mfps.sourceNodeUid,mfps.G).datum.flowOut flow_to_t = find_node_by_uid(mfps.terminalNodeUid,mfps.G).datum.flowIn if( (flow_to_t - flow_from_s) > 0): raise Exception('Infeasible s-t flow') return flow_to_t

Αυτή είναι μια απεικόνιση του mfps μαζί με το maxFlowProb συσχετισμένο. Καθε τόξο a στην εικόνα έχει μια ετικέτα, αυτές οι ετικέτες είναι a.datum.flowFrom/a.datum.flowTo, η καθεμία κόμβος n στην εικόνα έχει μια ετικέτα και αυτές οι ετικέτες είναι n.uid {n.datum.flowIn / a.datum.flowOut}.

Οθόνη μέγιστης ροής

Διατμητική ροή

Αφήστε mfps αντιπροσωπεύουν κατάσταση προβλήματος MaxFlowProblemState και αφήστε stCut αντιπροσωπεύουν ένα Τομή από mfps.G. ο ροή διάτμησης stCut ορίζεται ως εξής:

def get_stcut_flow(stCut,mfps): s = find_node_by_uid(mfps.sourceNodeUid, mfps.G) t = find_node_by_uid(mfps.terminalNodeUid, mfps.G) part_1 = get_first_part(stCut.cut,mfps.G) part_2 = get_second_part(stCut.cut,mfps.G) s_part = part_1 if s in part_1 else part_2 t_part = part_1 if t in part_1 else part_2 s_t_sum = sum(a.datum.flow for a in mfps.G.setOfArcs if (a in stCut.cut.setOfCutArcs) and (a.fromNode in s_part) ) t_s_sum = sum(a.datum.flow for a in mfps.G.setOfArcs if (a in stCut.cut.setOfCutArcs) and (a.fromNode in t_part) ) cut_flow = s_t_sum - t_s_sum return cut_flow

διακοπή ροής s-t είναι το άθροισμα των ροών του διαμερίσματος που περιέχει το κόμβος προέλευσης για το διαμέρισμα που περιέχει το τερματικός κόμβος μείον το άθροισμα των ροών του διαμερίσματος που περιέχει το τερματικός κόμβος για το διαμέρισμα που περιέχει το κόμβος προέλευσης .

Μέγιστη ροή, ελάχιστη διάτμηση

Τι maxFlowProblem αντιπροσωπεύουν ένα πρόβλημα αιχμής ροής και ότι η λύση στο maxFlowProblem αντιπροσωπεύεται από ένα δίκτυο ροής εκπροσωπήθηκε ως δίφθογγος H.

Αφήστε minStCut ο ελάχιστη ικανότητα κοπής απο δίκτυο ροής εκπροσωπείται από maxFlowProblem.G.

Επειδή στη ροή του μέγιστη ροή η ροή προέρχεται από ένα κόμβος προέλευσης και τελειώνει σε έναν μόνο κόμβο τερματικό και, λόγω περιορισμοί χωρητικότητας και το περιορισμοί διατήρησης , γνωρίζουμε ότι όλη η ροή εισέρχεται maxFlowProblem.terminalNodeUid πρέπει να διασχίσει οποιοδήποτε κόψτε s-t , συγκεκριμένα πρέπει να διασχίσει minStCut. Αυτό σημαίνει:

get_flow_value(H, maxFlowProblem) <= cut_capacity(minStCut, maxFlowProblem.G)

Λύση προβλήματος αιχμής

Η βασική ιδέα για την επίλυση α πρόβλημα αιχμής ροής maxFlowProblem είναι να ξεκινήσετε με ένα διάλυμα μέγιστης ροής αντιπροσωπεύεται από δίφθογγος H. Ένα τέτοιο σημείο εκκίνησης μπορεί να είναι H = strip_flows(maxFlowProblem.G).

Η εργασία είναι τότε να χρησιμοποιήσετε H και από μερικούς άπληστος τροποποίηση των τιμών a.datum.flow από μερικούς τόξα a σε H.setOfArcs για να παράγει ένα άλλο διάλυμα μέγιστης ροής αντιπροσωπεύεται από δίφθογγος K έτσι K δεν μπορεί ακόμη να αντιπροσωπεύει ένα δίκτυο ροής με βιώσιμες ροές και get_flow_value(H, maxFlowProblem) . Όσο συνεχίζεται αυτή η διαδικασία, η ποιότητα (get_flow_value(K, maxFlowProblem)) του διάλυμα μέγιστης ροής (K) βρέθηκαν, πρόσφατα, καλύτερα από οποιαδήποτε άλλη διάλυμα μέγιστης ροής που έχει βρεθεί. Εάν η διαδικασία φτάσει σε ένα σημείο όπου ξέρει ότι δεν είναι δυνατή καμία περαιτέρω βελτίωση, η διαδικασία μπορεί να τερματιστεί και θα επιστρέψει το διάλυμα μέγιστης ροής βέλτιστος .

Η παραπάνω περιγραφή είναι γενική και παραλείπει πολλές δοκιμές όπως, εάν μια τέτοια διαδικασία είναι δυνατή ή πόσο καιρό μπορεί να διαρκέσει, θα δώσω μερικές περισσότερες λεπτομέρειες και τον αλγόριθμο.

Το Θεώρημα Μέγιστης Ροής, Ελάχιστη Διάτμηση

Από το βιβλίο Ροές στα δίκτυα της Ford και του Fulkerson , η δήλωση της Θεώρημα μέγιστης ροής, ελάχιστη διάτμηση (Το θεώρημα 5.1) είναι:

Για οποιοδήποτε δίκτυο, η μέγιστη τιμή ροής s α t ισούται με την ελάχιστη ικανότητα κοπής όλων των κοψίματος που χωρίζουν s και t.

Χρησιμοποιώντας τους ορισμούς σε αυτήν την ανάρτηση, που μεταφράζεται σε:

Η λύση σε ένα maxFlowProblem αντιπροσωπεύεται από ένα δίκτυο ροής εκπροσωπήθηκε ως δίφθογγος H είναι άριστος Ναί:

get_flow_value(H, maxFlowProblem) == cut_capacity(minStCut, maxFlowProblem.G)

Μου αρέσει αυτό το τεστ του θεωρήματος και της Wikipedia άλλα .

ο το θεώρημα μέγιστης ροής, ελάχιστη διάτμηση χρησιμοποιείται για να δείξει την ορθότητα και την πληρότητα του Μέθοδος Ford-Fulkerson .

Θα δώσω επίσης μια απόδειξη του θεωρήματος στην ενότητα μετά τρόποι αύξησης .

Η μέθοδος Ford-Fulkerson και ο αλγόριθμος Edmonds-Karp

CLRS ορίστε τη μέθοδο Ford-Fulkerson (ενότητα 26.2) ως εξής:

FORD-FULKERSON-METHOD(G, s, t): initialize flow f to 0 while there exists an augmenting path p in the residual graph G_f augment flow f along

Υπόλοιπο γράφημα

ο Υπόλοιπο γράφημα του α δίκτυο ροής εκπροσωπείται ως δίφθογγος Το 'G' μπορεί να αναπαρασταθεί ως α δίφθογγος G_f:

ResidualDatum = namedtuple('ResidualDatum', ['residualCapacity','action']) def agg_n_to_u_cap(n,u,G_as_dict): arcs_out = G_as_dict[n] return sum([a.datum.capacity for a in arcs_out if( (a.fromNode == n) and (a.toNode == u) ) ]) def agg_n_to_u_flow(n,u,G_as_dict): arcs_out = G_as_dict[n] return sum([a.datum.flow for a in arcs_out if( (a.fromNode == n) and (a.toNode == u) ) ]) def get_residual_graph_of(G): G_as_dict = digraph_to_dict(G) residual_nodes = G.setOfNodes residual_arcs = frozenset([]) for n in G_as_dict: arcs_from = G_as_dict[n] nodes_to = frozenset([find_node_by_uid(a.toNode.uid,G) for a in arcs_from]) for u in nodes_to: n_to_u_cap_sum = agg_n_to_u_cap(n,u,G_as_dict) n_to_u_flow_sum = agg_n_to_u_flow(n,u,G_as_dict) if(n_to_u_cap_sum > n_to_u_flow_sum): flow = round(n_to_u_cap_sum - n_to_u_flow_sum, TOL) residual_arcs = residual_arcs.union( frozenset([Arc(n,u,datum=ResidualDatum(flow, 'push'))]) ) if(n_to_u_flow_sum > 0.0): flow = round(n_to_u_flow_sum, TOL) residual_arcs = residual_arcs.union( frozenset([Arc(u,n,datum=ResidualDatum(flow, 'pull'))]) ) return DiGraph(residual_nodes, residual_arcs)
  • agg_n_to_u_cap(n,u,G_as_dict) επιστροφή στο άθροισμα a.datum.capacity για το τόξα στο υποσύνολο του G.setOfArcs όπου εκείνος τόξο a βρίσκεται στο υποσύνολο εάν a.fromNode = n και a.toNode = u.

  • agg_n_to_u_cap(n,u,G_as_dict) επιστρέφει το άθροισμα a.datum.flow για όλα τα τόξα στο υποσύνολο του G.setOfArcs όπου εκείνος τόξο a βρίσκεται στο υποσύνολο εάν a.fromNode = n και a.toNode = u.

    είναι python γραμμένο σε γ

Εν ολίγοις, το υπόλοιπο γράφημα G_f αντιπροσωπεύει ορισμένες ενέργειες που μπορούν να εκτελεστούν στο δίφθογγος «Ζ».

Κάθε ζευγάρι κόμβοι n, u σε G.setOfNodes απο δίκτυο ροής αντιπροσωπεύεται από δίφθογγος G μπορεί να δημιουργήσει 0,1 ή 2 τόξα στο υπόλοιπο γράφημα G_f του «G».

  1. Το ζεύγος n, u δεν δημιουργεί τόξα σε G_f αν δεν υπάρχει τόξο a σε G.setOfArcs όπως a.fromNode = n και a.toNode = u.

  2. Το ζεύγος n, u παράγει το τόξο a σε G_f.setOfArcs όπου a αντιπροσωπεύει ένα τόξο επισημαίνονται ως ροή ώθησης από n α u a = Arco (n, u, datum = ResidualNode (n_to_u_cap_sum - n_to_u_flow_sum)) αν n_to_u_cap_sum> n_to_u_flow_sum.

  3. Το ζεύγος n, u παράγει το τόξο a σε G_f.setOfArcs όπου a αντιπροσωπεύει ένα τόξο επισημαίνονται ως τραβήξτε τόξο ροής από n α u a = Arco (n, u, datum = ResidualNode (n_to_u_cap_sum - n_to_u_flow_sum)) αν n_to_u_flow_sum> 0.0.

  • Καθε τόξο ροής ώθησης σε G_f.setOfArcs αντιπροσωπεύει την ενέργεια της προσθήκης ενός συνόλου ροής x <= n_to_u_cap_sum - n_to_u_flow_sum προς το τόξα στο υποσύνολο του G.setOfArcs όπου εκείνος τόξο a βρίσκεται στο υποσύνολο εάν a.fromNode = n και a.toNode = u.

  • Καθε τόξο ροής έλξης σε G_f.setOfArcs αντιπροσωπεύει τη δράση αφαίρεσης ενός συνόλου ροής x <= n_to_u_flow_sum προς το τόξα στο υποσύνολο του G.setOfArcs που τόξο a βρίσκεται στο υποσύνολο εάν a.fromNode = n και a.toNode = u.

Εκτελέστε μια μεμονωμένη ενέργεια όπως Σπρώξτε ή ακόμη από G_f στο τόξα εφαρμόζεται σε G θα μπορούσε να δημιουργήσει ένα δίκτυο ροής χωρίς εφικτές ροές επειδή η περιορισμοί χωρητικότητας ή περιορισμοί διατήρησης θα μπορούσε να βιαστεί στο δημιουργήθηκε δίκτυο ροής .

Εδώ είναι μια απεικόνιση του υπόλοιπο γράφημα από το προηγούμενο παράδειγμα εμφάνισης a διάλυμα μέγιστης ροής , στην οθόνη το καθένα τόξο a αντιπροσωπεύει a.residualCapacity.

Εμφάνιση μέγιστης ροής (Υπόλοιπο)

Τρόπος αύξησης

Αφήστε maxFlowProblem Γίνε ένα πρόβλημα αιχμής ροής και αφήστε G_f = get_residual_graph_of (G) είναι υπόλοιπο γράφημα από maxFlowProblem.G.

ΕΝΑ προς τα πάνω augmentingPath για maxFlowProblem είναι οποιοδήποτε μέσω από find_node_by_uid(maxFlowProblem.sourceNode,G_f) α find_node_by_uid(maxFlowProblem.terminalNode,G_f).

Αποδεικνύεται ότι α διαδρομή αύξησης augmentingPath μπορεί να εφαρμοστεί σε ένα διάλυμα μέγιστης ροής εκπροσωπείται από το δίφθογγος H δημιουργώντας ένα άλλο διάλυμα μέγιστης ροής εκπροσωπείται από το δίφθογγος K όπου get_flow_value (H, maxFlowProblem) αν H Δεν είναι βέλτιστος .

Εδώ βλέπετε πώς:

def augment(augmentingPath, H): augmentingPath = list(augmentingPath) H_as_dict = digraph_to_dict(H) new_nodes = frozenset({}) new_arcs = frozenset({}) visited_nodes = frozenset({}) visited_arcs = frozenset({}) bottleneck_residualCapacity = min( augmentingPath, key=lambda a: a.datum.residualCapacity ).datum.residualCapacity for x in augmentingPath: from_node_uid = x.fromNode.uid if x.datum.action == 'push' else x.toNode.uid to_node_uid = x.toNode.uid if x.datum.action == 'push' else x.fromNode.uid from_node = find_node_by_uid( from_node_uid, H ) to_node = find_node_by_uid( to_node_uid, H ) load = bottleneck_residualCapacity if x.datum.action == 'push' else -bottleneck_residualCapacity for a in H_as_dict[from_node]: if(a.toNode == to_node): test_sum = a.datum.flow + load new_flow = a.datum.flow new_from_node_flow_out = a.fromNode.datum.flowOut new_to_node_flow_in = a.toNode.datum.flowIn new_from_look = {n for n in new_nodes if (n.uid == a.fromNode.uid)} new_to_look = {n for n in new_nodes if (n.uid == a.toNode.uid) } prev_from_node = new_from_look.pop() if (len(new_from_look)>0) else a.fromNode prev_to_node = new_to_look.pop() if (len(new_to_look)>0) else a.toNode new_nodes = new_nodes.difference(frozenset({prev_from_node})) new_nodes = new_nodes.difference(frozenset({prev_to_node})) if(test_sum > a.datum.capacity): new_flow = a.datum.capacity new_from_node_flow_out = prev_from_node.datum.flowOut - a.datum.flow + a.datum.capacity new_to_node_flow_in = prev_to_node.datum.flowIn - a.datum.flow + a.datum.capacity load = test_sum - a.datum.capacity elif(test_sum <0.0): new_flow = 0.0 new_from_node_flow_out = prev_from_node.datum.flowOut - a.datum.flow new_to_node_flow_in = prev_to_node.datum.flowIn - a.datum.flow load = test_sum else: new_flow = test_sum new_from_node_flow_out = prev_from_node.datum.flowOut - a.datum.flow + new_flow new_to_node_flow_in = prev_to_node.datum.flowIn - a.datum.flow + new_flow load = 0.0 new_from_node_flow_out = round(new_from_node_flow_out, TOL) new_to_node_flow_in = round(new_to_node_flow_in, TOL) new_flow = round(new_flow, TOL) new_from_node = Node(prev_from_node.uid, FlowNodeDatum(prev_from_node.datum.flowIn, new_from_node_flow_out)) new_to_node = Node(prev_to_node.uid, FlowNodeDatum(new_to_node_flow_in, prev_to_node.datum.flowOut)) new_arc = Arc(new_from_node, new_to_node, FlowArcDatum(a.datum.capacity, new_flow)) visited_nodes = visited_nodes.union(frozenset({a.fromNode,a.toNode})) visited_arcs = visited_arcs.union(frozenset({a})) new_nodes = new_nodes.union(frozenset({new_from_node, new_to_node})) new_arcs = new_arcs.union(frozenset({new_arc})) not_visited_nodes = H.setOfNodes.difference(visited_nodes) not_visited_arcs = H.setOfArcs.difference(visited_arcs) full_new_nodes = new_nodes.union(not_visited_nodes) full_new_arcs = new_arcs.union(not_visited_arcs) G = DiGraph(full_new_nodes, full_new_arcs) full_new_arcs_update = frozenset([]) for a in full_new_arcs: new_from_node = a.fromNode new_to_node = a.toNode new_from_node = find_node_by_uid( a.fromNode.uid, G ) new_to_node = find_node_by_uid( a.toNode.uid, G ) full_new_arcs_update = full_new_arcs_update.union( {Arc(new_from_node, new_to_node, FlowArcDatum(a.datum.capacity, a.datum.flow))} ) G = DiGraph(full_new_nodes, full_new_arcs_update) return G

Στα παραπάνω, TOL είναι μια τιμή ανοχής για ολοκληρώστε τις τιμές ροής στο δίκτυο. Αυτό γίνεται για να αποφευχθεί ο καταρράκτης του ανακρίβεια υπολογισμών κυμαινόμενου σημείου . Έτσι, για παράδειγμα, χρησιμοποίησα το «TOL = 10» για το γύρο στο 10 σημαντικά ψηφία .

Αφήστε K = augment (augmentingPath, H) και μετά K αντιπροσωπεύει ένα εφικτή λύση μέγιστης ροής για maxFlowProblem. Για να είναι αληθινή η δήλωση, το δίκτυο ροής εκπροσωπείται από K θα έπρεπε να εφικτές ροές (όχι για παραβίαση περιορισμός χωρητικότητας κύμα περιορισμός διατήρησης . Να γιατί: Στην παραπάνω μέθοδο, το καθένα κόμβος προστέθηκε στο νέο δίκτυο ροής αντιπροσωπεύεται από δίφθογγος K είναι ένα ακριβές αντίγραφο του κόμβος από δίφθογγος H ή α κόμβος n ο οποίος είχε προσθέσει τον ίδιο αριθμό στο n.datum.flowIn όπως το n.datum.flowOut. Αυτό σημαίνει ότι το περιορισμός διατήρησης πληρούται στο 'K' αρκεί να πληρούται στο 'H'. ο περιορισμός διατήρησης είναι ικανοποιημένος επειδή ελέγξουμε ρητά ότι υπάρχει τόξο νέο a στο Διαδίκτυο έχετε a.datum.flow <= a.datum.capacity; ως εκ τούτου, εφ 'όσον το τόξα του σετ H.setOfArcs που αντιγράφηκαν αμετάβλητα σε K.setOfArcs μην παραβιάζετε το περιορισμός χωρητικότητας , τότε K δεν παραβιάζει το περιορισμός χωρητικότητας .

Είναι επίσης αλήθεια ότι get_flow_value (H, maxFlowProblem) αν H Δεν είναι βέλτιστος .

Εδώ είναι γιατί: Για αύξηση διαδρομής augmentingPath υπάρχει στο δίφθογγος την αναπαράσταση του υπόλοιπο γράφημα G_f του α πρόβλημα αιχμής ροής maxFlowProblem μετά το τελευταίο τόξο a σε augmentingPath πρέπει να είναι τόξο 'Push' και πρέπει να έχει a.toNode == maxFlowProblem.terminalNode. ΕΝΑ διαδρομή αύξησης ορίζεται ως ένα που τελειώνει στο τερματικός κόμβος απο πρόβλημα αιχμής ροής για το οποίο είναι ένα διαδρομή αύξησης . Από τον ορισμό του υπόλοιπο γράφημα , είναι σαφές ότι το τελευταίο τόξο σε ένα τρόπος αύξησης σε αυτό υπόλοιπο γράφημα πρέπει να είναι τόξο 'Σπρώξτε' γιατί υπάρχει τόξο 'Τραβήξτε' b στο τρόπος αύξησης θα έχει b.toNode == maxFlowProblem.terminalNode και b.fromNode! = maxFlowProblem.terminalNode από τον ορισμό του μέσω . Επιπλέον, από τον ορισμό του μέσω , είναι σαφές ότι το τερματικός κόμβος Τροποποιείται μόνο μία φορά με τη μέθοδο augment. Λοιπόν augment τροποποίηση maxFlowProblem.terminalNode.flowIn ακριβώς μία φορά και αυξάνει την τιμή maxFlowProblem.terminalNode.flowIn γιατί το τελευταίο τόξο σε augmentingPath πρέπει να είναι Αυτός τόξο προκαλεί την τροποποίηση στο maxFlowProblem.terminalNode.flowIn κατά τη διάρκεια augment. Από τον ορισμό του augment, καθώς ισχύει για τόξα «Πιέστε», το maxFlowProblem.terminalNode.flowIn μπορεί να αυξηθεί μόνο, όχι να μειωθεί.

Μερικά στοιχεία Sedgewick και Wayne

Το βιβλίο Αλγόριθμοι, τέταρτη έκδοση των Robert Sedgewich και Kevin Wayne έχει μερικές υπέροχες σύντομες δοκιμές (σελίδες 892-894) που θα είναι χρήσιμες. Θα τα αναδημιουργήσω εδώ, αν και θα χρησιμοποιήσω γλώσσα που ανταποκρίνεται στους παραπάνω ορισμούς. Οι ετικέτες μου για δοκιμές είναι ίδιες με αυτές του βιβλίου του Sedgewick.

Πρόταση Ε: Για κάθε δίφθογγος H που αντιπροσωπεύει ένα εφικτή λύση μέγιστης ροής Ακόμη πρόβλημα αιχμής ροής maxFlowProblem, για οποιαδήποτε stCut get_stcut_flow(stCut,H,maxFlowProblem) = get_flow_value(H, maxFlowProblem).

Δοκιμή: Αποχώρηση stCut=stCut(maxFlowProblem.sourceNode,maxFlowProblem.terminalNode,set([a for a in H.setOfArcs if a.toNode == maxFlowProblem.terminalNode])). Πρόταση Ε κατέχεται από stCut απευθείας από τον ορισμό του τιμή ροής s-t . Ας υποθέσουμε ότι θέλουμε να μετακινήσουμε ένα κόμβος n από το διαμέρισμα s (get_first_part(stCut.cut, G)) και στο διαμέρισμα (get_second_part(stCut.cut, G)), για να γίνει αυτό πρέπει να αλλάξουμε stCut.cut, το οποίο θα μπορούσε να αλλάξει stcut_flow = get_stcut_flow(stCut,H,maxFlowProblem) και ακυρώνει πρόταση Ε . Ωστόσο, ας δούμε πώς η τιμή του stcut_flow θα αλλάξει καθώς κάνουμε αυτήν την αλλαγή. ο κόμβος n είναι σε ισορροπία, που σημαίνει ότι το άθροισμα της ροής στο κόμβος n ισούται με το άθροισμα της ροής αυτού (αυτό είναι απαραίτητο ώστε H να αντιπροσωπεύει a εφικτή λύση ).

Σημειώστε ότι όλη η ροή που ανήκει στο stcut_flow μπαίνω σε κόμβος n το εισάγει από το διαμέρισμα s (το ρεύμα εισέρχεται κόμβος n αφού το διαμέρισμα το κάνει άμεσα ή έμμεσα, καθώς δεν θα είχε υπολογιστεί στην τιμή stcut_flow γιατί πηγαίνει σε λάθος κατεύθυνση με βάση τον ορισμό). Παρατηρήστε ότι όλη η ροή που είναι μέρος του stcut_flow που μπαίνει στο κόμβος n το εισάγει από το διαμέρισμα s (τη ροή που εισέρχεται στο κόμβος n από το διαμέρισμα t άμεσα ή έμμεσα δεν έχουν υπολογιστεί στην τιμή stcut_flow γιατί κατευθύνετε τη λάθος κατεύθυνση με βάση τον ορισμό).

Επίσης, όλη η ροή που βγαίνει n τελικά θα ρέει (άμεσα ή έμμεσα) στο τερματικός κόμβος (αποδεικνύεται παραπάνω). Όταν μετακινούμε το κόμβος n στο διαμέρισμα t, όλη η ροή που εισέρχεται n από το διαμέρισμα s πρέπει να προστεθεί στη νέα τιμή stcut_flow; Ωστόσο, όλη η ροή που βγαίνει n πρέπει να αφαιρεθεί από τη νέα τιμή stcut_flow; αφαιρείται το τμήμα της ροής που πηγαίνει απευθείας στο διαμέρισμα t, καθώς αυτή η ροή είναι τώρα εσωτερική στο νέο διαμέρισμα t και δεν μετράται ως stcut_flow.

Το μέρος της ροής από τη θέση n σε κόμβοι από το διαμέρισμα πρέπει επίσης να αφαιρεθεί από stcut_flow: Μετά n μετακινείται στο διαμέρισμα t, αυτές οι ροές θα κατευθύνονται από τη θέση t στο διαμέρισμα s και επομένως δεν πρέπει να λαμβάνεται υπόψη στο stcut_flow, επειδή αυτές οι ροές εξαλείφουν την είσοδο στο διαμέρισμα s και πρέπει να μειωθούν κατά το άθροισμα αυτών των ροών και η εκροή από το διαμέρισμα s στο διαμέρισμα-t (όπου όλες οι ροές από το st πρέπει να τερματιστούν) πρέπει να μειωθεί κατά το ίδιο ποσό. Ως το κόμβος n βρισκόταν σε ισορροπία πριν από την επεξεργασία, η ενημέρωση θα είχε προσθέσει την ίδια τιμή στη νέα τιμή stcut_flow όταν αφαιρείται, αφήνοντας το πρόταση Ε διαγραφή μετά την αναβάθμιση. Η ισχύς του πρόταση Ε τότε ακολουθεί στην επαγωγή στο μέγεθος του διαμερίσματος t.

Εδώ είναι μερικά παραδείγματα δίκτυα ροής για να βοηθήσουμε στην απεικόνιση των λιγότερο προφανών περιπτώσεων όπου το πρόταση Ε κραταει; Στην εικόνα, οι κόκκινες περιοχές υποδεικνύουν το διαμέρισμα s, οι μπλε περιοχές αντιπροσωπεύουν το διαμέρισμα t και το τόξα πράσινο υποδηλώνει α κόψτε s-t **. Στη δεύτερη εικόνα, η ροή μεταξύ ** κόμβου Προς και κόμβος Το Β αυξάνεται ενώ η ροή μέσα τερματικός κόμβος t δεν αλλάζει.

Συνέπεια: Χωρίς τιμή ροή διάτμησης s-t μπορεί να υπερβαίνει την ικανότητα του κόψτε s-t .

Πρόταση ΣΤ (θεώρημα μέγιστης ροής και ελάχιστης κοπής): Αφήστε f να είναι μια ροή s-τ . Οι ακόλουθες 3 συνθήκες είναι ισοδύναμες:

  1. Υπάρχει μια περικοπή s-τ του οποίου η χωρητικότητα είναι ίση με την τιμή της ροής f.

  2. f είναι ένα μέγιστη ροή .

  3. Δεν υπάρχει τρόπος αύξησης σε σχέση με f.

Η συνθήκη 1 υποδηλώνει την κατάσταση 2 από το αποτέλεσμα. Η συνθήκη 2 συνεπάγεται την κατάσταση 3 επειδή η ύπαρξη μιας αυξανόμενης διαδρομής συνεπάγεται την ύπαρξη ροής με υψηλότερες τιμές, που έρχονται σε αντίθεση με το μέγιστο του «f» Η συνθήκη 3 υποδηλώνει την κατάσταση 1: Αφήστε C_s το σύνολο όλων κόμβοι που μπορεί να επιτευχθεί από s με αύξηση διαδρομής στο υπόλοιπο γράφημα . Αφήστε C_t ο υπόλοιπα τόξα , τότε t πρέπει να είναι σε C_t (σύμφωνα με την υπόθεσή μας). ο τόξα που διασχίζουν από C_s α C_t στη συνέχεια σχηματίστε ένα κόψτε s-t περιέχει μόνο τόξα a όπου a.datum.capacity = a.datum.flow ή a.datum.flow = 0.0. Εάν όχι, το κόμβοι συνδέεται με ένα τόξο με την εναπομένουσα εναπομένουσα χωρητικότητα a C_s θα ήταν στο σετ C_s από τότε θα υπήρχε ένα αυξημένη πίστα από s Ακόμη κόμβος . Η ροή μέσω του κοπή s-t ισούται με την ικανότητα του κοπή s-t (από το τόξα από C_s α C_t έχουν ροή ίση με τη χωρητικότητα) και επίσης με την τιμή του ροή s-t (με πρόταση Ε ).

Αυτή η δήλωση του θεωρήματος μέγιστη ροή, ελάχιστη κοπή υπονοεί την παραπάνω δήλωση του Ροές στα δίκτυα .

Συντελεστής (ιδιοκτησία ακεραιότητας): Όταν οι χωρητικότητες είναι ακέραιοι, υπάρχει μια μέγιστη ακέραια ροή τιμών και ο αλγόριθμος Ford-Fulkerson το βρίσκει.

Δοκιμή: Κάθε τρόπος αύξησης αυξάνει τη ροή με θετικό ακέραιο, το ελάχιστο των αχρησιμοποίητων δυνατοτήτων στα τόξα Σπρώξτε και ρέει στις καμάρες έλξη , τα οποία είναι πάντα θετικοί ακέραιοι.

καλύτερη γραμματοσειρά serif για εκτύπωση

Αυτό δικαιολογεί την περιγραφή της μεθόδου Ford-Φούλκερσον από CLRS . Η μέθοδος είναι να συνεχίσετε να βρίσκετε αυξανόμενες διαδρομές και εφαρμογή augment έως το τελευταίο maxFlowSolution εύρεση καλύτερων λύσεων, έως ότου πια αυξανόμενη διαδρομή , που σημαίνει ότι το τελευταίο διάλυμα μέγιστης ροής είναι βέλτιστος .

De Ford-Fulkerson a Edmonds-Karp

Οι υπόλοιπες ερωτήσεις σχετικά με την επίλυση του προβλήματα αιχμής ροής Αυτοί είναι:

  1. Πώς πρέπει να χτιστούν διαδρομές αύξησης ;

  2. Θα λήξει η μέθοδος εάν χρησιμοποιούμε πραγματικούς αριθμούς και όχι ακέραιους αριθμούς;

  3. Πόσος χρόνος θα χρειαστεί για να τελειώσει (εάν το κάνει);

ο Αλγόριθμος Edmonds-Karp καθορίστε ότι κάθε διαδρομή αύξησης είναι χτισμένο από ένα αναζήτηση πλάτους ( BFS ) απο υπόλοιπο γράφημα ; Αποδεικνύεται ότι αυτή η απόφαση από το σημείο 1 θα αναγκάσει επίσης τον τερματισμό του αλγορίθμου (σημείο 2) και επιτρέπει το ασυμπτωτικός χρόνος και η πολυπλοκότητα του χώρου να καθοριστεί.

Πρώτον, εδώ είναι μια εφαρμογή BFS :

def bfs(sourceNode, terminalNode, G_f): G_f_as_dict = digraph_to_dict(G_f) parent_arcs = dict([]) visited = frozenset([]) deq = deque([sourceNode]) while len(deq) > 0: curr = deq.popleft() if curr == terminalNode: break for a in G_f_as_dict.get(curr): if (a.toNode not in visited): visited = visited.union(frozenset([a.toNode])) parent_arcs[a.toNode] = a deq.extend([a.toNode]) path = list([]) curr = terminalNode while(curr != sourceNode): if (curr not in parent_arcs): err_msg = 'No augmenting path from {} to {}.'.format(sourceNode.uid, terminalNode.uid) raise StopIteration(err_msg) path.extend([parent_arcs[curr]]) curr = parent_arcs[curr].fromNode path.reverse() test = deque([path[0].fromNode]) for a in path: if(test[-1] != a.fromNode): err_msg = 'Bad path: {}'.format(path) raise ValueError(err_msg) test.extend([a.toNode]) return path

Χρησιμοποίησα ένα deque από την ενότητα συλλογών python .

Για να απαντήσω στην ερώτηση 2, θα παραφράσω μια άλλη απόδειξη Sedgewick και Wayne : Πρόταση Ζ. Ο αριθμός των μονοπάτια αύξησης απαιτείται στον αλγόριθμο Έντμοντς-Καρπ με κόμβους N Υ τόξα είναι το πολύ NA/2. Δοκιμή: Κάθε διαδρομή αύξησης έχει φιόγκο στο λαιμό τόξο - ένα τόξο από το οποίο διαγράφεται από υπόλοιπο γράφημα γιατί αντιστοιχεί καλά σε ένα τόξο Σπρώξτε που είναι γεμάτο με χωρητικότητα ή τόξο έλξη μέσω της οποίας η ροή γίνεται 0. Κάθε φορά a τόξο γίνεται εμπόδιο τόξο , το μήκος οποιουδήποτε διαδρομή αύξησης μέσα από αυτό θα πρέπει να αυξηθεί κατά συντελεστή 2. Αυτό συμβαίνει επειδή το καθένα κόμβος σε ένα Διαδρομή μπορεί να εμφανίζεται μόνο μία φορά ή καθόλου (από τον ορισμό του Διαδρομή Από το διαδρομές εξερευνούνται από το συντομότερο Διαδρομή σε όλο αυτό σημαίνει ότι τουλάχιστον ένα κόμβος συν πρέπει να επισκεφθείτε την ακόλουθη διαδρομή μέσω του σημείου συμφόρησης κόμβος που σημαίνει ένα επιπλέον 2 τόξα στο δρόμο πριν φτάσετε στο κόμβος . Από το διαδρομή αύξησης έχει μέγιστο μήκος N, κάθε τόξο μπορεί να έχει μέγιστο N/2 , διαδρομές αύξησης, και ο συνολικός αριθμός διαδρομές αύξησης είναι το πολύ NA/2.

ο Αλγόριθμος Edmonds-Karp τρέχει στις O(NA^2). Ναι το πολύ οι τρόποι NA/2 θα διερευνηθεί κατά τη διάρκεια του αλγορίθμου και θα εξερευνηθεί κάθε ένα τροχιά με BFS είναι N+A τότε ο πιο σημαντικός όρος του προϊόντος και συνεπώς η ασυμπτωτική πολυπλοκότητα O(NA^2).

Αφήστε mfp ας είναι ένα maxFlowProblemState.

def edmonds_karp(mfp): sid, tid = mfp.sourceNodeUid, mfp.terminalNodeUid no_more_paths = False loop_count = 0 while(not no_more_paths): residual_digraph = get_residual_graph_of(mfp.G) try: asource = find_node_by_uid(mfp.sourceNodeUid, residual_digraph) aterm = find_node_by_uid(mfp.terminalNodeUid, residual_digraph) apath = bfs(asource, aterm, residual_digraph) paint_mfp_path(mfp, loop_count, apath) G = augment(apath, mfp.G) s = find_node_by_uid(sid, G) t = find_node_by_uid(tid, G) mfp = MaxFlowProblemState(G, s.uid, t.uid, mfp.maxFlowProblemStateUid) loop_count += 1 except StopIteration as e: no_more_paths = True return mfp

Η παλαιότερη έκδοση είναι αναποτελεσματική και έχει χειρότερη πολυπλοκότητα από την O(NA^2) καθώς χτίζει ένα νέο διάλυμα μέγιστης ροής και ένα νέο υπόλοιπο γράφημα κάθε φορά (αντί να τροποποιείτε το υπάρχοντα γραφήματα καθώς ο αλγόριθμος εξελίσσεται). Για να βρείτε μια λύση O(NA^2) αλήθεια ο αλγόριθμος πρέπει να διατηρεί και τα δύο digrafo που αντιπροσωπεύει το κατάσταση προβλήματος αιχμής ροής και το σχετικό υπολειμματικό γράφημα . Επομένως, ο αλγόριθμος θα πρέπει να αποφεύγει την άσκοπη επανάληψη ** τόξων Υ κόμβοι και ενημερώστε τις τιμές τους και τις σχετικές τιμές στο υπόλοιπο γράφημα μόνο όταν είναι απαραίτητο.

Για να γράψετε έναν αλγόριθμο Έντμοντς Καρπ γρηγορότερα, ξαναγράφω πολλά αποσπάσματα κώδικα από τα παραπάνω. Ελπίζω να διαβάσω τον κώδικα που δημιούργησε ένα νέο digrafo Ήταν χρήσιμο να κατανοήσουμε τι συμβαίνει. Στον γρήγορο αλγόριθμο χρησιμοποιώ μερικά νέα κόλπα Python και δομές δεδομένων που δεν θέλω να αναλύσω. Θα πω ότι a.fromNode και a.toNode αντιμετωπίζονται τώρα ως χορδές και υγρά κόμβοι . Για αυτόν τον κωδικό, αφήστε mfps να είναι maxFlowProblemState

import uuid def fast_get_mfps_flow(mfps): flow_from_s = {n for n in mfps.G.setOfNodes if n.uid == mfps.sourceNodeUid}.pop().datum.flowOut flow_to_t = {n for n in mfps.G.setOfNodes if n.uid == mfps.terminalNodeUid}.pop().datum.flowIn if( (flow_to_t - flow_from_s) > 0.00): raise Exception('Infeasible s-t flow') return flow_to_t def fast_e_k_preprocess(G): G = strip_flows(G) get = dict({}) get['nodes'] = dict({}) get['node_to_node_capacity'] = dict({}) get['node_to_node_flow'] = dict({}) get['arcs'] = dict({}) get['residual_arcs'] = dict({}) for a in G.setOfArcs: if(a.fromNode not in G.setOfNodes): err_msg = 'There is no Node {a.fromNode.uid!s} to match the Arc from {a.fromNode.uid!s} to {a.toNode.uid!s}'.format(**locals()) raise KeyError(err_msg) if(a.toNode not in G.setOfNodes): err_msg = 'There is no Node {a.toNode.uid!s} to match the Arc from {a.fromNode.uid!s} to {a.toNode.uid!s}'.format(**locals()) raise KeyError(err_msg) get['nodes'][a.fromNode.uid] = a.fromNode get['nodes'][a.toNode.uid] = a.toNode lark = Arc(a.fromNode.uid, a.toNode.uid, FlowArcDatumWithUid(a.datum.capacity, a.datum.flow, uuid.uuid4())) if(a.fromNode.uid not in get['arcs']): get['arcs'][a.fromNode.uid] = dict({a.toNode.uid : dict({lark.datum.uid : lark})}) else: if(a.toNode.uid not in get['arcs'][a.fromNode.uid]): get['arcs'][a.fromNode.uid][a.toNode.uid] = dict({lark.datum.uid : lark}) else: get['arcs'][a.fromNode.uid][a.toNode.uid][lark.datum.uid] = lark for a in G.setOfArcs: if a.toNode.uid not in get['arcs']: get['arcs'][a.toNode.uid] = dict({}) for n in get['nodes']: get['residual_arcs'][n] = dict() get['node_to_node_capacity'][n] = dict() get['node_to_node_flow'][n] = dict() for u in get['nodes']: n_to_u_cap_sum = sum(a.datum.capacity for a in G.setOfArcs if (a.fromNode.uid == n) and (a.toNode.uid == u) ) n_to_u_flow_sum = sum(a.datum.flow for a in G.setOfArcs if (a.fromNode.uid == n) and (a.toNode.uid == u) ) if(n_to_u_cap_sum > n_to_u_flow_sum): flow = round(n_to_u_cap_sum - n_to_u_flow_sum, TOL) get['residual_arcs'][n][u] = Arc(n,u,ResidualDatum(flow, 'push')) if(n_to_u_flow_sum > 0.0): flow = round(n_to_u_flow_sum, TOL) get['residual_arcs'][u][n] = Arc(u,n,ResidualDatum(flow, 'pull')) get['node_to_node_capacity'][n][u] = n_to_u_cap_sum get['node_to_node_flow'][n][u] = n_to_u_flow_sum return get def fast_bfs(sid, tid, get): parent_of = dict([]) visited = frozenset([]) deq = coll.deque([sid]) while len(deq) > 0: n = deq.popleft() if n == tid: break for u in get['residual_arcs'][n]: if (u not in visited): visited = visited.union(frozenset({u})) parent_of[u] = get['residual_arcs'][n][u] deq.extend([u]) path = list([]) curr = tid while(curr != sid): if (curr not in parent_of): err_msg = 'No augmenting path from {} to {}.'.format(sid, curr) raise StopIteration(err_msg) path.extend([parent_of[curr]]) curr = parent_of[curr].fromNode path.reverse() return path def fast_edmonds_karp(mfps): sid, tid = mfps.sourceNodeUid, mfps.terminalNodeUid get = fast_e_k_preprocess(mfps.G) no_more_paths, loop_count = False, 0 while(not no_more_paths): try: apath = fast_bfs(sid, tid, get) get = fast_augment(apath, get) loop_count += 1 except StopIteration as e: no_more_paths = True nodes = frozenset(get['nodes'].values()) arcs = frozenset({}) for from_node in get['arcs']: for to_node in get['arcs'][from_node]: for arc in get['arcs'][from_node][to_node]: arcs |= frozenset({get['arcs'][from_node][to_node][arc]}) G = DiGraph(nodes, arcs) mfps = MaxFlowProblemState(G, sourceNodeUid=sid, terminalNodeUid=tid, maxFlowProblemStateUid=mfps.maxFlowProblemStateUid) return mfps

Ακολουθεί μια οπτικοποίηση του τρόπου με τον οποίο αυτός ο αλγόριθμος λύνει το παράδειγμα δίκτυο ροής από πάνω. Η οπτικοποίηση δείχνει τα βήματα που αντικατοπτρίζονται στο digrafo G που αντιπροσωπεύει το δίκτυο ροής αργότερο και πώς αντικατοπτρίζονται στο υπόλοιπο γράφημα αυτού του δικτύου ροής. Οι τρόποι αύξησης στο υπόλοιπο γράφημα εμφανίζονται ως κόκκινες διαδρομές και το digrafo που αντιπροσωπεύει το πρόβλημα το σύνολο των κόμβοι Υ τόξα επηρεάζεται από έναν κύβο το μονοπάτι αύξησης επισημαίνεται με πράσινο χρώμα. Σε κάθε περίπτωση, θα επισημάνω τα μέρη του γραφήματος που θα αλλάξουν (κόκκινο ή πράσινο) και μετά θα εμφανίσω το γράφημα μετά τις αλλαγές (μόνο μαύρο).

Εμφάνιση μέγιστης ροής

Εμφάνιση μέγιστης ροής (υπολειπόμενο)

Ακολουθεί μια άλλη οπτικοποίηση του τρόπου με τον οποίο αυτός ο αλγόριθμος επιλύει ένα διαφορετικό παράδειγμα δίκτυο ροής . Παρατηρήστε ότι αυτό χρησιμοποιεί πραγματικούς αριθμούς και περιέχει αρκετούς τόξα με τις ίδιες τιμές fromNode και toNode.

Λάβετε επίσης υπόψη ότι επειδή τα τόξα με ένα 'pull' ResidualDatum μπορούν να είναι μέρος της διαδρομής Rising, οι επηρεαζόμενοι κόμβοι στο διάγραμμα ροής δικτύου δεν μπορούν να βρίσκονται σε διαδρομή στο G!.

Διμερή γραφικά

Ας υποθέσουμε ότι έχουμε ένα digrafo G, G είναι διμερής εάν είναι δυνατόν να διαχωριστεί το κόμβοι σε G.setOfNodes σε δύο σύνολα (part_1 και part_2) έτσι ώστε για οποιοδήποτε τόξο a σε G.setOfArcs όχι Μπορεί να είναι αλήθεια ότι a.fromNode σε part_1 και a.toNode σε part_1. κανενα απο τα δυο Μπορεί να είναι αλήθεια ότι a.fromNode σε part_2 και a.toNode σε part_2.

Με άλλα λόγια, G είναι διμερές εάν μπορεί να χωριστεί σε δύο ομάδες κόμβοι με τέτοιο τρόπο ώστε το καθένα τόξο πρέπει να συνδέσετε ένα κόμβος σε ένα σύνολο σε έναν κόμβο στο άλλο σετ.

Διμερείς δοκιμές

Ας υποθέσουμε ότι έχουμε ένα digrafo G, θέλουμε να δοκιμάσουμε εάν είναι διμερές . Μπορούμε να το κάνουμε αυτό στο O(|G.setOfNodes|+|G.setOfArcs|) από το άπληστο χρώμα του διχρωμικού γραφήματος.

Πρώτον, πρέπει να δημιουργήσουμε ένα νέο digrafo H. Αυτό το γράφημα θα έχει το ίδιο σύνολο κόμβοι όπως G, αλλά θα έχουν περισσότερα τόξα από G. Καθε τόξο a σε G θα δημιουργήσει 2 τόξα σε H; ο πρώτος τόξο θα είναι ίδιο με το a και το δεύτερο τόξο επενδύει τον διευθυντή του a (b = Arc(a.toNode,a.fromNode,a.datum)).

Bipartition = coll.namedtuple('Bipartition',['firstPart', 'secondPart', 'G']) def bipartition(G): nodes = frozenset(G.setOfNodes arcs = frozenset(G.setOfArcs) arcs = arcs.union( frozenset( {Arc(a.toNode,a.fromNode,a.datum) for a in G.setOfArcs} ) ) H = DiGraph(nodes, arcs) H_as_dict = digraph_to_dict(H) color = dict([]) some_node = next(iter(H.setOfNodes)) deq = coll.deque([some_node]) color[some_node] = -1 while len(deq) > 0: curr = deq.popleft() for a in H_as_dict.get(curr): if (a.toNode not in color): color[a.toNode] = -1*color[curr] deq.extend([a.toNode]) elif(color[curr] == color[a.toNode]): print(curr,a.toNode) raise Exception('Not Bipartite.') firstPart = frozenset( {n for n in color if color[n] == -1 } ) secondPart = frozenset( {n for n in color if color[n] == 1 } ) if( firstPart.union(secondPart) != G.setOfNodes ): raise Exception('Not Bipartite.') return Bipartition(firstPart, secondPart, G)

Αγώνες και μέγιστοι αγώνες

Ας υποθέσουμε ότι έχουμε ένα digrafo G και matching είναι ένα υποσύνολο του τόξα από G.setOfArcs. matching είναι ένα ταιριάζει ναι για δύο digrafo a και b σε matching: len(frozenset( {a.fromNode} ).union( {a.toNode} ).union( {b.fromNode} ).union( {b.toNode} )) == 4. Με άλλα λόγια, όχι τόξο σε ένα σύμπτωση μοιραστείτε ένα κόμβος .

ο σύμπτωση matching, είναι η μέγιστη αντιστοίχιση εάν δεν υπάρχει άλλη συμφωνία alt_matching σε G τόσο πολύ ώστε len(matching) . Με άλλα λόγια, matching είναι ένα μέγιστο ταίριασμα Εάν είναι το μεγαλύτερο σετ τόξα σε G.setOfArcs που εξακολουθεί να ικανοποιεί τον ορισμό του συνδυασμός (προσθήκη οποιουδήποτε τόξο που δεν είναι ήδη στον αγώνα θα σπάσει το συνδυασμός ορισμός).

ΕΝΑ μέγιστος συνδυασμός matching είναι ένα τέλειος συνδυασμός ναι για καθένα κόμβος n σε G.setOfArcs υπάρχει τόξο a σε matching όπου a.fromNode == n or a.toNode == n.

Μέγιστος συνδυασμός δύο διαμερισμάτων

ΕΝΑ μέγιστος συνδυασμός δύο διαμερισμάτων είναι ένα μέγιστος συνδυασμός σε ένα digrafo G Τι είναι αυτό διμερισμένο .

Από G είναι διμερισμένο , το πρόβλημα εύρεσης α διμερής μέγιστος συνδυασμός μπορεί να μετατραπεί σε α πρόβλημα αιχμής ροής επιλύσιμο με αλγόριθμο από τον Edmonds-Karp και μετά το μέγιστη σύνδεση δύο μερών μπορεί να ανακτηθεί από το διάλυμα στο πρόβλημα αιχμής ροής .

Αφήστε bipartition γίνε α διμερές από G.

Για αυτό, πρέπει να δημιουργήσω ένα νέο digrafo (H) με κάποια νέα κόμβοι (H.setOfNodes) και μερικά τόξα νέο (H.setOfArcs). H.setOfNodes περιέχει όλα κόμβοι σε G.setOfNodes και δύο κόμβοι περισσότερα, s (ένα κόμβος προέλευσης ) και t (ένα τερματικός κόμβος ).

H.setOfArcs θα περιέχει ένα τόξο για κάθε G.setOfArcs. Αν ένα τόξο a είναι ένα G.setOfArcs και a.fromNode είναι σε bipartition.firstPart και a.toNode είναι σε bipartition.secondPart και στη συνέχεια περιλαμβάνει a σε H (προσθήκη a FlowArcDatum(1,0)).

Εάν a.fromNode είναι ένα bipartition.secondPart και a.toNode είναι ένα bipartition.firstPart, τότε συμπεριλάβετε Arc(a.toNode,a.fromNode,FlowArcDatum(1,0)) σε H.setOfArcs.

Ορισμός γραφήματος διμερισμένο διασφαλίζει ότι όχι τόξο συνδέστε οποιοδήποτε κόμβος όπου και τα δύο κόμβοι βρίσκονται στο ίδιο διαμέρισμα. H.setOfArcs περιέχει επίσης ένα τόξο από κόμβος s στον καθένα κόμβος σε bipartition.firstPart. Τέλος, H.setOfArcs περιέχει ένα τόξο καθε κόμβος σε bipartition.secondPart ένα κόμβος t. a.datum.capacity = 1 για όλους a σε H.setOfArcs.

Πρώτο διαμέρισμα του κόμβοι σε G.setOfNodes τα δύο σύνολα (part1 και part2) σε διασταση τόσο πολύ που όχι τόξο σε G.setOfArcs πηγαίνει από ένα σετ στο ίδιο σετ (αυτό το διαμέρισμα είναι δυνατό επειδή G είναι διμερισμένο ). Στη συνέχεια, προσθέστε όλα τα τόξα σε G.setOfArcs που κατευθύνονται από το part1 στο part2 προς H.setOfArcs. Στη συνέχεια, δημιουργήστε έναν μόνο κόμβο πηγή s και έναν μόνο κόμβο τερματικό t και δημιουργήστε περισσότερα τόξα

Στη συνέχεια, δημιουργήστε ένα maxFlowProblemState.

def solve_mbm( bipartition ): s = Node(uuid.uuid4(), FlowNodeDatum(0,0)) t = Node(uuid.uuid4(), FlowNodeDatum(0,0)) translate = {} arcs = frozenset([]) for a in bipartition.G.setOfArcs: if ( (a.fromNode in bipartition.firstPart) and (a.toNode in bipartition.secondPart) ): fark = Arc(a.fromNode,a.toNode,FlowArcDatum(1,0)) arcs = arcs.union({fark}) translate[frozenset({a.fromNode.uid,a.toNode.uid})] = a elif ( (a.toNode in bipartition.firstPart) and (a.fromNode in bipartition.secondPart) ): bark = Arc(a.toNode,a.fromNode,FlowArcDatum(1,0)) arcs = arcs.union({bark}) translate[frozenset({a.fromNode.uid,a.toNode.uid})] = a arcs1 = frozenset( {Arc(s,n,FlowArcDatum(1,0)) for n in bipartition.firstPart } ) arcs2 = frozenset( {Arc(n,t,FlowArcDatum(1,0)) for n in bipartition.secondPart } ) arcs = arcs.union(arcs1.union(arcs2)) nodes = frozenset( {Node(n.uid,FlowNodeDatum(0,0)) for n in bipartition.G.setOfNodes} ).union({s}).union({t}) G = DiGraph(nodes, arcs) mfp = MaxFlowProblemState(G, s.uid, t.uid, 'bipartite') result = edmonds_karp(mfp) lookup_set = {a for a in result.G.setOfArcs if (a.datum.flow > 0) and (a.fromNode.uid != s.uid) and (a.toNode.uid != t.uid)} matching = {translate[frozenset({a.fromNode.uid,a.toNode.uid})] for a in lookup_set} return matching

Ελάχιστο κάλυμμα κόμβου

Ένα κάλυμμα κόμβου σε ένα δίφθογγος G Είναι ένα σύνολο κόμβοι (cover) από G.setOfNodes τόσο πολύ για οποιοδήποτε τόξο a από G.setOfArcs Αυτό πρέπει να ισχύει: (a.fromNode en cubierta) o (a.toNode en cubierta).

Ένα ελάχιστο κάλυμμα κόμβου είναι το μικρότερο σύνολο κόμβοι στο γράφημα που είναι ακόμα α κάλυμμα κόμβου . Το θεώρημα του König το δείχνει σε ένα γράφημα διμερισμένο , το μέγεθος του μέγιστο ταίριασμα σε αυτό το γράφημα είναι ίσο με το μέγεθος του ελάχιστο κάλυμμα κόμβου , και προτείνει πώς μπορεί να ανακτηθεί το κάλυμμα κόμβου από ένα μέγιστο ταίριασμα :

Ας υποθέσουμε ότι έχουμε το διμερές bipartition και το μέγιστο ταίριασμα matching. Ορίστε ένα νέο digrafo H, H.setOfNodes=G.setOfNodes, το τόξα σε H.setOfArcs είναι η ένωση δύο σετ.

Το πρώτο σετ των τόξα a σε matching, με την αλλαγή που εάν a.fromNode in bipartition.firstPart και a.toNode en bipartition.secondPart τότε a.fromNode και a.toNode ανταλλάσσονται στο δημιουργήθηκε acro δίνουν τέτοια τόξα ένα χαρακτηριστικό a.datum.inMatching = True για να δείξει ότι προήλθαν από τόξα σε ένα σύμπτωση .

Το δεύτερο σετ είναι τόξα a ΟΧΙ στο matching, με την αλλαγή που εάν a.fromNode en bipartition.secondPart και a.toNode en bipartition.firstPart τότε a.fromNode και a.toNode ανταλλάσσονται στο τόξο δημιουργήθηκε (δίνει τέτοια τόξο ένα χαρακτηριστικό a.datum.inMatching = False).

Στη συνέχεια, εκτελέστε ένα αναζήτηση πρώτου βάθους ( DFS ) από το καθένα κόμβος n σε bipartition.firstPart που δεν είναι n == a.fromNode ούτε n == a.toNodes Για κάθε τόξο a σε matching. Κατά τη διάρκεια του DFS, μερικοί κόμβοι επισκέπτονται και άλλοι δεν είναι (αποθηκεύστε αυτές τις πληροφορίες σε πεδίο n.datum.visited). ο ελάχιστο κάλυμμα κόμβου είναι η ένωση του κόμβοι {a.fromNode para a en H.setOfArcs si ((a.datum.inMatching) y (a.fromNode.datum.visited))} και κόμβοι {a.fromNode para a en H.setOfArcs si (a.datum.inMatching) y (no a.toNode.datum.visited)}.

Αυτό μπορεί να αποδειχθεί ότι οδηγεί από ένα μέγιστο ταίριασμα σε ένα ελάχιστο κάλυμμα κόμβου από ένα απόδειξη με αντίφαση , πάρτε ένα τόξο a η οποία υποτίθεται ότι δεν καλύπτεται και εξετάζει και τις τέσσερις περιπτώσεις σχετικά με το εάν a.fromNode και a.toNode ανήκουν (είτε ως toNode ή fromNode) σε οποιοδήποτε τόξο σε σύμπτωση matching. Κάθε περίπτωση οδηγεί σε αντίφαση λόγω της σειράς με την οποία επισκέπτεται το DFS κόμβοι και το γεγονός ότι matching είναι ένα μέγιστη αντιστοίχιση .

Ας υποθέσουμε ότι έχουμε μια συνάρτηση για την εκτέλεση αυτών των βημάτων και την επιστροφή του συνόλου κόμβοι που περιλαμβάνει το ελάχιστο κάλυμμα κόμβου όταν του δίνεται digrafo G, και το μέγιστο ταίριασμα matching:

ArcMatchingDatum = coll.namedtuple('ArcMatchingDatum', ['inMatching' ]) NodeMatchingDatum = coll.namedtuple('NodeMatchingDatum', ['visited']) def dfs_helper(snodes, G): sniter, do_stop = iter(snodes), False visited, visited_uids = set(), set() while(not do_stop): try: stack = [ next(sniter) ] while len(stack) > 0: curr = stack.pop() if curr.uid not in visited_uids: visited = visited.union( frozenset( {Node(curr.uid, NodeMatchingDatum(False))} ) ) visited_uids = visited.union(frozenset({curr.uid})) succ = frozenset({a.toNode for a in G.setOfArcs if a.fromNode == curr}) stack.extend( succ.difference( frozenset(visited) ) ) except StopIteration as e: stack, do_stop = [], True return visited def get_min_node_cover(matching, bipartition): nodes = frozenset( { Node(n.uid, NodeMatchingDatum(False)) for n in bipartition.G.setOfNodes} ) G = DiGraph(nodes, None) charcs = frozenset( {a for a in matching if ( (a.fromNode in bipartition.firstPart) and (a.toNode in bipartition.secondPart) )} ) arcs0 = frozenset( { Arc(find_node_by_uid(a.toNode.uid,G), find_node_by_uid(a.fromNode.uid,G), ArcMatchingDatum(True) ) for a in charcs } ) arcs1 = frozenset( { Arc(find_node_by_uid(a.fromNode.uid,G), find_node_by_uid(a.toNode.uid,G), ArcMatchingDatum(True) ) for a in matching.difference(charcs) } ) not_matching = bipartition.G.setOfArcs.difference( matching ) charcs = frozenset( {a for a in not_matching if ( (a.fromNode in bipartition.secondPart) and (a.toNode in bipartition.firstPart) )} ) arcs2 = frozenset( { Arc(find_node_by_uid(a.toNode.uid,G), find_node_by_uid(a.fromNode.uid,G), ArcMatchingDatum(False) ) for a in charcs } ) arcs3 = frozenset( { Arc(find_node_by_uid(a.fromNode.uid,G), find_node_by_uid(a.toNode.uid,G), ArcMatchingDatum(False) ) for a in not_matching.difference(charcs) } ) arcs = arcs0.union(arcs1.union(arcs2.union(arcs3))) G = DiGraph(nodes, arcs) bip = Bipartition({find_node_by_uid(n.uid,G) for n in bipartition.firstPart},{find_node_by_uid(n.uid,G) for n in bipartition.secondPart},G) match_from_nodes = frozenset({find_node_by_uid(a.fromNode.uid,G) for a in matching}) match_to_nodes = frozenset({find_node_by_uid(a.toNode.uid,G) for a in matching}) snodes = bip.firstPart.difference(match_from_nodes).difference(match_to_nodes) visited_nodes = dfs_helper(snodes, bip.G) not_visited_nodes = bip.G.setOfNodes.difference(visited_nodes) H = DiGraph(visited_nodes.union(not_visited_nodes), arcs) cover1 = frozenset( {a.fromNode for a in H.setOfArcs if ( (a.datum.inMatching) and (a.fromNode.datum.visited) ) } ) cover2 = frozenset( {a.fromNode for a in H.setOfArcs if ( (a.datum.inMatching) and (not a.toNode.datum.visited) ) } ) min_cover_nodes = cover1.union(cover2) true_min_cover_nodes = frozenset({find_node_by_uid(n.uid, bipartition.G) for n in min_cover_nodes}) return min_cover_nodes

Το πρόβλημα γραμμικής ανάθεσης

Το πρόβλημα γραμμικής ανάθεσης συνίσταται στην εύρεση μιας μέγιστης σύμπτωσης βαρών σε ένα σταθμισμένο διμερές γράφημα.

Προβλήματα όπως αυτό που εμφανίζεται στην αρχή αυτής της ανάρτησης μπορούν να εκφραστούν ως πρόβλημα γραμμική ανάθεση . Λαμβάνοντας υπόψη ένα σύνολο εργαζομένων, ένα σύνολο εργασιών και μια συνάρτηση που δείχνει την κερδοφορία μιας ανάθεσης ενός εργαζομένου σε μια εργασία, θέλουμε να μεγιστοποιήσουμε το άθροισμα όλων των εργασιών που κάνουμε. αυτό είναι ένα πρόβλημα γραμμικής ανάθεσης .

Ας υποθέσουμε ότι ο αριθμός των εργασιών και των εργαζομένων είναι ίσος, αν και θα δείξω ότι αυτή η υπόθεση είναι εύκολο να αφαιρεθεί. Κατά την εφαρμογή, εκπροσωπώ βάρη τόξου με ένα χαρακτηριστικό a.datum.weight για ένα τόξο a.

WeightArcDatum = namedtuple('WeightArcDatum', [weight])

Αλγόριθμος Kuhn-Munkres

Ο αλγόριθμος Kuhn-Munkres λύνει το πρόβλημα γραμμικής ανάθεσης . Μια καλή εφαρμογή μπορεί να διαρκέσει χρόνο O(N^{4}), (όπου N είναι ο αριθμός των κόμβοι στο digrafo αντιπροσωπεύει το πρόβλημα). Μια εφαρμογή που είναι πιο εύκολο να εξηγηθεί χρειάζεται O (N ^ {5}) (για μια έκδοση που αναγεννάται διάγραμμα ) και O (N ^ {4}) για (για μια έκδοση που διατηρεί το διάγραμμα ). Αυτό είναι παρόμοιο με τις δύο διαφορετικές υλοποιήσεις του αλγορίθμου Έντμοντς-Καρπ .

Για αυτήν την περιγραφή, δουλεύω μόνο με πλήρη διμερή γραφικά (εκείνα όπου το ήμισυ του κόμβοι βρίσκονται σε ένα μέρος του διμερές και το άλλο μισό στο δεύτερο μέρος). Στον εργαζόμενο, το κίνητρο εργασίας σημαίνει ότι υπάρχουν τόσοι εργαζόμενοι όσο και καθήκοντα.

Αυτό φαίνεται σαν μια σημαντική κατάσταση (τι γίνεται αν αυτά τα σύνολα δεν είναι ίδια;), αλλά είναι εύκολο να διορθώσετε αυτό το πρόβλημα. Μιλώ για το πώς να το κάνω στην τελευταία ενότητα.

Η έκδοση του αλγορίθμου που περιγράφεται εδώ χρησιμοποιεί τη χρήσιμη ιδέα του μηδέν βάρος τόξα . Δυστυχώς, αυτή η έννοια έχει νόημα μόνο όταν λύνουμε ένα σμικροποίηση (Αν αντί να μεγιστοποιήσουμε τα οφέλη των εργασιών εργαζομένων μας, ελαχιστοποιούμε το κόστος αυτών των εργασιών).

η διαπραγματευτική δύναμη των αγοραστών

Ευτυχώς, είναι εύκολο να μετατρέψετε ένα μέγιστο πρόβλημα γραμμικής ανάθεσης σε ένα ελάχιστο πρόβλημα γραμμικής ανάθεσης καθιέρωση καθενός από τα βάρη του τόξο a σε M-a.datum.weight όπου M=max({a.datum.weight for a in G.setOfArcs}). Η λύση στο πρόβλημα μεγιστοποίησης Το πρωτότυπο θα είναι ίδιο με τη λύση ελαχιστοποιώντας το πρόβλημα μετά την αλλαγή των βαρών του Arc . Έτσι, για τα υπόλοιπα, ας υποθέσουμε ότι κάνουμε αυτήν την αλλαγή.

ο Αλγόριθμος Kuhn-Munkres λύσει ελάχιστο βάρος βάρους σε ένα σταθμισμένο διάγραμμα δύο διαμερισμάτων με μια ακολουθία του μέγιστες αντιστοιχίες σε γραφικά χωρίς βάρος διμερής. Αν βρούμε ένα τέλειο ταίρι στο αναπαράσταση του διαγράμματος του προβλήματος γραμμικής ανάθεσης και εάν το βάρος του καθενός τόξο στο σύμπτωση είναι μηδέν, οπότε βρήκαμε το ελάχιστο βάρος βάρους αφού αυτή η σύμπτωση υποδηλώνει ότι όλα κόμβοι στο διάγραμμα υπήρξαν ζευγαρωμένος Για τόξο με το χαμηλότερο δυνατό κόστος (κανένα κόστος δεν μπορεί να είναι μικρότερο από 0, βάσει προηγούμενων ορισμών).

Καμία άλλη τόξα μπορεί να προστεθεί στο σύμπτωση (Από όλα κόμβοι έχουν ήδη αντιστοιχιστεί) και όχι τόξα πρέπει να αφαιρεθεί από το συμπίπτων γιατί οποιαδήποτε πιθανή αντικατάσταση τόξο θα έχει τουλάχιστον τόσο μεγάλη αξία βάρους.

Αν βρούμε ένα μέγιστο ταίριασμα απο subgrafo από G περιέχει μόνο μηδέν βάρος τόξα , και δεν είναι τέλειο ταίρι , δεν έχουμε πλήρη λύση (από τότε ο συνδυασμός Δεν είναι τέλειος ). Ωστόσο, μπορούμε να παράγουμε ένα νέο digrafo H αλλάζοντας τα βάρη του τόξα σε G.setOfArcs έτσι ώστε να εμφανιστεί νέο 0-βάρος τόξα και η βέλτιστη λύση του 'Η' είναι η ίδια με τη βέλτιστη λύση του 'G'. Δεδομένου ότι εγγυόμαστε ότι τουλάχιστον ένα μηδενικό βάρος τόξο συμβαίνει σε κάθε επανάληψη, εγγυόμαστε ότι θα φτάσουμε σε ένα τέλειο ταίρι σε όχι περισσότερο από | G.setOfNodes | 2 = N 2 σε τέτοιες επαναλήψεις.

Ας υποθέσουμε ότι στο διμερές bipartition, bipartition.firstPart περιέχει κόμβοι που εκπροσωπούν τους εργαζομένους και bipartition.secondPart Αντιπροσωπεύει κόμβοι που ταυτόχρονα αντιπροσωπεύει αποδεικτικά στοιχεία.

Ο αλγόριθμος ξεκινά δημιουργώντας ένα νέο digrafo H. H.setOfNodes = G.setOfNodes. Μερικοί τόξα σε H Αυτοί είναι κόμβοι n δημιουργήθηκε σε bipartition.firstPart. Καθε κόμβος n παράγει ένα τόξο b σε H.setOfArcs Για κάθε τόξο a σε bipartition.G.setOfArcs όπου a.fromNode = n ή a.toNode = n, b=Arc(a.fromNode, a.toNode, a.datum.weight - z) όπου z=min(x.datum.weight for x in G.setOfArcs if ( (x.fromNode == n) or (x.toNode == n) )).

Συν τόξα σε H δημιουργούνται από κόμβοι n σε bipartition.secondPart. Κάθε ένα από αυτά κόμβοι n παράγει ένα τόξο b σε H.setOfArcs Για κάθε τόξο a σε bipartition.G.setOfArcs όπου a.fromNode = n ή a.toNode = n, b=Arc(a.fromNode, a.toNode, ArcWeightDatum(a.datum.weight - z)) όπου z=min(x.datum.weight for x in G.setOfArcs if ( (x.fromNode == n) or (x.toNode == n) )).

KMA: Στη συνέχεια, σχηματίστε ένα νέο digrafo K αποτελείται μόνο από το μηδέν βάρος τόξα του H, και του κόμβοι περιστατικό σε αυτούς τόξα . Έντυπο a bipartition στο κόμβοι στο K, στη συνέχεια χρησιμοποιήστε solve_mbm( bipartition ) Για να πάρετε ένα μέγιστος συνδυασμός (matching) σε K. Εάν matching είναι ένα τέλειος συνδυασμός σε H (ο τόξα σε matching Αυτοί είναι περιστατικό σε όλα τα κόμβοι σε H.setOfNodes) μετά matching είναι η βέλτιστη λύση για πρόβλημα γραμμικής ανάθεσης .

Διαφορετικά, εάν matching Δεν είναι τέλειος , δημιουργεί το ελάχιστο κάλυμμα κόμβου από K χρησιμοποιώντας node_cover = get_min_node_cover(matching, bipartition(K)). Στη συνέχεια ορίστε z=min({a.datum.weight for a in H.setOfArcs if a not in node_cover}). Ορίστε nodes = H.setOfNodes, arcs1 = {Arc(a.fromNode,a.toNode,ArcWeightDatum(a.datum.weigh-z)) for a in H.setOfArcs if ( (a.fromNode not in node_cover) and (a.toNode not in node_cover)}, arcs2 = {Arc(a.fromNode,a.toNode,ArcWeightDatum(a.datum.weigh)) for a in H.setOfArcs if ( (a.fromNode not in node_cover) != (a.toNode not in node_cover)}, arcs3 = {Arc(a.fromNode,a.toNode,ArcWeightDatum(a.datum.weigh+z)) for a in H.setOfArcs if ( (a.fromNode in node_cover) and (a.toNode in node_cover)}. Το != σύμβολο στην παραπάνω έκφραση ενεργεί ως χειριστής XOR . Τότε arcs = arcs1.union(arcs2.union(arcs3)). Τότε H=DiGraph(nodes,arcs). Επιστροφή στην επωνυμία ΚΜΑ . Ο αλγόριθμος συνεχίζεται έως το a τέλειος συνδυασμός . Είναι συνδυασμός είναι επίσης η λύση για πρόβλημα γραμμικής ανάθεσης .

def kuhn_munkres( bipartition ): nodes = bipartition.G.setOfNodes arcs = frozenset({}) for n in bipartition.firstPart: z = min( {x.datum.weight for x in bipartition.G.setOfArcs if ( (x.fromNode == n) or (x.toNode == n) )} ) arcs = arcs.union( frozenset({Arc(a.fromNode, a.toNode, ArcWeightDatum(a.datum.weight - z)) }) ) for n in bipartition.secondPart: z = min( {x.datum.weight for x in bipartition.G.setOfArcs if ( (x.fromNode == n) or (x.toNode == n) )} ) arcs = arcs.union( frozenset({Arc(a.fromNode, a.toNode, ArcWeightDatum(a.datum.weight - z)) }) ) H = DiGraph(nodes, arcs) assignment, value = dict({}), 0 not_done = True while( not_done ): zwarcs = frozenset( {a for a in H.setOfArcs if a.datum.weight == 0} ) znodes = frozenset( {n.fromNode for n in zwarcs} ).union( frozenset( {n.toNode for n in zwarcs} ) ) K = DiGraph(znodes, zwarcs) k_bipartition = bipartition(K) matching = solve_mbm( k_bipartition ) mnodes = frozenset({a.fromNode for a in matching}).union(frozenset({a.toNode for a in matching})) if( len(mnodes) == len(H.setOfNodes) ): for a in matching: assignment[ a.fromNode.uid ] = a.toNode.uid value = sum({a.datum.weight for a in matching}) not_done = False else: node_cover = get_min_node_cover(matching, bipartition(K)) z = min( frozenset( {a.datum.weight for a in H.setOfArcs if a not in node_cover} ) ) nodes = H.setOfNodes arcs1 = frozenset( {Arc(a.fromNode,a.toNode,ArcWeightDatum(a.datum.weigh-z)) for a in H.setOfArcs if ( (a.fromNode not in node_cover) and (a.toNode not in node_cover)} ) arcs2 = frozenset( {Arc(a.fromNode,a.toNode,ArcWeightDatum(a.datum.weigh)) for a in H.setOfArcs if ( (a.fromNode not in node_cover) != (a.toNode not in node_cover)} ) arcs3 = frozenset( {Arc(a.fromNode,a.toNode,ArcWeightDatum(a.datum.weigh+z)) for a in H.setOfArcs if ( (a.fromNode in node_cover) and (a.toNode in node_cover)} ) arcs = arcs1.union(arcs2.union(arcs3)) H = DiGraph(nodes,arcs) return value, assignment

Αυτή η εφαρμογή είναι O(N^{5}) γιατί δημιουργεί ένα νέο μέγιστος συνδυασμός matching σε κάθε επανάληψη · παρόμοια με τις προηγούμενες δύο υλοποιήσεις του Έντμοντς-Καρπ Αυτός ο αλγόριθμος μπορεί να τροποποιηθεί για να παρακολουθεί τον αγώνα και να τον προσαρμόζει έξυπνα σε κάθε επανάληψη. Όταν γίνει αυτό, η πολυπλοκότητα γίνεται O(N^{4}). Μια πιο προηγμένη και νεότερη έκδοση αυτού του αλγορίθμου (που απαιτεί κάποιες πιο προηγμένες δομές δεδομένων) μπορεί να εκτελεστεί στο O (N ^ {3}). Λεπτομέρειες σχετικά με την απλούστερη εφαρμογή παραπάνω και την πιο προηγμένη εφαρμογή μπορείτε να βρείτε στη διεύθυνση αυτήν την ανάρτηση που παρακίνησε αυτό το ιστολόγιο.

Καμία από τις εργασίες στα βάρη του τόξο τροποποιεί την τελική ανάθεση που επιστρέφεται από τον αλγόριθμο. Επομένως: Δεδομένου ότι τα γραφήματα εισόδου μας είναι πάντα πλήρη γραφικά δύο διαμερισμάτων , μια λύση πρέπει να αντιστοιχεί σε κάθε κόμβος σε ένα διαμέρισμα στο άλλο κόμβος στο δεύτερο διαμέρισμα, μέσω του τόξο μεταξύ αυτών των δύο κόμβοι . Σημειώστε ότι οι εργασίες που εκτελούνται στα βάρη του τόξο Ποτέ μην αλλάζετε τη σειρά (κατά παραγγελία κατά βάρος) του τόξα συμβάντα χωρίς κόμβο ιδιαιτερος .

Επομένως, όταν ο αλγόριθμος τελειώνει σε ένα τέλειο και πλήρες ταίριασμα bi-partition , καθε κόμβος έχει ανατεθεί ένα μηδενικό τόξο , δεδομένου ότι η σχετική σειρά του τόξα από τι κόμβος δεν έχει αλλάξει κατά τη διάρκεια του αλγορίθμου και από το α μηδενικό βάρος τόξο είναι το τόξο φθηνότερη δυνατή και το τέλειο πλήρες διμερές συμπλήρωμα εγγυάται ότι υπάρχει τόξο για κάθε κόμβος . Αυτό σημαίνει ότι η παραγόμενη λύση είναι στην πραγματικότητα η ίδια με την προβληματική λύση αρχική γραμμική χαρτογράφηση χωρίς καμία τροποποίηση των βαρών του τόξο .

Μη ισορροπημένες κατανομές

Φαίνεται ότι ο αλγόριθμος είναι αρκετά περιορισμένος, καθώς όπως περιγράφεται, λειτουργεί μόνο σε πλήρη διμερή γραφικά (εκείνοι όπου το ήμισυ του κόμβοι βρίσκονται σε ένα μέρος του διμερές και το άλλο μισό στο δεύτερο μέρος). Στον εργαζόμενο, το κίνητρο της εργασίας σημαίνει ότι υπάρχουν τόσοι εργαζόμενοι όσο και καθήκοντα (φαίνεται αρκετά περιοριστικό).

Ωστόσο, υπάρχει ένας εύκολος μετασχηματισμός που καταργεί αυτόν τον περιορισμό. Ας υποθέσουμε ότι υπάρχουν λιγότεροι εργαζόμενοι από τα καθήκοντα, προσθέτουμε μερικούς πλαστά εργαζομένους (αρκετά για να δημιουργήσουμε το γράφημα που προκύπτει α πλήρες διμερές γραφικό ). Κάθε πλασματικός εργαζόμενος έχει τόξο κατευθύνεται από τον εργαζόμενο σε κάθε ένα από τα καθήκοντα. Κάθε ένα από αυτά τόξο έχει βάρος 0 (η τοποθέτηση σε αγώνα δεν δίνει κανένα πρόσθετο όφελος). Μετά από αυτήν την αλλαγή, το γράφημα είναι ένα πλήρες διμερές γραφικό που μπορούμε να λύσουμε. Δεν ξεκινά καμία εργασία που έχει ανατεθεί σε έναν πλασματικό εργαζόμενο.

Ας υποθέσουμε ότι υπάρχουν περισσότερες εργασίες από τους εργαζόμενους. Προσθέσαμε μερικές πλαστές εργασίες (αρκετά για να δημιουργήσουμε το γράφημα που προκύπτει α πλήρες γράφημα δύο διαμερισμάτων ). Κάθε φανταστική εργασία έχει ένα τόξο κατευθύνεται από κάθε εργαζόμενο στο φανταστικό έργο. Κάθε ένα από αυτά τόξο έχει βάρος 0 (η τοποθέτηση σε αγώνα δεν δίνει κανένα πρόσθετο όφελος). Μετά από αυτήν την αλλαγή, το γράφημα είναι ένα πλήρες γράφημα δύο διαμερισμάτων που μπορούμε να λύσουμε. Κάθε εργαζόμενος που έχει ανατεθεί στην πλασματική εργασία δεν απασχολείται κατά τη διάρκεια της περιόδου.

Παράδειγμα γραμμικής ανάθεσης

Τέλος, θα κάνουμε ένα παράδειγμα με τον κωδικό που έχω χρησιμοποιήσει. Θα τροποποιήσω το παράδειγμα του προβλήματος από εδώ . Έχουμε 3 εργασίες: χρειαζόμαστε για να καθαρίσετε το μπάνιο , σκουπίζω το πάτωμα Γ καθάρισε τα παράθυρα .

Οι διαθέσιμοι εργαζόμενοι είναι Αλίκη , Βαρίδι , Κάρολος Υ Ντιάν . Κάθε ένας από τους εργαζόμενους μας δίνει τον μισθό που απαιτείται ανά εργασία. Εδώ είναι οι μισθοί ανά εργαζόμενο:

Καθάρισε την τουαλέτα Σκουπίζω το πάτωμα Καθάρισε τα παράθυρα
Αλίκη 2 $ 3 $ 3 $
Βαρίδι 3 $ 2 $ 3 $
Κάρολος 3 $ 3 $ 2 $
Ντιάν 9 $ 9 $ 1 $

Εάν θέλουμε να πληρώσουμε το ελάχιστο χρηματικό ποσό, αλλά ακόμα να ολοκληρώσουμε όλες τις εργασίες, ποιος πρέπει να κάνει τι έργο; Ξεκινήστε εισάγοντας μια πλαστή εργασία για το διάγραμμα που αντιπροσωπεύει το διμερές πρόβλημα.

Καθάρισε την τουαλέτα Σκουπίζω το πάτωμα Καθάρισε τα παράθυρα Μην κάνεις τίποτα
Αλίκη 2 $ 3 $ 3 $ 0 $
Βαρίδι 3 $ 2 $ 3 $ 0 $
Κάρολος 3 $ 3 $ 2 $ 0 $
Ντιάν 9 $ 9 $ 1 $ 0 $

Υποθέτοντας ότι το πρόβλημα κωδικοποιείται σε ένα digrafo , τότε kuhn_munkres( bipartition(G) ) θα λύσει το πρόβλημα και θα επιστρέψει την ανάθεση. Είναι εύκολο να επαληθευτεί ότι η βέλτιστη κατανομή (χαμηλότερο κόστος) θα κοστίσει 5 $.

Ακολουθεί μια οπτικοποίηση της λύσης που δημιουργεί ο παραπάνω κώδικας:

Οθόνη μέγιστης ροής

Οθόνη Peak Flow (Υπόλοιπο)

Αυτό είναι όλο . Τώρα γνωρίζετε όλα όσα πρέπει να γνωρίζετε για το πρόβλημα γραμμικής ανάθεσης.

Μπορείτε να βρείτε όλο τον κωδικό για αυτό το άρθρο στη διεύθυνση GitHub .

Δημιουργία αντιδραστικών εφαρμογών με Redux, RxJS και Redux-παρατηρήσιμα στο React Native

Κινητό

Δημιουργία αντιδραστικών εφαρμογών με Redux, RxJS και Redux-παρατηρήσιμα στο React Native
Design News - Καινοτομία από όλο τον κόσμο

Design News - Καινοτομία από όλο τον κόσμο

Σχεδιασμός Ux

Δημοφιλείς Αναρτήσεις
Πώς η μηχανική εκμάθηση μπορεί να βελτιώσει την ασφάλεια στον κυβερνοχώρο για αυτόνομα αυτοκίνητα
Πώς η μηχανική εκμάθηση μπορεί να βελτιώσει την ασφάλεια στον κυβερνοχώρο για αυτόνομα αυτοκίνητα
Αυτοματισμός σεληνίου: Μοντέλο αντικειμένου σελίδας και εργοστάσιο σελίδας
Αυτοματισμός σεληνίου: Μοντέλο αντικειμένου σελίδας και εργοστάσιο σελίδας
Μηχανικός Frontfor-end Salesforce
Μηχανικός Frontfor-end Salesforce
Ομιλίες σχεδιασμού: Έρευνα σε δράση με την ερευνητή UX Caitria O'Neill
Ομιλίες σχεδιασμού: Έρευνα σε δράση με την ερευνητή UX Caitria O'Neill
Τι είναι η πρόβλεψη πωλήσεων;
Τι είναι η πρόβλεψη πωλήσεων;
 
Στην υπεράσπιση των θηλυκών μηχανικών
Στην υπεράσπιση των θηλυκών μηχανικών
Ανάπτυξη εφαρμογών ιστού για κινητά: Πότε, Γιατί και Πώς
Ανάπτυξη εφαρμογών ιστού για κινητά: Πότε, Γιατί και Πώς
Αποτελεσματικές επικοινωνιακές στρατηγικές για σχεδιαστές
Αποτελεσματικές επικοινωνιακές στρατηγικές για σχεδιαστές
Τρόπος διεξαγωγής δοκιμών ευχρηστίας σε έξι βήματα
Τρόπος διεξαγωγής δοκιμών ευχρηστίας σε έξι βήματα
Ένας πλήρης οδηγός για τη δοκιμή React Hooks
Ένας πλήρης οδηγός για τη δοκιμή React Hooks
Δημοφιλείς Αναρτήσεις
  • αρχιτέκτονας λύσεων υπηρεσιών web της Amazon
  • s corp c corp llc σύγκριση
  • πώς να γράψετε θήκες δοκιμών junit για εφαρμογή ελατηρίου
  • σημείο πώλησης για android
  • Παράδειγμα εφαρμογής web Spring mvc
Κατηγορίες
  • Το Μέλλον Της Εργασίας
  • Πίσω Μέρος
  • Ζωή Σχεδιαστών
  • Διαχείριση Έργου
  • © 2022 | Ολα Τα Δικαιώματα Διατηρούνται

    portaldacalheta.pt