portaldacalheta.pt
  • Põhiline
  • Veebi Kasutajaliides
  • Ui Disain
  • Andmeteadus Ja Andmebaasid
  • Vilgas
Andmeteadus Ja Andmebaasid

Tippvool ja lineaarse määramise probleem



Siin on probleem: teie ettevõte määrab töövõtjad lepingute täitmiseks. Vaatate oma nimekirju ja otsustate, millised töövõtjad on kuu pikkuse lepingu jaoks saadaval, ja vaatate oma olemasolevad lepingud üle, et näha, millised on kuu pikkuste ülesannete jaoks. Arvestades, et teate, kui tõhusalt saavad kõik töövõtjad iga lepingut täita, kuidas määrata töövõtjad selle kuu üldise efektiivsuse maksimeerimiseks?

See on näide määramisprobleemist ja probleemi saab lahendada klassikaga Ungari algoritm .



Kahepoolne kokkulangevus

Ungari algoritm (tuntud ka kui Kuhn-Munkresi algoritm) on polünoomiaja algoritm, mis maksimeerib kaalu kaalu kahepoolsel graafil. Siin saab töövõtjaid ja lepinguid modelleerida kahepoolse graafikuna, kusjuures nende tõhusus on töövõtja ja lepingu sõlmede vaheliste servade kaal.



Selles artiklis leiate teavet Ungari algoritmi rakenduse kohta, mis kasutab Edmonds-Karpi algoritm lineaarse määramise ülesande lahendamiseks. Samuti saate teada, kuidas Edmonds-Karpi algoritm on modifikatsiooni väike modifikatsioon Ford-Fulkerson ja selle muudatuse olulisus.

Tippvoolu probleem

Tippvooluprobleemi iseenesest võib mitteametlikult kirjeldada kui osa vedeliku või gaasi liikumist torustike võrgu kaudu ühest allikast ühte klemmi. Seda tehakse eeldusel, et rõhk võrgus on piisav, et tagada vedeliku või gaasi püsimine toru või toruliitmiku mis tahes pikkuses (kohtades, kus toru erinevad pikkused kokku puutuvad).



Graafikus teatud muudatusi tehes võib eraldamisprobleemist saada voolu tippprobleem.

Eeltööd

Nende probleemide lahendamiseks vajalikud ideed tekivad paljudel matemaatika- ja inseneridistsipliinidel, sageli on sarnased mõisted tuntud erinevate nimedega ja väljendatud erineval viisil (näiteks külgnemismaatriksid ja külgnevusloendid). Kuna need ideed on üsna esoteerilised, tehakse valikuid selle kohta, kuidas neid mõisteid üldjuhul mis tahes seadetes määratletakse.



See artikkel ei eelda eelteadmisi peale lühikese sissejuhatava hulga teooria.

Selles postituses olevad teostused kujutavad probleeme suunatud graafidena (diggraaf).



Digrafo

A digrafo on kaks atribuudid , setOfNodes Y setOfArcs . Mõlemad atribuudid on komplektid (räpased kogud). Selle postituse koodiplokkides kasutan Pythoni frozenset , kuid see detail pole eriti oluline.

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

(Märkus. See ja kõik muud selle artikli koodid on kirjutatud Python 3.6-s.)



Sõlmed

A sõlm n See koosneb kahest atribuudist:

  • n.uid: A Unikaalne identifikaator .



    See tähendab, et mis tahes kahe sõlme puhul x ja y,

x.uid != y.uid
  • n.datum: See tähistab mis tahes andmeobjekti.
Node = namedtuple('Node', ['uid','datum'])

Kaar

A kaar a See koosneb kolmest atribuudist:

  • a.fromNode: See on a sõlm , nagu eespool määratletud.

  • a.toNode: See on a sõlm , nagu eespool määratletud.

  • a.datum: See tähistab mis tahes andmeobjekti.

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

Komplekt vibud kell joonistus tähistab binaarsuhet sõlmed kell joonistus . - olemasolu kaar a tähendab, et a.fromNode vahel on seos ja a.toNode.

Suunatud graafis (erinevalt suunamata graafist) seose olemasolu a.fromNode vahel ja a.toNode ei vihjata sellele, et sarnane suhe a.toNode vahel ja a.fromNode olemas.

kapitali eelarvestamise diskonteeritud rahavoogude meetodid keskenduvad

Seda seetõttu, et suunamata graafil ei ole väljendatud seos tingimata sümmeetriline.

Numbrid

Sõlmed Y vibud saab kasutada a määratlemiseks joonistus , kuid mugavuse huvides järgmistes algoritmides a joonistus kasutades a sõnastik .

Siin on meetod, mille abil saab ülaltoodud graafilise kujutise teisendada sarnaseks sõnastiku esituseks on :

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

Ja siin on veel üks, mis muudab selle sõnastike sõnaraamatuks, mis on veel üks kasulik toiming:

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

Kui artiklis räägitakse a joonistus kuna sõnastik esindab seda, mainib ta G_as_dict viitamiseks.

Mõnikord aitab see a sõlm aasta joonistus G kuni teie uid (Kordumatu identifikaator):

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

Graafika tuvastamisel kasutavad inimesed mõnikord termineid sõlm ja tipp viitavad samale mõistele; sama öeldakse ka terminite kohta kaar ja serv.

Pythonis on kaks graafilist esitust, on mis kasutab sõnastikke ja muud mis kasutab objekte graafika esitamiseks. Esindus selles postituses jääb nende kahe ühise esituse vahele.

See on minu esindus a joonistus . Selliseid on palju, kuid see on minu oma.

Teed ja rajad

Olgu S_Arcs olema a järjestus lõplik (tellitud kogu) vibud sees joonistus G nii palju, et kui a on ükskõik milline kaar aastal S_Arcs välja arvatud viimane ja b järgneb a jadas, siis peab olema a sõlm n = a.fromNode aastal G.setOfNodes kui a.toNode = b.fromNode.

Alustades esimesest kaar aastal S_Arcs ja jätkub viimaseni kaar aastal S_Arcs, kogub (leitud järjekorras) kõik sõlmed n nagu me neid kahe eespool defineerime vibud järjestikune S_Arcs Märkige tellitud kollektsioon sõlmed selle toimingu käigus kogutud 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
  • Kui mõni sõlm ilmub järjestuses S_Nodes rohkem kui üks kord siis helistage S_Arcs a Tee kõrval joonistus G.

  • Muul juhul helistage S_Arcs a kaudu kohta list(S_Nodes)[0] a list(S_Nodes)[-1] kell joonistus G.

Allikasõlm

Helistama sõlm s a allikasõlm aastal joonistus G kui s asub G.setOfNodes ja G.setOfArcs tal pole kaar a kui 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

Terminalisõlm

Helistama sõlm t a terminali sõlm kell joonistus G kui t asub G.setOfNodes ja G.setOfArcs tal pole kaar a kui 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

A lõigatud cut aasta joonistus G ühendatud on alamhulk alates vibud alates G.setOfArcs mis osa komplekt sõlmed G.setOfNodes aastal G G on ühendatud, kui kumbki sõlm n aastal G.setOfNodes on vähemalt üks kaar a aastal G.setOfArcs kui n = a.fromNode või n = a.toNode, kuid mitte a.fromNode != a.toNode.

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

Eespool toodud määratlus viitab jaotise vibud , kuid saate määratleda ka partitsiooni sõlmed kohta G.setOfNodes

Funktsioonide jaoks predecessors_of ja successors_of, n on sõlm komplektis G.setOfNodes alates joonistus G ja cut on lõigatud 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

Olgu cut Ole a lõigatud selle joonistus 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 on lõigatud selle joonistus G kui: (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 nimetatakse a x-y lõigatud kui (x in get_first_part(cut, G)) y (y in get_second_part(cut, G) ) y (x != y). Kui sõlm x sees x-y lõigatud cut on allikasõlm ja sõlm y kell x-y lõigatud on terminali sõlm , siis see lõigatud nimetatakse a s-t lõigatud .

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

Vooluvõrgud

Võite kasutada a joonistus G vooluvõrgu esindamiseks.

Määrake kumbki sõlm n, kus n asub G.setOfNodes a n.datum mis on FlowNodeDatum

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

Määrake kumbki kaar a, kus a on a G.setOfArcs ja a.datum mis on FlowArcDatum.

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

flowNodeDatum.flowIn ja flowNodeDatum.flowOut Nemad on reaalarvud positiivne .

flowArcDatum.capacity ja flowArcDatum.flow need on ka positiivsed reaalarvud.

aws-lahenduste arhitekti praktikaeksam

Iga sõlme jaoks sõlm n sisse 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})

The Silmapilt G esindab nüüd vooluvõrk .

The voolama kohta G viitab voolule a.flow kõigi jaoks vibud a aastal G

Teostatavad voogud

Las ta laseb joonistus G esindama a vooluvõrk .

The vooluvõrk mida esindab G omama teostatavad voogud jah:

  1. Igaühele sõlm n aastal G.setOfNodes väljaarvatud allikasõlmed Y terminali sõlmed : n.datum.flowIn = n.datum.flowOut.

  2. Igaühele kaar a aastal G.setOfNodes: a.datum.capacity <= a.datum.flow.

Tingimust 1 nimetatakse kaitse piiramine .

Tingimust 2 nimetatakse võimsuse piiramine .

Lõikevõime

The lõikevõime aasta s-t lõigatud stCut koos allikasõlm s ja a terminali sõlm t aasta vooluvõrk mida esindab a joonistus G see on:

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

Minimaalne võimsuse vähendamine

Olgu stCut = stCut(s,t,cut) Ole a s-t lõigatud aasta vooluvõrk mida esindab a joonistus G.

stCut kas ta on minimaalne lõikevõime selle vooluvõrk mida esindab G kui muud pole s-t lõigatud stCutCompetitor selles vooluvõrk kui:

cut_capacity(stCut, G)

Voogude eemaldamine

Tahaksin viidata joonistus mis oleks a võtmise tulemus joonistus G ja kustutage kõik vooandmed kõigilt sõlmed aastal G.setOfNodes ja ka kõik vibud aastal 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)

Tippvoolu probleem

A vooluvõrk esindatud kui joonistus G, a allikasõlm s aastal G.setOfNodes ja a terminali sõlm t aastal G.setOfNodes, G võib esindada a tippvoolu probleem jah:

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

Märgi see esitus:

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

Kus sourceNodeUid = s.uid, terminalNodeUid = t.uid ja maxFlowProblemStateUid on probleemeksemplari identifikaator.

Peak Flow lahendus

Olgu maxFlowProblem esindama a tippvoolu probleem . Probleemi lahendus maxFlowProblem saab tähistada a vooluvõrk esindatud kui joonistus H.

The Silmapilt H on lahendus teostatav tema jaoks tippvoolu probleem sisendis python maxFlowProblem jah:

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

  2. H on vooluvõrk ja on teostatavad voogud .

Kui lisaks punktidele 1 ja 2:

  1. Muud ei saa olla vooluvõrk mida esindab joonistus K kui strip_flows(G) == strip_flows(K) ja find_node_by_uid(t.uid,G).flowIn .

Siis H see on ka lahendus optimaalne jaoks maxFlowProblem

Teisisõnu, a teostatav tippvoolulahus saab esitada a-ga joonistus , et:

  1. See on identne joonistus G vastavatest tippvoolu probleem välja arvatud voog n.datum.flowIn, n.datum.flowOut ja voog a.datum.flow mis tahes sõlmed Y vibud need võivad olla erinevad.

  2. Esindab a vooluvõrk mis sellel viga on teostatavad voogud .

Ja see võib tähendada a optimaalne tippvoolulahus kui lisaks:

  1. flowIn tema jaoks sõlm mis vastab terminali sõlm kell tippvoolu probleem on võimalikult suur (kui tingimused 1 ja 2 on endiselt täidetud).

Jah joonistus H tähistab a tippvoolulahus : find_node_by_uid(s.uid,H).flowOut = find_node_by_uid(t.uid,H).flowIn see tuleneb maksimaalne vooluhulk, minimaalse nihke teoreem (arutatakse allpool). Mitteametlikult, kuna H omama elujõulised voolud see tähendab, et voolama ei saa 'luua' (välja arvatud allikasõlm s) ega 'hävitatud' (välja arvatud terminali sõlm t) ületades ühtegi (muud) sõlm ( kaitse piirangud ).

Kuna a tippvoolu probleem sisaldab ainult ühte allikasõlme s ja ühe terminali sõlm t, iga voog, mis on 'loodud' s -s tuleb hävitada t Laine vooluvõrk ei omama teostatavad voogud ( kaitse kitsendamine oli vägistatud).

Las ta laseb joonistus H esindama a teostatav tippvoolulahus ; ülaltoodud väärtust nimetatakse Voolu väärtus s-t kohta H

See lubab:

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

See tähendab, et mfps on õigusjärglane , mis tähendab, et maxFlowProblem on täpselt nagu mfps välja arvatud see, et maxFlowProblem väärtused kaaride jaoks a.flow aastal a võib erineda maxFlowProblem.setOfArcs -st kaaride jaoks a.flow aastal a

mfps.setOfArcs

See on 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 kuva koos selle mfps seotud. Iga kaar maxFlowProb pildil on silt, need sildid on a, igaüks sõlm a.datum.flowFrom/a.datum.flowTo pildil on silt ja need sildid on n.

Voolu tippnäidik

Nihke vool

Olgu n.uid {n.datum.flowIn / a.datum.flowOut} kujutavad probleemseisundit mfps ja laseme MaxFlowProblemState esindama a lõigatud kohta stCut The nihkevool mfps.G on määratletud järgmiselt:

stCut

s-t vooluhulk on partitsiooni sisaldava sektsiooni voogude summa allikasõlm partitsiooni sisaldava partitsiooni jaoks terminali sõlm miinus partitsiooni sisaldava sektsiooni voogude summa terminali sõlm partitsiooni sisaldava partitsiooni jaoks allikasõlm .

Maksimaalne vooluhulk, minimaalne nihkejõud

Mis 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 esindama a tippvoolu probleem ja et lahendus maxFlowProblem on tähistatud a vooluvõrk esindatud kui joonistus maxFlowProblem.

Olgu H minimaalne lõikevõime selle vooluvõrk mida esindab minStCut.

Sest voolus tippvool voog pärineb ühest allikasõlm ja lõpeb ühes sõlmes terminal ja tänu võimsuse piirangud ja kaitse piirangud , me teame, et kogu voog siseneb maxFlowProblem.G peab ületama ükskõik millise s-t lõigatud , eriti peab see ületama maxFlowProblem.terminalNodeUid. See tähendab:

minStCut

Maksimaalse voolu probleemi lahendus

Põhiidee lahendada a tippvoolu probleem get_flow_value(H, maxFlowProblem) <= cut_capacity(minStCut, maxFlowProblem.G) on alustada a tippvoolulahus mida esindab joonistus maxFlowProblem. Selline lähtepunkt võib olla H.

Seejärel on ülesandeks kasutada H = strip_flows(maxFlowProblem.G) ja mõne poolt ahne väärtuste muutmine H mõnest vibud a.datum.flow aastal a teise tootmiseks tippvoolulahus mida esindab joonistus H.setOfArcs nii et K ei saa veel esindada a vooluvõrk koos elujõulised voolud ja K. Niikaua kui see protsess jätkub, on toote kvaliteet (get_flow_value(H, maxFlowProblem) ) tippvoolulahus (get_flow_value(K, maxFlowProblem)) leidis hiljuti paremini kui ükski teine tippvoolulahus see on leitud. Kui protsess jõuab punkti, kus ta teab, et edasine parendamine pole võimalik, võib protsess lõpetada ja tagastab selle tippvoolulahus optimaalne .

Ülaltoodud kirjeldus on üldine ja jätab välja paljud testid, näiteks kui selline protsess on võimalik või kui kaua see võib aega võtta, annan veel mõned üksikasjad ja algoritmi.

Maksimaalse voolu teoreem, minimaalne nihkumine

Raamatust Vood võrkudes de Ford y Fulkerson , deklaratsioon Maksimaalse voolu teoreem, minimaalne nihkumine (Lause 5.1) on:

Mis tahes võrgu puhul maksimaalne voo väärtus K a s on võrdne kõigi t eraldavate lõikude minimaalse lõikevõimega ja s.

Selle postituse määratlusi kasutades tähendab see järgmist:

Lahendus a t mida esindab a vooluvõrk esindatud kui joonistus maxFlowProblem see on optimaalne jah:

H

Mulle meeldib see test teoreemi ja Vikipeedias on muud .

The maksimaalse voolu, minimaalse nihke teoreem kasutatakse dokumendi õigsuse ja täielikkuse demonstreerimiseks Ford-Fulkersoni meetod .

Annan teoreemi kohta tõendi ka järgnevas osas suurendamise viisid .

Ford-Fulkersoni meetod ja Edmonds-Karpi algoritm

CLRS määratlege Ford-Fulkersoni meetod (punkt 26.2) järgmiselt:

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

Jääkgraafik

The Jääkgraafik aasta vooluvõrk esindatud kui joonistus 'G' saab esitada a-na joonistus 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_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) naase agg_n_to_u_cap(n,u,G_as_dict) summa juurde jaoks vibud a.datum.capacity alamhulgas kus ta kaar G.setOfArcs on alamhulgas, kui a ja a.fromNode = n.

    milles linux on kirjutatud
  • a.toNode = u tagastab agg_n_to_u_cap(n,u,G_as_dict) summa kõigi jaoks vibud a.datum.flow alamhulgas kus ta kaar G.setOfArcs on alamhulgas, kui a ja a.fromNode = n.

Lühidalt öeldes jääkgraafik a.toNode = u tähistab teatud toiminguid, mida saab joonistus 'G'.

Iga paar sõlmed G_f aastal n, u selle vooluvõrk mida esindab joonistus G.setOfNodes võib genereerida 0,1 või 2 vibud kell jääkgraafik G 'G'.

  1. Paar G_f ei genereeri vibud aastal n, u kui seda pole kaar G_f aastal a nagu G.setOfArcs ja a.fromNode = n.

  2. Paar a.toNode = u genereerib kaar n, u aastal a kus G_f.setOfArcs tähistab a kaar märgistatud kui impulssvoog alates a a n u kui a = Arco (n, u, datum = ResidualNode (n_to_u_cap_sum - n_to_u_flow_sum)).

  3. Paar n_to_u_cap_sum> n_to_u_flow_sum genereerib kaar n, u aastal a kus G_f.setOfArcs tähistab a kaar märgistatud kui tõmba voolukaar kohta a a n u kui a = Arco (n, u, datum = ResidualNode (n_to_u_cap_sum - n_to_u_flow_sum)).

  • Iga tõukevoolukaar aastal n_to_u_flow_sum> 0.0 tähistab voo kogusumma G_f.setOfArcs lisamise toimingut kuni vibud x <= n_to_u_cap_sum - n_to_u_flow_sum alamhulgas kus ta kaar G.setOfArcs on alamhulgas, kui a ja a.fromNode = n.

  • Iga veojõu voolukaar aastal a.toNode = u tähistab kogu voo lahutamise toimingut G_f.setOfArcs kuni vibud x <= n_to_u_flow_sum alamhulgas kus kaar G.setOfArcs on alamhulgas, kui a ja a.fromNode = n.

Sooritage individuaalne toiming nagu Lükake või ikka alates a.toNode = u aastal vibud kohaldatav G_f võiks genereerida a vooluvõrk ilma teostatavad voogud sest võimsuse piirangud või kaitse piirangud saaks vägistada loodud voogude võrk .

Siin on jääkgraafik eelmisest a kuvamise näitest tippvoolulahus , kuvatakse igaüks kaar G tähistab a.

Maksimaalse voo kuvamine (jääk)

Suurenev rada

Olgu a.residualCapacity Ole a tippvoolu probleem ja laske maxFlowProblem olema jääkgraafik kohta G_f = get_residual_graph_of (G)

A tee üles maxFlowProblem.G jaoks augmentingPath on ükskõik milline kaudu alates maxFlowProblem a find_node_by_uid(maxFlowProblem.sourceNode,G_f).

Selgub, et a suurendamise tee find_node_by_uid(maxFlowProblem.terminalNode,G_f) saab rakendada a tippvoolulahus mida esindab joonistus augmentingPath teise genereerimine tippvoolulahus mida esindab joonistus H kus K kui get_flow_value (H, maxFlowProblem) See ei ole optimaalne .

Siin näete, kuidas:

H

Eespool nimetatutega 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 on hälbe tolerants ümardada voolu väärtused võrgus. Seda selleks, et vältida kaskaadi ujukomaarvutuste ebatäpsus . Nii näiteks kasutasin kümneni ümardamiseks sõna 'TOL = 10' olulised numbrid .

Lahkuge TOL, seejärel K = augment (augmentingPath, H) tähistab a teostatav tippvoolulahus jaoks K Et väide oleks tõene, peab vooluvõrk mida esindab maxFlowProblem oleks pidanud teostatavad voogud (mitte rikkuda võimsuse piiramine Laine kaitse piiramine . Selle põhjus on järgmine: ülaltoodud meetodil igaüks sõlm lisati uuele vooluvõrk mida esindab joonistus K on a täpne koopia sõlm alates joonistus K või a sõlm H mille teie n -le on lisatud sama number nagu teie n.datum.flowIn. See tähendab, et kaitse piiramine see on täidetud tähes „K” seni, kuni see on täidetud tähes „H”. The kaitse piiramine on rahul, kuna kontrollime sõnaselgelt, kas mõni neist on olemas kaar uus n.datum.flowOut võrgus olete a; seega seni, kuni vibud komplekti a.datum.flow <= a.datum.capacity mis kopeeriti muutmata kujul H.setOfArcs ei riku võimsuse piiramine , siis K.setOfArcs ei riku võimsuse piiramine .

Samuti on tõsi, et K kui get_flow_value (H, maxFlowProblem) See ei ole optimaalne .

Sellepärast: a marsruudi suurenemine H on olemas joonistus esindus jääkgraafik augmentingPath aasta tippvoolu probleem G_f siis viimane kaar maxFlowProblem aastal a peab olema a kaar 'Push' ja sellel peab olema augmentingPath. A suurendamise tee on määratletud kui üks lõpp terminali sõlm selle tippvoolu probleem mille jaoks on a suurendamise tee . Definitsioonist jääkgraafik , on selge, et viimane kaar sees suurendamise viis selles jääkgraafik peab olema a kaar 'Push', sest ükskõik kaar 'Tõmba' a.toNode == maxFlowProblem.terminalNode aastal suurendamise viis on b ja b.toNode == maxFlowProblem.terminalNode määratlusest kaudu . Lisaks määratlusest kaudu , on selge, et terminali sõlm Seda muudetakse meetodi b.fromNode! = maxFlowProblem.terminalNode abil ainult üks kord. Niisiis augment muuda augment täpselt üks kord ja suurendab maxFlowProblem.terminalNode.flowIn väärtust sest viimane kaar aastal maxFlowProblem.terminalNode.flowIn see peab olema Tema kaar mis põhjustab modifikatsiooni augmentingPath ajal maxFlowProblem.terminalNode.flowIn. augment Definitsioonist, kuna see kehtib vibud 'Push', augment seda saab ainult suurendada, mitte vähendada.

Mõned Sedgewicki ja Wayne'i tõendid

Raamat Algoritmid, Robert Sedgewichi ja Kevin Wayne'i neljas väljaanne on mõned suurepärased lühikesed testid (lk 892–894), mis on kasulikud. Loon need siin uuesti, ehkki kasutan ülaltoodud määratlustele vastavat keelt. Minu sildid testide jaoks on samad, mis Sedgewicki raamatus.

Ettepanek E: Iga joonistus maxFlowProblem.terminalNode.flowIn mis tähistab a teostatav tippvoolulahus veel tippvoolu probleem H mis tahes maxFlowProblem stCut.

Test: Lahku get_stcut_flow(stCut,H,maxFlowProblem) = get_flow_value(H, maxFlowProblem). Ettepanek E on stCut=stCut(maxFlowProblem.sourceNode,maxFlowProblem.terminalNode,set([a for a in H.setOfArcs if a.toNode == maxFlowProblem.terminalNode])) otse määratlusest vooluhulk s-t . Oletame, et tahame liikuda a sõlm stCut partitsioonist s (n) ja partitsioonil get_first_part(stCut.cut, G), selleks peame muutma (get_second_part(stCut.cut, G)), mis võib muutuda stCut.cut ja kehtetuks tunnistama ettepanek E . Vaatame siiski, kuidas stcut_flow = get_stcut_flow(stCut,H,maxFlowProblem) väärtus see muutub, kui me selle muudatuse teeme. The sõlm stcut_flow on tasakaalus, mis tähendab, et voo summa in sõlm n on võrdne selle voo summaga (see on vajalik selleks, et n tähistaks a teostatav lahendus ).

Pange tähele, et kogu voog, mis on osa H sisse tulemas sõlm stcut_flow sisestab selle sektsioonist s (sisenev voog sõlm n kuna partitsioon t teeb seda otseselt või kaudselt, siis ei oleks seda arvestatud väärtusesse n kuna see läheb definitsiooni põhjal vales suunas). Pange tähele, et kogu voog, mis on osa stcut_flow mis siseneb sõlm stcut_flow sisestab selle sektsioonist s (vool, mis siseneb sõlm n otseselt või kaudselt partitsioonist t ei ole väärtuses n arvesse võetud kuna liigute definitsiooni põhjal vales suunas).

Samuti kogu vool, mis kustub stcut_flow voolab lõpuks (otseselt või kaudselt) terminali sõlm (ülaltoodud). Kui me liigutame sõlm n sektsioonis t kogu vool, mis siseneb n partitsioonist s tuleb lisada uuele väärtusele n; aga kogu vool, mis kustub stcut_flow tuleb uuest väärtusest lahutada n; lahutatakse otse voo osa, mis läheb otse sektsiooni t, kuna see voog on nüüd uue sektsiooni t sisemine ja seda ei loeta stcut_flow

Voolu osa asendist stcut_flow aastal sõlmed sektsioonist s tuleb lahutada ka n: pärast stcut_flow liigub sektsiooni t, suunatakse need voogud sektsiooni s asendist t ja seetõttu ei tohiks neid n -s arvesse võtta, kuna need voogud kõrvaldavad sisendi sektsioonis s ja neid tuleb vähendada neid voogusid ja väljavoolu sektsioonist s sektsiooni -t (kus kõik voog st peavad lõppema) tuleb sama palju vähendada. Nagu sõlm stcut_flow oli enne protsessi tasakaalus, lisab värskendus uuele väärtusele sama väärtuse n lahutades lahkub ettepanek E pärast täiendamist selge. Programmi kehtivus ettepanek E siis jätkame sektsiooni t suuruse induktsioonis.

Siin on mõned näited vooluvõrgud aidata visualiseerida vähem ilmseid juhtumeid, kus ettepanek E see hoiab; Pildil tähistavad punased alad s partitsiooni, sinised alad t partitsiooni ja vibud roheline tähistab a lõika s-t **. Teisel pildil vool ** sõlme vahel Kuni ja sõlm B suureneb sissevoolu ajal terminali sõlm t ei muutu.

Järeldus: Puudub väärtus nihkevool s-t võib ületada s-t lõigatud .

Lause F. (maksimaalne vooluhulk ja minimaalse lõikelause): Olgu stcut_flow olla vool s-t . Järgmised 3 tingimust on samaväärsed:

  1. Seal on lõik s-t mille võimsus on võrdne voolu väärtusega f

  2. f on tippvool .

  3. Pole suurendamise viis seoses f.

Tingimus 1 tähendab kaasnevat seisundit 2. Tingimus 2 viitab tingimusele 3, kuna suureneva tee olemasolu tähendab kõrgemate väärtustega voo olemasolu, mis on vastuolus maksimaalse f väärtusega. Tingimus 3 tähendab tingimust 1: olgu f kõigi komplekt sõlmed kuhu pääseb C_s koos marsruudi suurenemine kell jääkgraafik . Olgu s ülejäänud vibud , siis C_t peab olema t (meie oletuse järgi). The vibud see rist alates C_t a C_s siis moodustage a s-t lõigatud sisaldavad ainult vibud C_t kus a või a.datum.capacity = a.datum.flow. Kui ei, siis sõlmed ühendatud a kaar järelejäänud jääkmahtuga a a.datum.flow = 0.0 oleks komplektis C_s sellest ajast peale oleks olemas a raja kasv alates C_s veel sõlm . Vool läbi s-t lõigatud võrdub võimsusega s-t lõigatud (alates vibud kohta s a C_s vooluhulk on võrdne läbilaskevõimega) ja ka väärtusega voog s-t (kõrval ettepanek E ).

See lause lause max vooluhulk, min lõigatud tähendab ülaltoodud avaldust Vood võrkudes .

Järeldus (terviklikkuse omadus): Kui mahtuvus on täisarv, on maksimaalse täisarvu voog ja Ford-Fulkersoni algoritm leiab selle.

looge oma erc20 märk

Test: iga kord suurendamise viis suurendab voogu positiivse täisarvu võrra, mis on kaarides kasutamata mahtude miinimum suruma ja voolab võlvides veojõud , mis kõik on alati positiivsed täisarvud.

See õigustab meetodi kirjeldust Ford-Fulkerson alates CLRS . Meetod on jätkata leidmist marsruutide suurenemine ja rakendades C_t viimaseni augment paremate lahenduste leidmine, kuni enam pole marsruudi suurenemine , mis tähendab, et viimane tippvoolulahus see on optimaalne .

De Ford-Fulkerson ja Edmonds-Karp

Ülejäänud küsimused seoses tippvoolu probleemid Nemad on:

  1. Kuidas neid peaks ehitama suurendamise teed ?

  2. Kas meetod lõpeb, kui kasutame reaalarvusid, mitte täisarvusid?

  3. Kui kaua võtab see aega (kui see lõpeb)?

The Edmonds-Karpi algoritm täpsustage, et igaüks suurendamise tee on ehitatud a amplituudi otsing ( BFS ) selle jääkgraafik ; Selgub, et see punktist 1 pärinev otsus sunnib ka algoritmi lõpetama (punkt 2) ja võimaldab asümptootiline aeg ja ruumi keerukus tuleb kindlaks teha.

Esiteks, siin on rakendus BFS :

maxFlowSolution

Kasutasin a deque pythoni kogumismoodulist .

Küsimusele 2 vastamiseks kavatsen parafraseerida veel ühe tõendi selle kohta Sedgewick ja Wayne : Ettepanek G. Nende arv kasvuteed vajalik algoritmis Edmonds-Karp sõlmedega 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 Y vibud on maksimaalselt N. Test: iga kord suurendamise tee on kaelakaar kaar - a kaar mis on kustutatud jääkgraafik kuna see vastab hästi kaarele suruma mis on täidetud mahuni või kaareni veojõud mille kaudu vool muutub 0. Iga kord a kaar saab kitsaskohaks kaar , mis tahes pikkus suurendamise tee selle kaudu peaks suurenema 2 korda. Seda seetõttu, et kumbki sõlm sees tee võivad ilmuda ainult üks kord või üldse mitte (määratlusest tee ) Kuna marsruutidel uuritakse kõige lühemalt tee kogu see tähendab, et vähemalt üks sõlm pluss peab läbi pudelikaela külastama järgmist marsruuti sõlm mis tähendab täiendavat 2 vibud teel enne saabumist sõlm . Kuna juurdekasvu tee on maksimaalne pikkus NA/2, iga kaar võib olla maksimaalselt N , juurdekasvu teed, ja koguarv juurdekasvuteed on maksimaalselt N/2.

The Edmonds-Karpi algoritm töötab NA/2. Jah, kõige rohkem viise O(NA^2) uuritakse algoritmi käigus ja uuritakse igaüks neist trajektoor koos BFS on NA/2 siis toote kõige olulisem termin ja seetõttu asümptootiline keerukus N+A

Olgu O(NA^2) olgu see mfp.

maxFlowProblemState

Vanem versioon on ebaefektiivne ja keerukam kui 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 kuna see ehitab uue tippvoolulahus ja uus jääkgraafik iga kord (selle asemel, et muuta olemasolevad joonised algoritmi edenedes). Lahenduse leidmiseks O(NA^2) tõsi, algoritm peab säilitama mõlemad digrafo mis tähistab voolu tippvigade olek ja sellega seotud ** jääkgraafik . Seetõttu peaks algoritm vältima asjatut ** kaare kordamist Y sõlmed ja värskendage nende väärtusi ja nendega seotud väärtusi jääkgraafik ainult vajadusel.

Algoritmi kirjutamiseks Edmonds Karp kiiremini, kirjutasin mitu ülaltoodud koodijuppi ümber. Loodan läbida kood, mis uue tekitas digrafo See oli abiks toimuva mõistmisel. Kiires algoritmis kasutan mõningaid uusi Pythoni trikke ja andmestruktuure, mida ma ei soovi üksikasjalikult kirjeldada. Ma ütlen, et O(NA^2) ja a.fromNode käsitletakse nüüd stringide ja uididena sõlmed . Selle koodi jaoks olgu a.toNode olema mfps

maxFlowProblemState

Siin on visualiseering sellest, kuidas see algoritm näite lahendab vooluvõrk ülevalt. Visualiseerimine näitab samme, mis kajastuvad digrafo 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 mis esindab vooluvõrku hiljemalt ja kuidas need kajastuvad jääkgraafik selle vooluvõrgu osa. Suurenemise viisid kell jääkgraafik on näidatud punaste marsruutidena ja digrafo mis esindab probleemi komplekti sõlmed Y vibud surmaga mõjutatud suurendamise marsruut on rohelisega esile tõstetud. Mõlemal juhul toongi välja diagrammi muudetavad osad (punane või roheline) ja seejärel kuvan diagrammi pärast muudatusi (ainult must).

Maksimaalse voolu kuvamine

Maksimaalse voo kuvamine (jääk)

Siin on veel üks visualiseering selle kohta, kuidas see algoritm lahendab teistsuguse näite vooluvõrk . Pange tähele, et see kasutab reaalarvusid ja sisaldab mitut vibud samade väärtustega G ja fromNode.

Pange tähele ka seda, et kuna kaared, millel on ResidualDatum 'tõmme', võivad olla osa tõusvast teest, ei tohi vooluvõrgu skeemi_ mõjutatud sõlmed olla rajal toNode

Kahepoolne graafika

Oletame, et meil on a digrafo G!, G see on kahepoolne kui on võimalik jaotada sõlmed aastal G kahes komplektis (G.setOfNodes ja part_1) nii, et mis tahes kaar part_2 aastal a ei See võib olla tõsi et G.setOfArcs aastal a.fromNode ja part_1 aastal a.toNode Kumbagi See võib olla tõsi et part_1 aastal a.fromNode ja part_2 aastal a.toNode

Teisisõnu: part_2 see on kahepoolne kui selle saab jagada kaheks sõlmed nii, et igaüks kaar peab ühendama a sõlm komplektis sõlmele teises komplektis.

Kahepoolne testimine

Oletame, et meil on a digrafo G, tahame testida, kas see on kahepoolne . Saame seda teha G kahevärvilise tabeli ahne värv.

Esiteks peame looma uue digrafo O(|G.setOfNodes|+|G.setOfArcs|). Sellel graafikul on sama hulk sõlmed meeldib H, kuid neid on veel vibud kui G. Iga kaar G aastal a loob 2 vibud aastal G esimene kaar on identne H ja teisega kaar investeerib a direktor (a).

b = Arc(a.toNode,a.fromNode,a.datum)

Vasted ja maksimaalsed vasted

Oletame, et meil on a digrafo 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) ja G on alamhulk vibud kohta matching G.setOfArcs on sobitamine jah kahele digrafo matching ja a aastal b: matching. Teisisõnu, ei kaar sees kokkusattumus jagama a sõlm .

The kokkusattumus len(frozenset( {a.fromNode} ).union( {a.toNode} ).union( {b.fromNode} ).union( {b.toNode} )) == 4 on maksimaalne vaste, kui muud pole kokkuleppele matching aastal alt_matching nii palju, et G. Teisisõnu: len(matching) on maksimaalne vaste Kui see on pikim komplekt vibud aastal matching mis vastab endiselt kombinatsioon (lisades kõik kaar seda pole veel matšis, murrab kombinatsioon määratlus).

A maksimaalne kombinatsioon G.setOfArcs on täiuslik kombinatsioon jah igale sõlm matching aastal n seal on kaar G.setOfArcs aastal a kus matching.

Maksimaalne kaheosaline kombinatsioon

A maksimaalne kahest sektsioonist koosnev kombinatsioon on maksimaalne kombinatsioon sees digrafo a.fromNode == n or a.toNode == n Mis see on kahes jaotuses .

Kuna G see on kahes jaotuses , probleem a kahepoolne maksimaalne kombinatsioon saab teisendada a tippvoolu probleem lahendatav algoritmiga autor Edmonds-Karp ja siis maksimaalne kahepoolne sidestus saab taastada lahusest kuni tippvoolu probleem .

Olgu G olema a kahepoolne kohta bipartition

Selleks pean genereerima uue digrafo (G) mõne uue sõlmed (H) ja mõned vibud uus (H.setOfNodes). H.setOfArcs sisaldab kõiki sõlmed aastal H.setOfNodes ja kaks sõlmed veel, G.setOfNodes (a allikasõlm ) ja s (a terminali sõlm ).

t sisaldab a kaar iga H.setOfArcs kohta. Kui a kaar G.setOfArcs on a a ja G.setOfArcs asub a.fromNode ja bipartition.firstPart asub a.toNode ja seejärel sisaldab bipartition.secondPart aastal a (lisades H).

Kui FlowArcDatum(1,0) on a a.fromNode ja bipartition.secondPart on a.toNode, siis lisage bipartition.firstPart aastal Arc(a.toNode,a.fromNode,FlowArcDatum(1,0))

Diagrammi määratlemine kahes jaotuses tagab, et ei kaar ühendage ükskõik milline sõlm kus mõlemad sõlmed nad on samal sektsioonil. H.setOfArcs sisaldab ka a kaar alates sõlm H.setOfArcs igale sõlm aastal s Lõpuks bipartition.firstPart sisaldab a kaar iga sõlm aastal H.setOfArcs a sõlm bipartition.secondPart. t kõigile a.datum.capacity = 1 aastal a

Esimene jaotis sõlmed aastal H.setOfArcs kaks komplekti (G.setOfNodes ja part1) eraldatud nii palju, et ei kaar aastal part2 läheb ühest komplektist samasse komplekti (see partitsioon on võimalik, kuna G.setOfArcs on kahes jaotuses ). Seejärel lisage kõik vibud aastal G mis on suunatud G.setOfArcs -st kuni part1 part2 suunas. Seejärel looge üks sõlm allikas H.setOfArcs ja ühe sõlme terminal s ja loo rohkem vibud

Seejärel ehitage t.

maxFlowProblemState

Minimaalne sõlme kate

Sõlmekate a joonistus 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 See on komplekt sõlmed (G) pärit cover nii palju, et mis tahes kaar G.setOfNodes kohta a see peab olema tõsi: G.setOfArcs.

Minimaalne sõlmekate on väikseim komplekt sõlmed graafikul, mis on endiselt a sõlmekate . Königi teoreem näitab seda graafikul kahes jaotuses , suurus maksimaalne vaste selles graafikus on võrdne minimaalne sõlmekate ja soovitab, kuidas sõlmekatet saab a-st taastada maksimaalne vaste :

Oletame, et meil on kahepoolne (a.fromNode en cubierta) o (a.toNode en cubierta) ja maksimaalne vaste bipartition. Määratlege uus digrafo matching, H, vibud aastal H.setOfNodes=G.setOfNodes need on kahe komplekti liit.

Esimene komplekt vibud H.setOfArcs sisse a, muutusega, et kui matching ja a.fromNode in bipartition.firstPart siis a.toNode en bipartition.secondPart ja a.fromNode vahetatakse akro loodud nad annavad selliseid vibud atribuut a.toNode näidata, et need on saadud vibud sees kokkusattumus .

Teine komplekt on vibud a.datum.inMatching = True EI sees a, muutusega, et kui matching ja a.fromNode en bipartition.secondPart siis a.toNode en bipartition.firstPart ja a.fromNode vahetatakse kaar loodud (annab sellise kaar atribuut a.toNode).

Seejärel käivitage a esimene sügavusotsing ( DFS ) sõlm a.datum.inMatching = False aastal n mis ei ole bipartition.firstPart ega n == a.fromNode Iga kaar n == a.toNodes aastal a DFS-i ajal mõned sõlmed külastatakse ja teisi mitte (salvestage see teave väljale matching). The minimaalne sõlmekate on liit sõlmed n.datum.visited ja sõlmed {a.fromNode para a en H.setOfArcs si ((a.datum.inMatching) y (a.fromNode.datum.visited))} .

Seda saab näidata sõitmaks a-st maksimaalne vaste kuni a minimaalne sõlmekate poolt a tõend vastuolu abil , võtke a kaar {a.fromNode para a en H.setOfArcs si (a.datum.inMatching) y (no a.toNode.datum.visited)} mida väidetavalt ei käsitletud, ja kaaluge kõiki nelja juhtumit selle kohta, kas a ja a.fromNode kuuluvad (kas nimega a.toNode või toNode) kaar aastal kokkusattumus fromNode. Iga juhtum toob kaasa vastuolu DFS-i külastuste järjekorra tõttu sõlmed ja asjaolu, et matching on maksimaalne sobitamine .

Oletame, et meil on funktsioon nende sammude käivitamiseks ja hulga tagastamiseks sõlmed mis sisaldab minimaalne sõlmekate kui talle antakse digrafo matching ja maksimaalne vaste G:

matching

Lineaarse määramise probleem

Lineaarse määramise probleem seisneb kaalude maksimaalse kokkulangevuse leidmises kaalutud kahepoolses graafis.

Selliseid probleeme nagu see, mis ilmus selle postituse alguses, võib väljendada probleemina lineaarne määramine . Arvestades töötajate komplekti, ülesannete komplekti ja funktsiooni, mis näitab töötaja ülesandele määramise tasuvust, tahame maksimeerida kõigi tehtud ülesannete summat; see on lineaarse määramise probleem .

Oletame, et ülesannete ja töötajate arv on võrdne, kuigi ma näitan, et seda eeldust on lihtne eemaldada. Rakenduses esindan ma vibu raskused atribuudiga 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 jaoks kaar a.datum.weight.

a

Kuhni-Munkresi algoritm

Kuhni-Munkresi algoritm lahendab lineaarse määramise probleem . Hea rakendamine võib võtta aega WeightArcDatum = namedtuple('WeightArcDatum', [weight]) , (kus O(N^{4}) on arv sõlmed kell digrafo mis esindavad probleemi). Rakendus, mida on lihtsam seletada, võtab N (versiooni jaoks, mis taastub digrafos ) ja O (N ^ {5}) for (versiooni jaoks, mis hoiab digrafos ). See sarnaneb algoritmi kahe erineva rakendusega Edmonds-Karp .

Selle kirjelduse jaoks töötan ainult täieliku kaheosalise graafikaga (nendega, kus pool graafikast) sõlmed on osa kahepoolne ja teine ​​pool teises osas). Töötajas tähendab ülesande motiveerimine seda, et töötajaid on sama palju kui ülesandeid.

See tundub märkimisväärne tingimus (mis siis, kui need komplektid pole võrdsed?), Kuid seda probleemi on lihtne lahendada; Sellest, kuidas seda teha, räägin viimases osas.

Siin kirjeldatud algoritmi versioon kasutab kasulikku mõistet nullkaalu vibud . Kahjuks on sellel kontseptsioonil mõte vaid siis, kui lahendame a minimeerimine (Kui selle asemel, et maksimeerida oma töötajate tööülesannetest tulenevaid eeliseid, minimeerime hoopis selliste ülesannete maksumuse).

käitumispõhine arendus vs testipõhine arendus

Õnneks on a teisendamine lihtne maksimaalse lineaarse määramise probleem sees minimaalne lineaarse määramise probleem iga kaalu määramine kaar O (N ^ {4}) aastal a kus M-a.datum.weight. Maksimeerimisprobleemi lahendus originaal on identne lahusega pärast raskuste muutmist probleemi minimeerimine of Arc . Oletame, et ülejäänud osas teeme selle muudatuse.

The Kuhni-Munkresi algoritm lahendada miinimumkaal kaalutud kahest jaotatud tabelis järjestusega maksimaalsed vasted graafikas kaalumata kahepoolne. Kui leiame ühe täiuslik vaste aastal digrafi esitamine lineaarse määramise probleemi ja kui iga kaalu kaar aastal kokkusattumus on null, seega oleme leidnud minimaalne kaal kuna see kokkusattumus viitab sellele, et kõik sõlmed kell digrafos on olnud paaris jaoks kaar võimalikult väikeste kuludega (ükski kulu ei või varasemate määratluste põhjal olla väiksem kui 0).

Ei kedagi teist vibud saab lisada kokkusattumus (Kuna kõik sõlmed on juba paaritatud) ja ei vibud tuleb eemaldada kokkulangev sest võimalik asendamine kaar on vähemalt sama suur kaalu väärtus.

Kui leiame a maksimaalne vaste selle subgrafo kohta M=max({a.datum.weight for a in G.setOfArcs}) sisaldavad ainult nullkaalu vibud ja see pole a täiuslik vaste , meil pole täielikku lahendust (alates kombinatsioon See ei ole täiuslik ). Siiski saame toota uut digrafo G muutes kaalu vibud aastal H nii et ilmuks uus 0-kaal vibud ja 'H' optimaalne lahendus on sama mis 'G' optimaalne lahendus. Kuna me garanteerime, et vähemalt üks nullkaalu vibu esineb igas iteratsioonis, garanteerime, et jõuame a täiuslik vaste aastal mitte rohkem kui | G.setOfNodes | 2 = N 2 sellistes kordustes.

Oletame, et kahepoolne G.setOfArcs, bipartition sisaldab sõlmed töötajate esindamine ja bipartition.firstPart See esindab sõlmed mis samal ajal kujutab endast tõendeid.

Algoritm algab uue genereerimisega digrafo bipartition.secondPart. H. Mõned vibud aastal H.setOfNodes = G.setOfNodes Nemad on sõlmed H loodud n Iga sõlm bipartition.firstPart genereerib a kaar n aastal b Igaühele kaar H.setOfArcs aastal a kus bipartition.G.setOfArcs või a.fromNode = n, a.toNode = n kus b=Arc(a.fromNode, a.toNode, a.datum.weight - z).

Lisaks vibud aastal z=min(x.datum.weight for x in G.setOfArcs if ( (x.fromNode == n) or (x.toNode == n) )) on loodud sõlmed H aastal n Igaüks neist sõlmed bipartition.secondPart genereerib a kaar n aastal b Igaühele kaar H.setOfArcs aastal a kus bipartition.G.setOfArcs või a.fromNode = n, a.toNode = n kus b=Arc(a.fromNode, a.toNode, ArcWeightDatum(a.datum.weight - z)).

KMA: Seejärel moodustage uus digrafo z=min(x.datum.weight for x in G.setOfArcs if ( (x.fromNode == n) or (x.toNode == n) )) koosneb ainult nullkaalu vibud K ja sõlmed intsident nendes vibud . Vormi a H aastal sõlmed sisse bipartition, seejärel kasutage K Selle saamiseks maksimaalne kombinatsioon (solve_mbm( bipartition )) matching. Kui K on täiuslik kombinatsioon aastal matching ( vibud aastal H Nemad on intsident kõigis sõlmed aastal matching), siis H.setOfNodes on optimaalne lahendus lineaarse määramise probleem .

Vastasel juhul, kui matching See ei ole täiuslik , genereerib minimaalne sõlmekate kohta matching kasutades K. Seejärel määrake node_cover = get_min_node_cover(matching, bipartition(K)). Määratlege 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)} sümbol ülaltoodud avaldises toimib operaatorina XOR . Siis !=. Siis arcs = arcs1.union(arcs2.union(arcs3)). Tagasi brändi juurde KMA . Algoritm jätkub kuni a täiuslik kombinatsioon . On kombinatsioon on ka lahendus lineaarse määramise probleem .

H=DiGraph(nodes,arcs)

See rakendus on 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 sest see genereerib uue maksimaalne kombinatsioon O(N^{5}) igas iteratsioonis; sarnane kahe eelmise rakendusega Edmonds-Karp Seda algoritmi saab vaste jälgimiseks muuta ja arukalt igale iteratsioonile kohandada. Kui see on tehtud, muutub keerukus matching. Selle algoritmi täpsemat ja uuemat versiooni (mis nõuab mõnda täpsemat andmestruktuuri) saab käivitada O(N^{4}) Üksikasjad ülaltoodud lihtsama ja täpsema rakendamise kohta leiate aadressilt see postitus mis motiveeris seda blogi.

Ükski operatsioon kaaluga kaar modifitseerib algoritmi tagastatud lõpliku omistamise. Seetõttu: Kuna meie sisendgraafikud on alati täielikult jagatud graafika , peab lahendus määrama igaüks sõlm ühes partitsioonis teise sõlm teises jaotises kaar nende kahe vahel sõlmed . Pange tähele, et toimingud, mis tehakse kaaluga kaar ärge kunagi muutke vibud vahejuhtumid üheski sõlmes eriti .

Seega, kui algoritm lõpeb a täiuslik ja täielik sobiv kahesektsiooniline , iga sõlm on määratud a kaare nullkaal , kuna suhteline järjekord vibud millest sõlm ei ole algoritmi ajal muutunud ja kuna a nullkaalu vibu see on vibu võimalikult odav ja täiuslik kaheosaline täiendus tagab, et on olemas kaar igaühele sõlm . See tähendab, et loodud lahendus on tegelikult sama mis probleemilahendus algne lineaarne kaardistamine ilma kaalude muutmata kaar .

Tasakaalustamata eraldised

Näib, et algoritm on üsna piiratud, kuna kirjeldatud viisil töötab see ainult täielik kahepoolne graafika (need, kus pool sõlmed on osa kahepoolne ja teine ​​pool teises osas). Töötajas tähendab ülesande motiveerimine seda, et töötajaid on nii palju kui on ülesandeid (see tundub üsna piirav).

Siiski on olemas lihtne teisendus, mis eemaldab selle piirangu. Oletame, et töötajaid on vähem kui ülesandeid, lisame mõned näivtöötajad (piisab, et saadud graafik a täielik kahepoolne graafika ). Igal väljamõeldud töötajal on a kaar suunab töötaja igale ülesandele. Igaüks neist kaar kaal on 0 (matši panemine ei anna mingit täiendavat kasu). Pärast seda muudatust on graafik a täielik kahepoolne graafika mida saame lahendada. Fiktiivsele töötajale määratud ülesandeid ei alustata.

Oletame, et ülesandeid on rohkem kui töötajaid. Lisasime mõned näivülesanded (piisavalt, et saadud graafik a täisjagatud graafik ). Igal väljamõeldud ülesandel on a kaar suunatakse igalt tööliselt fiktiivsele ülesandele. Igaüks neist kaar selle kaal on 0 (matši panemine ei anna mingit täiendavat kasu). Pärast seda muudatust on graafik a täisjagatud graafik mida saame lahendada. Ükski väljamõeldud ülesandega töötaja ei ole selle aja jooksul tööle võetud.

Lineaarse määramise näide

Lõpuks teeme näite koodiga, mida olen kasutanud. Ma muudan probleemi näidet alates siin . Meil on 3 ülesannet: meil on vaja vannitoa puhastamiseks , põrandat pühkima , Y pese aknaid .

Saadaval töötajad on Alice , Bob , Charlie Y Diane . Iga töötaja annab meile palga, mida ta vajab ühe ülesande kohta. Siin on palgad töötaja kohta:

Puhastage vannituba Põrandat pühkima Peske Windows
Alice 2 dollarit 3 dollarit 3 dollarit
Bob 3 dollarit 2 dollarit 3 dollarit
Charlie 3 dollarit 3 dollarit 2 dollarit
Diane 9 dollarit 9 dollarit 1 dollar

Kui tahame maksta kõige vähem raha, kuid saame siiski kõik ülesanded tehtud, siis kes peaks millist ülesannet tegema? Alustage kaheosalist probleemi esindava digraafi jaoks näiv ülesande sisestamine.

Puhastage vannituba Põrandat pühkima Peske Windows Ära tee midagi
Alice 2 dollarit 3 dollarit 3 dollarit 0 dollarit
Bob 3 dollarit 2 dollarit 3 dollarit 0 dollarit
Charlie 3 dollarit 3 dollarit 2 dollarit 0 dollarit
Diane 9 dollarit 9 dollarit 1 dollar 0 dollarit

Eeldades, et probleem on kodeeritud a digrafo , siis O (N ^ {3}) lahendab probleemi ja tagastab ülesande. On lihtne kontrollida, kas optimaalne eraldamine (madalaim hind) maksab 5 dollarit.

Siin on ülaltoodud koodi genereeritud lahenduse visualiseerimine:

Voolu tippnäidik

Voolu tippnäidik (jääk)

See on kõik . Nüüd teate kõike, mida peate teadma lineaarse määramise probleemi kohta.

Kogu selle artikli koodi leiate aadressilt GitHub .

Heuristiline analüüs UX-i jaoks - kuidas käivitada kasutatavuse hindamine

Ux Disain

Heuristiline analüüs UX-i jaoks - kuidas käivitada kasutatavuse hindamine
Raspberry Pi serveri arendamiseks arendamine

Raspberry Pi serveri arendamiseks arendamine

Tehnoloogia

Lemmik Postitused
Raha kogumise pigi teki kunst
Raha kogumise pigi teki kunst
Amazon vs. Walmart: Bezos läheb kogu toiduainete omandamisega jugulaarseks
Amazon vs. Walmart: Bezos läheb kogu toiduainete omandamisega jugulaarseks
Bootstrapped: kaugettevõtte ehitamine
Bootstrapped: kaugettevõtte ehitamine
Bootstrapi kasutamine ja .NET-projektide loomine
Bootstrapi kasutamine ja .NET-projektide loomine
Kuidas luua meilisõnumite analüüsi bot: NLP-õpetus.
Kuidas luua meilisõnumite analüüsi bot: NLP-õpetus.
 
Kommunikatsioonidirektor
Kommunikatsioonidirektor
Kuidas vältida funktsioonide libisemist kasutajalugude parimate tavade abil
Kuidas vältida funktsioonide libisemist kasutajalugude parimate tavade abil
Täpsem Git-juhend: Git Stash, Reset, Rebase ja palju muud
Täpsem Git-juhend: Git Stash, Reset, Rebase ja palju muud
Disainihariduse tähtsus
Disainihariduse tähtsus
KPI-d edu saavutamiseks - ülevaade projektijuhi jõudlusmõõdikutest
KPI-d edu saavutamiseks - ülevaade projektijuhi jõudlusmõõdikutest
Lemmik Postitused
  • kus kasutatakse node js-i
  • mis on projekteerimisdokument
  • veebiapi .net-is
  • lekkinud krediitkaardid, mis töötavad
  • Gestaltpsühholoogia põhiprintsiibid
  • kasutage veebisaidi hostimiseks githubi
  • android seadme monitor pole seadet
Kategooriad
  • Veebi Kasutajaliides
  • Ui Disain
  • Andmeteadus Ja Andmebaasid
  • Vilgas
  • © 2022 | Kõik Õigused Kaitstud

    portaldacalheta.pt