portaldacalheta.pt
  • Principal
  • Rise Of Remote
  • Outils Et Tutoriels
  • Équipes Distribuées
  • Mode De Vie
Science Des Données Et Bases De Données

Débit de pointe et problème d'affectation linéaire



Voici un problème: votre entreprise affecte des entrepreneurs pour exécuter les contrats. Vous regardez vos listes et décidez quels entrepreneurs sont disponibles pour un contrat d'un mois et passez en revue vos contrats disponibles pour voir lesquels sont pour des tâches d'un mois. Étant donné que vous savez avec quelle efficacité chaque entrepreneur peut exécuter chaque contrat, comment attribuez-vous des entrepreneurs pour maximiser l'efficacité globale pour ce mois?

Ceci est un exemple du problème d'affectation, et le problème peut être résolu avec le classique Algorithme hongrois .



Coïncidence bipartite



L'algorithme hongrois (également connu sous le nom d'algorithme de Kuhn-Munkres) est un algorithme de temps polynomial, qui maximise le poids de poids sur un graphe bipartite pondéré. Ici, les entrepreneurs et les contrats peuvent être modélisés sous forme de graphique bipartite, leur efficacité étant le poids des arêtes entre l'entrepreneur et les nœuds du contrat.



Dans cet article, vous découvrirez une implémentation de l'algorithme hongrois qui utilise le Algorithme d'Edmonds-Karp pour résoudre le problème d'affectation linéaire. Vous apprendrez également comment l'algorithme d'Edmonds-Karp est une légère modification du Ford-Fulkerson et l'importance de cette modification.

Le problème du débit de pointe

Le problème du débit de pointe lui-même peut être décrit de manière informelle comme le problème du déplacement d'un fluide ou d'un gaz à travers un réseau de tuyaux d'une source unique à un seul terminal. Ceci est fait en supposant que la pression dans le réseau est suffisante pour garantir que le fluide ou le gaz ne peut rester dans aucune longueur de tuyau ou de raccord de tuyau (les endroits où se rencontrent différentes longueurs de tuyau).



En apportant certaines modifications au graphique, le problème d'allocation peut devenir un problème de débit de pointe.

Préliminaires

Les idées nécessaires pour résoudre ces problèmes surgissent dans de nombreuses disciplines mathématiques et d'ingénierie, souvent des concepts similaires sont connus sous des noms différents et exprimés de différentes manières (par exemple, matrices de contiguïté et listes de contiguïté). Puisque ces idées sont assez ésotériques, des choix sont faits sur la manière dont ces concepts seront généralement définis pour un contexte donné.



Cet article ne supposera aucune connaissance préalable au-delà d'une brève introduction à la théorie des ensembles.

Les implémentations de cet article représentent les problèmes sous forme de graphes dirigés (digraphe).



Digrafo

UNE digrafo il en a deux les attributs , setOfNodes Oui setOfArcs . Ces deux attributs sont ensembles (collections désordonnées). Dans les blocs de code de cet article, j'utilise Python frozenset , mais ce détail n'est pas particulièrement important.

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

(Remarque: ce code et tous les autres codes de cet article sont écrits en Python 3.6.)



Nœuds

UNE nœud n Il est composé de deux attributs:

  • n.uid: A Identifiant unique .



    Cela signifie que pour l'un des deux nœuds x et y,

x.uid != y.uid
  • n.datum: Ceci représente n'importe quel objet de données.
Node = namedtuple('Node', ['uid','datum'])

Arc

UNE arc a Il est composé de trois attributs:

  • a.fromNode: Ceci est un nœud , comme défini ci-dessus.

  • a.toNode: Ceci est un nœud , comme défini ci-dessus.

  • a.datum: Ceci représente n'importe quel objet de données.

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

L'ensemble des arcs dans le digraphe représente une relation binaire dans le nœuds dans le digraphe . L'existence de arc a implique qu'il existe une relation entre a.fromNode et a.toNode.

Dans un graphe orienté (par opposition à un graphe non orienté), l'existence d'une relation entre a.fromNode et a.toNode non impliquent qu'une relation similaire entre a.toNode et a.fromNode Ça existe.

comment apprendre c++ en ligne

En effet, dans un graphe non orienté, la relation exprimée n'est pas nécessairement symétrique.

Chiffres

Nœuds Oui arcs peut être utilisé pour définir un digraphe , mais par commodité, dans les algorithmes suivants, un digraphe en utilisant comme un dictionnaire .

Voici une méthode qui peut convertir la représentation graphique ci-dessus en une représentation de dictionnaire similaire à est :

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

Et en voici un autre qui le transforme en dictionnaire de dictionnaires, une autre opération qui vous sera utile:

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

Lorsque l'article parle d'un digraphe comme le dictionnaire le représente, il mentionnera G_as_dict pour référence.

Parfois, il est utile de prendre un nœud d'un digraphe G jusqu'à votre uid (Identifiant unique):

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

Lors de l'identification des graphiques, les gens utilisent parfois les termes nœud et vertex pour désigner le même concept; la même chose est dite des termes arc et bord.

Il existe deux représentations graphiques en Python, est qui utilise des dictionnaires et autre qui utilise des objets pour représenter des graphiques. La représentation dans ce post se situe entre ces deux représentations communes.

Ceci est ma représentation d'un digraphe . Il y en a beaucoup comme ça, mais c'est le mien.

Routes et pistes

Soit S_Arcs être un séquence définitif (collection ordonnée) de arcs en un digraphe G à tel point que si a est un arc dans S_Arcs sauf le dernier, et b suit a dans la séquence, alors il doit y avoir un nœud n = a.fromNode dans G.setOfNodes comme a.toNode = b.fromNode.

À partir du premier arc dans S_Arcs, et en continuant jusqu'au dernier arc dans S_Arcs, rassemble (dans l'ordre trouvé) tous nœuds n comme nous les définissons ci-dessus entre chaque deux arcs consécutive dans S_Arcs. Marquer la collection commandée de nœuds collectées lors de cette opération 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
  • Si seulement nœud apparaît plus d'une fois dans la séquence S_Nodes puis appelez S_Arcs une Chemin pour digraphe G.

  • Sinon, appelez S_Arcs ongle via de list(S_Nodes)[0] a list(S_Nodes)[-1] dans le digraphe G.

Nœud source

Appeler pour nœud s une nœud source dans digraphe G si s est dans G.setOfNodes et G.setOfArcs cela n'a pas arc a comme 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

Nœud terminal

Appeler pour nœud t une nœud terminal dans le digraphe G si t est dans G.setOfNodes et G.setOfArcs cela n'a pas arc a comme 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

UNE Couper cut d'un digraphe G connecté c'est un sous-ensemble de arcs de G.setOfArcs lequel partie l'ensemble des nœuds G.setOfNodes dans G. G est connecté si chaque nœud n dans G.setOfNodes a au moins un arc a dans G.setOfArcs comme n = a.fromNode ou n = a.toNode, mais pas a.fromNode != a.toNode.

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

La définition ci-dessus fait référence au sous-ensemble de arcs , mais vous pouvez également définir une partition du nœuds de G.setOfNodes.

Pour les fonctions predecessors_of et successors_of, n c'est un nœud dans l'ensemble G.setOfNodes de digraphe G, et cut c'est un Couper de 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

Soit cut Être un Couper du digraphe 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 c'est un Couper du digraphe G si: (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 s'appelle un coupe x-y si (x in get_first_part(cut, G)) y (y in get_second_part(cut, G) ) y (x != y). Quand il nœud x en un coupe x-y cut c'est un nœud source et le nœud y dans le coupe x-y c'est un nœud terminal , ensuite ceci Couper s'appelle un coupe s-t .

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

Réseaux de flux

Vous pouvez utiliser un digraphe G pour représenter un réseau de flux.

Attribuer chacun nœud n, où n est dans G.setOfNodes a n.datum qui est un FlowNodeDatum:

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

Attribuer chacun arc a, où a est un G.setOfArcs et a.datum qui est un FlowArcDatum.

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

flowNodeDatum.flowIn et flowNodeDatum.flowOut ils sont nombres réels positif .

flowArcDatum.capacity et flowArcDatum.flow ce sont aussi des nombres réels positifs.

javascript obtenir la date et l'heure

Pour chaque nœud nœud n dans 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})

La Digraph G représente maintenant le réseau de flux .

La couler de G fait référence au flux a.flow pour tous les arcs a dans G.

Flux réalisables

Laisse le digraphe G représenter un réseau de flux .

La réseau de flux représenté par G avoir flux réalisables Oui:

  1. Pour chaque nœud n dans G.setOfNodes à l'exception de nœuds source Oui nœuds terminaux : n.datum.flowIn = n.datum.flowOut.

  2. Pour chaque arc a dans G.setOfNodes: a.datum.capacity <= a.datum.flow.

La condition 1 est appelée confinement de conservation .

La condition 2 est appelée confinement de capacité .

Capacité de coupe

La capacité de coupe d'un coupe s-t stCut avec un nœud source s et un nœud terminal t d'une réseau de flux représenté par un digraphe G il est:

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

Coupe de capacité minimale

Soit stCut = stCut(s,t,cut) Être un coupe s-t d'une réseau de flux représenté par un digraphe G.

stCut c'est lui capacité de coupe minimale de la réseau de flux représenté par G s'il n'y en a pas d'autre coupe s-t stCutCompetitor dans celle-ci réseau de flux comme:

cut_capacity(stCut, G)

Dépouiller les flux

Je voudrais me référer à digraphe quel serait le résultat de prendre un digraphe G et effacer toutes les données de flux de tous nœuds dans G.setOfNodes et aussi tout arcs dans 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)

Problème de débit de pointe

Ongle réseau de flux représenté comme un digraphe G, a nœud source s dans G.setOfNodes et un nœud terminal t dans G.setOfNodes, G peut représenter un problème de débit de pointe Oui:

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

Marquez cette représentation:

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

Où sourceNodeUid = s.uid, terminalNodeUid = t.uid et maxFlowProblemStateUid est un identifiant pour l'instance de problème.

Solution de débit de pointe

Soit maxFlowProblem représenter un problème de débit de pointe . La solution au problème maxFlowProblem peut être représenté par un réseau de flux représenté comme un digraphe H.

La Digraph H est une solution réalisable pour lui problème de débit de pointe à l'entrée python maxFlowProblem Oui:

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

  2. H c'est une réseau de flux et a flux réalisables .

Si en plus de 1 et 2:

  1. Il ne peut y en avoir d'autre réseau de flux représenté par le digraphe K comme strip_flows(G) == strip_flows(K) et find_node_by_uid(t.uid,G).flowIn .

Alors H c'est aussi une solution optimal pour maxFlowProblem.

En d'autres termes, un solution de débit de pointe réalisable peut être représenté avec un digraphe , Quoi:

  1. C'est identique à digraphe G du correspondant problème de débit de pointe à l'exception du flux n.datum.flowIn, n.datum.flowOut et le flux a.datum.flow de l'un des nœuds Oui arcs ils peuvent être différents.

  2. Représente un réseau de flux Qu'est ce qui ne va pas avec ça flux réalisables .

Et cela peut représenter un solution optimale de débit de pointe si en plus:

  1. Le flowIn pour lui nœud qui correspond à nœud terminal dans le problème de débit de pointe est aussi grand que possible (lorsque les conditions 1 et 2 sont toujours satisfaites).

Oui digraphe H représente un solution de débit de pointe : find_node_by_uid(s.uid,H).flowOut = find_node_by_uid(t.uid,H).flowIn cela découle de la débit maximum, théorème de cisaillement minimum (discuté ci-dessous). De manière informelle, puisque H avoir flux viables cela signifie que le couler ne peut pas être `` créé '' (à l'exception du nœud source s) ni 'détruit' (à l'exception de nœud terminal t) en traversant n'importe quel (autre) nœud ( contraintes de conservation ).

Depuis un problème de débit de pointe contient un seul nœud source s et un seul nœud terminal t, chaque flux 'créé' dans s doit être 'détruit' dans t vague réseau de flux non avoir flux réalisables (la restriction de conservation avait été violée).

Laisse le digraphe H représenter un solution de débit de pointe réalisable ; la valeur ci-dessus est appelée Valeur de débit s-t de H.

Il permet:

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

Cela signifie que mfps c'est un état successeur de maxFlowProblem, ce qui signifie que mfps est exactement comme maxFlowProblem à l'exception que les valeurs de a.flow pour les arcs a dans maxFlowProblem.setOfArcs peut être différent de a.flow pour les arcs a dans 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

Ceci est un affichage d'un mfps avec ses maxFlowProb associée. Chaque arc a dans l'image, il a une étiquette, ces étiquettes sont a.datum.flowFrom/a.datum.flowTo, chacune nœud n dans l'image, il a une étiquette et ces étiquettes sont n.uid {n.datum.flowIn / a.datum.flowOut}.

Affichage du débit de pointe

Flux de cisaillement

Soit mfps représentent un état de problème MaxFlowProblemState et laissez stCut représenter un Couper de mfps.G. La écoulement de cisaillement stCut est défini comme ceci:

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

coupure de débit s-t est la somme des flux de la partition contenant le nœud source pour la partition contenant le nœud terminal moins la somme des flux de la partition contenant les nœud terminal pour la partition contenant le nœud source .

Débit maximum, cisaillement minimum

Quoi maxFlowProblem représenter un problème de débit de pointe et que la solution à maxFlowProblem est représenté par un réseau de flux représenté comme digraphe H.

Soit minStCut la capacité de coupe minimale de la réseau de flux représenté par maxFlowProblem.G.

Parce que dans le flux de débit de pointe le flux provient d'un seul nœud source et se termine par un seul nœud Terminal et, en raison de restrictions de capacité et les restrictions de conservation , on sait que tout le flux entrant maxFlowProblem.terminalNodeUid doit traverser tout coupe s-t , en particulier il doit traverser minStCut. Ça signifie:

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

Solution au problème de débit de pointe

L'idée de base pour résoudre un problème de débit de pointe maxFlowProblem est de commencer par un solution de débit de pointe représenté par digraphe H. Un tel point de départ peut être H = strip_flows(maxFlowProblem.G).

La tâche consiste alors à utiliser H et par certains glouton modification des valeurs a.datum.flow de certaines arcs a dans H.setOfArcs produire un autre solution de débit de pointe représenté par digraphe K de sorte que K ne peut pas encore représenter un réseau de flux avec flux viables et get_flow_value(H, maxFlowProblem) . Tant que ce processus se poursuit, la qualité (get_flow_value(K, maxFlowProblem)) du solution de débit de pointe (K) trouvé, récemment, meilleur que tout autre solution de débit de pointe qui a été trouvé. Si le processus atteint un point où il sait qu'aucune amélioration supplémentaire n'est possible, le processus peut se terminer et renverra le solution de débit de pointe optimum .

La description ci-dessus est générale et omet de nombreux tests tels que, si un tel processus est possible ou combien de temps cela peut prendre, je vais donner plus de détails et l'algorithme.

Le théorème d'écoulement maximum, cisaillement minimum

Du livre Flux dans les réseaux de Ford y Fulkerson , la déclaration de Théorème d'écoulement maximum, cisaillement minimum (Théorème 5.1) est:

Pour tout réseau, la valeur de débit maximale de s a t est égale à la capacité de coupe minimale de toutes les coupes qui séparent s et t.

En utilisant les définitions de cet article, cela se traduit par:

La solution à un maxFlowProblem représenté par un réseau de flux représenté comme digraphe H il est optimal Oui:

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

J'aime ce test du théorème et Wikipedia a autre .

La le théorème d'écoulement maximum, cisaillement minimum est utilisé pour démontrer l'exactitude et l'exhaustivité des Méthode Ford-Fulkerson .

Je donnerai également une preuve du théorème dans la section après moyens d'augmenter .

La méthode Ford-Fulkerson et l'algorithme Edmonds-Karp

CLRS définir la méthode Ford-Fulkerson (section 26.2) comme ceci:

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

Graphique résiduel

La Graphique résiduel d'une réseau de flux représenté comme le digraphe 'G' peut être représenté par un digraphe 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) revenir à la somme de a.datum.capacity pour les arcs dans le sous-ensemble de G.setOfArcs où il arc a est dans le sous-ensemble si a.fromNode = n et a.toNode = u.

  • agg_n_to_u_cap(n,u,G_as_dict) renvoie la somme de a.datum.flow pour tous les arcs dans le sous-ensemble de G.setOfArcs où il arc a est dans le sous-ensemble si a.fromNode = n et a.toNode = u.

En bref, le graphe résiduel G_f représente certaines actions qui peuvent être effectuées sur le digraphe «G».

Chaque paire de nœuds n, u dans G.setOfNodes de la réseau de flux représenté par digraphe G peut générer 0,1 ou 2 arcs dans le graphe résiduel G_f de «G».

  1. La paire n, u ne génère pas arcs dans G_f s'il n'y a pas arc a dans G.setOfArcs comme a.fromNode = n et a.toNode = u.

  2. La paire n, u génère le arc a dans G_f.setOfArcs où a représente un arc étiqueté comme un flux impulsionnel de n a u a = Arco (n, u, datum = ResidualNode (n_to_u_cap_sum - n_to_u_flow_sum)) si n_to_u_cap_sum> n_to_u_flow_sum.

  3. La paire n, u génère le arc a dans G_f.setOfArcs où a représente un arc étiqueté comme un tirez l'arc de flux de n a u a = Arco (n, u, datum = ResidualNode (n_to_u_cap_sum - n_to_u_flow_sum)) si n_to_u_flow_sum> 0.0.

  • Chaque arc à flux poussé dans G_f.setOfArcs représente l'action d'ajouter un total de flux x <= n_to_u_cap_sum - n_to_u_flow_sum à arcs dans le sous-ensemble de G.setOfArcs où il arc a est dans le sous-ensemble si a.fromNode = n et a.toNode = u.

  • Chaque arc de flux de traction dans G_f.setOfArcs représente l'action de soustraire un total de flux x <= n_to_u_flow_sum à arcs dans le sous-ensemble de G.setOfArcs où arc a est dans le sous-ensemble si a.fromNode = n et a.toNode = u.

Effectuer une action individuelle telle que Poussez ou encore de G_f dans les arcs applicable dans G pourrait générer un réseau de flux sans pour autant flux réalisables parce que le contraintes de capacité ou contraintes de conservation pourrait être violée dans le réseau de flux généré .

Voici une visualisation du graphe résiduel de l'exemple précédent d'affichage d'un solution de débit de pointe , dans l'affichage chaque arc a représente a.residualCapacity.

Affichage du débit maximum (résiduel)

Voie croissante

Soit maxFlowProblem Être un problème de débit de pointe , et laissez G_f = get_residual_graph_of (G) être graphe résiduel de maxFlowProblem.G.

Ongle en haut augmentingPath pour maxFlowProblem est un via de find_node_by_uid(maxFlowProblem.sourceNode,G_f) a find_node_by_uid(maxFlowProblem.terminalNode,G_f).

Il s'avère qu'un chemin d'augmentation augmentingPath peut être appliqué à un solution de débit de pointe représenté par le digraphe H générer un autre solution de débit de pointe représenté par le digraphe K où get_flow_value (H, maxFlowProblem) si H ce n'est pas optimum .

Ici vous voyez comment:

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

Dans ce qui précède, TOL est une valeur de tolérance pour arrondir les valeurs de débit dans le réseau. Ceci afin d'éviter la cascade de imprécision des calculs en virgule flottante . Par exemple, j'ai utilisé 'TOL = 10' pour arrondir à 10 chiffres significatifs .

Quitter K = augment (augmentingPath, H), puis K représente un solution de débit de pointe réalisable pour maxFlowProblem. Pour que la déclaration soit vraie, le réseau de flux représenté par K doit avoir flux réalisables (ne pas violer restriction de capacité vague restriction de conservation . Voici pourquoi: dans la méthode ci-dessus, chaque nœud ajouté au nouveau réseau de flux représenté par digraphe K est une copie exacte d'un nœud de digraphe H ou un nœud n qui a eu le même numéro ajouté à votre n.datum.flowIn comme votre n.datum.flowOut. Cela signifie que le restriction de conservation elle est accomplie en «K» tant qu'elle est accomplie en «H». La restriction de conservation est satisfait car nous vérifions explicitement que tout arc nouveau a dans le réseau, vous avez a.datum.flow <= a.datum.capacity; par conséquent, tant que le arcs de l'ensemble H.setOfArcs qui ont été copiés inchangés dans K.setOfArcs ne violez pas le restriction de capacité , alors K ne viole pas le restriction de capacité .

Il est également vrai que get_flow_value (H, maxFlowProblem) si H ce n'est pas optimum .

Voici pourquoi: pour un augmentation de l'itinéraire augmentingPath existe dans le digraphe la représentation de graphe résiduel G_f d'un problème de débit de pointe maxFlowProblem puis le dernier arc a dans augmentingPath Doit être un arc 'Push' et doit avoir a.toNode == maxFlowProblem.terminalNode. Ongle chemin d'augmentation est défini comme un se terminant par nœud terminal du problème de débit de pointe pour qui est un chemin d'augmentation . D'après la définition de graphe résiduel , il est clair que le dernier arc dans une moyen d'augmenter dans ce graphe résiduel Doit être un arc 'Push' parce que tout arc 'Tirer' b dans la moyen d'augmenter aura b.toNode == maxFlowProblem.terminalNode et b.fromNode! = maxFlowProblem.terminalNode de la définition de via . De plus, d'après la définition de via , il est clair que le nœud terminal Il n'est modifié qu'une seule fois avec la méthode augment. Donc augment modifier maxFlowProblem.terminalNode.flowIn exactement une fois et augmente la valeur de maxFlowProblem.terminalNode.flowIn parce que le dernier arc dans augmentingPath ça doit être lui arc qui provoque la modification de maxFlowProblem.terminalNode.flowIn pendant augment. D'après la définition de augment, puisqu'elle s'applique à arcs 'Pousser', le maxFlowProblem.terminalNode.flowIn il ne peut qu'être augmenté et non diminué.

Quelques preuves de Sedgewick et Wayne

Le livre Algorithmes, quatrième édition par Robert Sedgewich et Kevin Wayne a de merveilleux tests courts (pages 892-894) qui seront utiles. Je vais les recréer ici, même si j'utiliserai un langage conforme aux définitions ci-dessus. Mes étiquettes pour les tests sont les mêmes que dans le livre de Sedgewick.

Proposition E: Pour n'importe qui digraphe H qui représente un solution de débit de pointe réalisable encore problème de débit de pointe maxFlowProblem, pour tout stCut get_stcut_flow(stCut,H,maxFlowProblem) = get_flow_value(H, maxFlowProblem).

Test: Quitter stCut=stCut(maxFlowProblem.sourceNode,maxFlowProblem.terminalNode,set([a for a in H.setOfArcs if a.toNode == maxFlowProblem.terminalNode])). Proposition E est détenu par stCut directement à partir de la définition de valeur de débit s-t . Supposons que nous voulions déplacer un nœud n à partir de la partition s (get_first_part(stCut.cut, G)) et sur la partition (get_second_part(stCut.cut, G)), pour ce faire, nous devons changer stCut.cut, ce qui pourrait changer stcut_flow = get_stcut_flow(stCut,H,maxFlowProblem) et invalider proposition E . Cependant, voyons comment la valeur de stcut_flow cela changera à mesure que nous procéderons à ce changement. La nœud n est en équilibre, ce qui signifie que la somme des flux en nœud n est égal à la somme du flux de this (cela est nécessaire pour que H représente un solution réalisable ).

Notez que tout le flux qui fait partie de stcut_flow venir dans nœud n y entre à partir de la partition s (le flux entrant nœud n puisque la partition t le fait directement ou indirectement à partir de cela, elle n'aurait pas été comptée dans la valeur stcut_flow parce que ça va dans la mauvaise direction selon la définition). Notez que tout le flux qui fait partie du stcut_flow qui entre dans le nœud n y entre à partir de la partition s (le flux qui entre dans le nœud n de la partition t directement ou indirectement n'ont pas été comptés dans la valeur stcut_flow parce que vous vous dirigez dans la mauvaise direction en fonction de la définition).

Aussi, tout le flux qui sort n finira par couler (directement ou indirectement) dans le nœud terminal (démontré ci-dessus). Lorsque nous déplaçons le nœud n dans la partition t, tout le flux entrant n de la partition s doit être ajoutée à la nouvelle valeur stcut_flow; cependant, tout le flux qui sort n doit être soustraite de la nouvelle valeur stcut_flow; la partie du flux qui va directement à la partition t est soustraite, puisque ce flux est maintenant interne à la nouvelle partition t et n'est pas compté comme stcut_flow.

technologie iot et maison intelligente

La partie du flux à partir de la position n dans nœuds de la partition s doit également être soustrait de stcut_flow: Après n se déplace vers la partition t, ces flux seront dirigés à partir de la position de la partition t dans la partition s et ne doivent donc pas être pris en compte dans le stcut_flow, car ces flux éliminent l'entrée dans la partition s et doivent être réduits de la somme de ces flux et le flux sortant de la partition s vers la partition -t (où tous les flux de st doivent se terminer) doivent être réduits du même montant. Comme lui nœud n était en équilibre avant le processus, la mise à jour aura ajouté la même valeur à la nouvelle valeur stcut_flow lorsqu'il est soustrait, laissant le proposition E effacer après la mise à niveau. La validité de la proposition E puis on continue dans l'induction sur la taille de la partition t.

Voici quelques exemples de réseaux de flux pour aider à visualiser les cas les moins évidents où le proposition E il se maintient; Dans l'image, les zones rouges indiquent la partition s, les zones bleues représentent la partition t et les arcs le vert indique un couper s-t **. Dans la deuxième image, le flux entre ** node Vers et nœud B augmente tandis que le débit nœud terminal t ne change pas.

Corollaire: Aucune valeur de écoulement de cisaillement s-t peut dépasser la capacité de coupe s-t .

Proposition F. (débit maximum et théorème de coupure minimum): Soit f être un flux s-t . Les 3 conditions suivantes sont équivalentes:

  1. Il y a une coupure s-t dont la capacité est égale à la valeur du débit f.

  2. f c'est un débit de pointe .

  3. Il n'y a pas moyen d'augmenter par rapport à f.

La condition 1 implique la condition 2 par le corollaire. La condition 2 implique la condition 3 car l'existence d'un chemin croissant implique l'existence d'un flux avec des valeurs plus élevées, contredisant le maximum de «f». La condition 3 implique la condition 1: Soit C_s l'ensemble de tous nœuds accessible depuis s avec un augmentation de l'itinéraire dans le graphe résiduel . Soit C_t Les arcs restants , alors t doit être dans C_t (selon notre hypothèse). Les arcs qui traversent de C_s a C_t puis formez un coupe s-t contenant seulement arcs a où a.datum.capacity = a.datum.flow ou a.datum.flow = 0.0. Sinon, le nœuds connecté par un arc avec la capacité résiduelle restante a C_s serait dans l'ensemble C_s depuis, il y aurait un augmentation de la piste de s encore nœud . Le flux à travers le coupe s-t égale la capacité de coupe s-t (depuis le arcs de C_s a C_t ont un débit égal à la capacité) et aussi à la valeur du débit s-t (pour proposition E ).

Cette déclaration du théorème débit max, coupe min implique la déclaration ci-dessus de Flux dans les réseaux .

Corollaire (propriété d'intégralité): Lorsque les capacités sont des nombres entiers, il existe un flux de valeurs entier maximal et l'algorithme de Ford-Fulkerson le trouve.

Test: chaque moyen d'augmenter augmente le débit d'un entier positif, le minimum des capacités inutilisées dans les arcs poussez et coule dans les arcades traction , qui sont toujours des entiers positifs.

Cela justifie la description de la méthode Ford-Fulkerson de CLRS . La méthode consiste à continuer à trouver routes croissantes et appliquer augment jusqu'au dernier maxFlowSolution trouver de meilleures solutions, jusqu'à ce que plus itinéraire croissant , ce qui signifie que le dernier solution de débit de pointe il est optimum .

De Ford-Fulkerson à Edmonds-Karp

Les questions restantes concernant la résolution de problèmes de débit de pointe ils sont:

  1. Comment devraient-ils être construits chemins d'augmentation ?

  2. La méthode se terminera-t-elle si nous utilisons des nombres réels et non des nombres entiers?

  3. Combien de temps faudra-t-il pour terminer (si c'est le cas)?

La Algorithme d'Edmonds-Karp préciser que chacun chemin d'augmentation est construit par un recherche d'amplitude ( BFS ) du graphe résiduel ; Il s'avère que cette décision à partir du point 1 forcera également l'algorithme à se terminer (point 2) et permettra au temps asymptotique et complexité de l'espace être déterminé.

Tout d'abord, voici une implémentation 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

J'ai utilisé un deque du module de collections python .

Pour répondre à la question 2, je vais paraphraser une autre preuve de Sedgewick et Wayne : Proposition G. Le numéro de voies d'augmentation nécessaire dans l'algorithme Edmonds-Karp avec des nœuds N Oui arcs est au plus NA/2. Test: chaque chemin d'augmentation a un nœud de cou arc - une arc qui est supprimé de graphe résiduel car il correspond bien à un arc poussez qui est rempli à pleine capacité ou un arc traction à travers lequel le flux devient 0. Chaque fois qu'un arc devient un goulot d'étranglement arc , la longueur de tout chemin d'augmentation à travers elle devrait être multipliée par 2. En effet, chaque nœud dans une route peut apparaître une seule fois ou pas du tout (d'après la définition de route ) Depuis le itinéraires sont explorés du plus court route ce qui signifie qu'au moins un nœud plus doit être visité par l'itinéraire suivant à travers le goulot d'étranglement nœud ce qui signifie 2 supplémentaires arcs sur la route avant d'arriver dans le nœud . Depuis le chemin d'incrément est la longueur maximale N, chaque arc peut avoir un maximum de N/2 , chemins d'incrémentation, et le nombre total de chemins d'incrémentation est au plus NA/2.

La Algorithme d'Edmonds-Karp s'exécute sur O(NA^2). Oui au plus les chemins NA/2 sera exploré au cours de l'algorithme et explorer chaque trajectoire avec BFS est N+A puis le terme le plus significatif du produit et donc la complexité asymptotique O(NA^2).

Soit mfp que ce soit un 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

L'ancienne version est inefficace et a une complexité pire que O(NA^2) comme il construit un nouveau solution de débit de pointe et un nouveau graphe résiduel à chaque fois (au lieu de modifier le digraphes existants à mesure que l'algorithme progresse). Pour arriver à une solution O(NA^2) true l'algorithme doit garder à la fois le digrafo que représente le état de problème de débit de pointe et son graphe résiduel ** associé . Par conséquent, l'algorithme doit éviter de répéter inutilement ** des arcs Oui nœuds et mettre à jour leurs valeurs et les valeurs associées dans le graphe résiduel seulement lorsque cela est nécessaire.

Pour écrire un algorithme Edmonds Karp plus rapidement, j'ai réécrit plusieurs extraits de code ci-dessus. J'espère parcourir le code qui a généré un nouveau digrafo Cela a été utile pour comprendre ce qui se passe. Dans l'algorithme rapide, j'utilise de nouvelles astuces et structures de données Python que je ne veux pas détailler. Je vais dire que a.fromNode et a.toNode sont désormais traités comme des chaînes et des uids pour nœuds . Pour ce code, laissez mfps être un 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

Voici une visualisation de la façon dont cet algorithme résout l'exemple réseau de flux d'en haut. La visualisation montre les étapes reflétées dans le digrafo G représentant le réseau de flux dernier et comment ils se reflètent dans le graphe résiduel de ce réseau de flux. Les moyens d'augmenter dans le graphe résiduel sont représentés par des itinéraires rouges et les digrafo qui représente le problème l'ensemble de nœuds Oui arcs affecté par un dé la voie d'augmentation est surligné en vert. Dans chaque cas, je vais mettre en évidence les parties du graphique qui seront modifiées (rouge ou vert) puis afficher le graphique après les modifications (noir uniquement).

Affichage du débit maximum

Affichage du débit maximum (résiduel)

Voici une autre visualisation de la façon dont cet algorithme résout un exemple différent réseau de flux . Notez que cela utilise des nombres réels et contient plusieurs arcs avec les mêmes valeurs fromNode et toNode.

Notez également que, étant donné que les arcs avec un ResidualDatum 'pull' peuvent faire partie du chemin ascendant, les nœuds affectés dans le diagramme du réseau de flux ne se trouvent pas sur un chemin dans G!.

Graphiques bipartites

Supposons que nous ayons un digrafo G, G il est bipartite s'il est possible de partitionner le nœuds dans G.setOfNodes en deux ensembles (part_1 et part_2) tels que pour tout arc a dans G.setOfArcs non C'est peut-être vrai que a.fromNode dans part_1 et a.toNode dans part_1. Ni C'est peut-être vrai que a.fromNode dans part_2 et a.toNode dans part_2.

En d'autres termes, G il est bipartition s'il peut être divisé en deux ensembles de nœuds de telle manière que chacun arc doit connecter un nœud dans un ensemble à un nœud dans l'autre ensemble.

Test bipartite

Supposons que nous ayons un digrafo G, nous voulons tester si c'est bipartition . Nous pouvons le faire dans O(|G.setOfNodes|+|G.setOfArcs|) par la couleur gourmande du nuancier bicolore.

Premièrement, nous devons générer un nouveau digrafo H. Ce graphique aura le même ensemble de nœuds comme G, mais en aura plus arcs que G. Chaque arc a dans G créera 2 arcs dans H; le premier arc sera identique à a, et le second arc investit le directeur de 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)

Matchs et matchs maximum

Supposons que nous ayons un digrafo G et matching est un sous-ensemble de arcs de G.setOfArcs. matching c'est un correspondant à oui pour deux digrafo a et b dans matching: len(frozenset( {a.fromNode} ).union( {a.toNode} ).union( {b.fromNode} ).union( {b.toNode} )) == 4. En d'autres termes, non arc dans une coïncidence Partagez un nœud .

meilleures pratiques de conception de sites Web 2016

La coïncidence matching, est une correspondance maximale s'il n'y en a pas d'autre accord alt_matching dans G à tel point que len(matching) . En d'autres termes, matching c'est une correspondance maximale S'il s'agit de la plus longue série de arcs dans G.setOfArcs qui satisfait toujours à la définition de combinaison (en ajoutant tout arc qui n'est pas déjà dans le match cassera le combinaison définition).

Ongle combinaison maximale matching c'est une combinaison parfaite oui pour chacun nœud n dans G.setOfArcs il y a arc a dans matching où a.fromNode == n or a.toNode == n.

Combinaison bi-partitionnée maximale

Ongle combinaison bi-partitionnée maximale c'est une combinaison maximale en un digrafo G Qu'est que c'est bi-partitionné .

Depuis G il est bi-partitionné , le problème de trouver un combinaison maximale bipartite peut être transformé en un problème de débit de pointe résoluble avec algorithme par Edmonds-Karp et puis le couplage bipartite maximal peut être récupéré de la solution à problème de débit de pointe .

Soit bipartition être un bipartition de G.

Pour cela, j'ai besoin de générer un nouveau digrafo (H) avec de nouveaux nœuds (H.setOfNodes) et certains arcs nouveau (H.setOfArcs). H.setOfNodes contient tout nœuds dans G.setOfNodes et deux nœuds plus, s (une nœud source ) et t (une nœud terminal ).

H.setOfArcs contiendra un arc pour chaque G.setOfArcs. Si un arc a est un G.setOfArcs et a.fromNode est dans bipartition.firstPart et a.toNode est dans bipartition.secondPart puis inclut a dans H (ajout d'un FlowArcDatum(1,0)).

Si a.fromNode est un bipartition.secondPart et a.toNode est un bipartition.firstPart, alors incluez Arc(a.toNode,a.fromNode,FlowArcDatum(1,0)) dans H.setOfArcs.

Définition d'un graphique bi-partitionné garantit que non arc connecter tout nœud où les deux nœuds ils sont sur la même partition. H.setOfArcs contient également un arc de nœud s à chaque nœud dans bipartition.firstPart. Enfin, H.setOfArcs contient un arc chaque nœud dans bipartition.secondPart une nœud t. a.datum.capacity = 1 pour tous a dans H.setOfArcs.

Première partition du nœuds dans G.setOfNodes les deux ensembles (part1 et part2) séparé tellement que non arc dans G.setOfArcs passe d'un ensemble au même ensemble (cette partition est possible car G est bi-partitionné ). Puis ajoutez tous les arcs dans G.setOfArcs qui sont dirigés depuis le part1 au part2 vers H.setOfArcs. Ensuite, créez un seul nœud la source s et un seul nœud Terminal t et créez plus arcs

Puis construisez un 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

Couverture minimale des nœuds

Une couverture de nœud dans un digraphe G C'est un ensemble de nœuds (cover) à partir de G.setOfNodes tellement que pour tout arc a de G.setOfArcs cela doit être vrai: (a.fromNode en cubierta) o (a.toNode en cubierta).

Une couverture de nœud minimale est le plus petit ensemble de nœuds dans le graphique qui est toujours un couverture de nœud . Le théorème de König indique que sur un graphe bi-partitionné , la taille du correspondance maximale dans ce graphique est égal à la taille du couverture minimale des nœuds , et suggère comment la couverture de nœud peut être récupérée à partir d'un correspondance maximale :

Supposons que nous ayons le bipartition bipartition et la correspondance maximale matching. Définissez un nouveau digrafo H, H.setOfNodes=G.setOfNodes, le arcs dans H.setOfArcs ils sont l'union de deux ensembles.

Le premier ensemble de arcs a dans matching, avec le changement que si a.fromNode in bipartition.firstPart et a.toNode en bipartition.secondPart puis a.fromNode et a.toNode sont échangés dans le acro créé ils donnent un tel arcs un attribut a.datum.inMatching = True pour indiquer qu'ils proviennent de arcs dans une coïncidence .

Le deuxième ensemble est arcs a PAS dans matching, avec le changement que si a.fromNode en bipartition.secondPart et a.toNode en bipartition.firstPart puis a.fromNode et a.toNode sont échangés dans le arc créé (donne un tel arc un attribut a.datum.inMatching = False).

Puis lancez un première recherche en profondeur ( DFS ) de chaque nœud n dans bipartition.firstPart qui n'est pas n == a.fromNode ni n == a.toNodes pour n'importe qui arc a dans matching. Pendant DFS, certains nœuds sont visités et d'autres non (stockez ces informations dans un champ n.datum.visited). La couverture minimale des nœuds est l'union de nœuds {a.fromNode para a en H.setOfArcs si ((a.datum.inMatching) y (a.fromNode.datum.visited))} et nœuds {a.fromNode para a en H.setOfArcs si (a.datum.inMatching) y (no a.toNode.datum.visited)}.

Cela peut être montré pour conduire à partir d'un correspondance maximale à une couverture minimale des nœuds par un preuve par contradiction , prendre un arc a qui n'était pas censé être couvert et examinez les quatre cas pour savoir si a.fromNode et a.toNode appartiennent (soit comme toNode ou fromNode) à tout arc dans coïncidence matching. Chaque cas conduit à une contradiction en raison de l'ordre dans lequel DFS visite nœuds et le fait que matching c'est un correspondance maximale .

Supposons que nous ayons une fonction pour exécuter ces étapes et renvoyer l'ensemble de nœuds qui comprend le couverture minimale des nœuds quand il reçoit le digrafo G, et le correspondance maximale 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

Le problème d'affectation linéaire

Le problème d'affectation linéaire consiste à trouver une coïncidence maximale des poids dans un graphe biparti pondéré.

Des problèmes comme celui qui apparaît au début de cet article peuvent être exprimés comme un problème affectation linéaire . Étant donné un ensemble de travailleurs, un ensemble de tâches et une fonction qui indique la rentabilité d'une affectation d'un travailleur à une tâche, nous voulons maximiser la somme de toutes les affectations que nous effectuons; c'est un problème d'affectation linéaire .

Supposons que le nombre de tâches et de travailleurs soit égal, même si je montrerai que cette hypothèse est facile à supprimer. Dans la mise en œuvre, je représente poids d'arc avec un attribut a.datum.weight pour arc a.

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

Algorithme de Kuhn-Munkres

L'algorithme de Kuhn-Munkres résout le problème d'affectation linéaire . Une bonne implémentation peut prendre du temps O(N^{4}), (où N est le nombre de nœuds dans le digrafo représentant le problème). Une implémentation plus simple à expliquer prend O (N ^ {5}) (pour une version qui régénère digraphos ) et O (N ^ {4}) for (pour une version qui garde le digraphos ). Ceci est similaire aux deux implémentations différentes de l'algorithme Edmonds-Karp .

Pour cette description, je travaille uniquement avec des graphiques bipartites complets (ceux où la moitié des nœuds font partie de la bipartition et l'autre moitié dans la seconde partie). Chez le travailleur, la motivation des tâches signifie qu'il y a autant de travailleurs que de tâches.

Cela semble être une condition importante (et si ces ensembles ne sont pas égaux?), Mais ce problème est facile à résoudre; Je parle de la façon de le faire dans la dernière section.

La version de l'algorithme décrite ici utilise le concept utile de arcs de poids zéro . Malheureusement, ce concept n'a de sens que lorsque nous résolvons un minimisation (Si, au lieu de maximiser les avantages de nos affectations de tâches, nous minimisons plutôt le coût de ces affectations).

qu'est-ce qu'un architecte de solutions certifié aws

Heureusement, il est facile de convertir un problème d'affectation linéaire maximum en un problème d'affectation linéaire minimal établissant chacun des poids de arc a dans M-a.datum.weight où M=max({a.datum.weight for a in G.setOfArcs}). La solution au problème de maximisation l'original sera identique à la solution minimiser le problème après le changement des poids d'Arc . Donc pour le reste, supposons que nous fassions ce changement.

La Algorithme de Kuhn-Munkres résoudre poids minimum dans un graphique bi-partitionné pondéré par une séquence de matchs maximum dans les graphiques non pondéré bipartite. Si on en trouve un match parfait dans la représentation du digraphe du problème d'affectation linéaire et si le poids de chacun arc dans la coïncidence est zéro, nous avons donc trouvé le poids minimum poids puisque cette coïncidence suggère que tout nœuds dans le digraphos ils ont été joint pour un arc avec le coût le plus bas possible (aucun coût ne peut être inférieur à 0, selon les définitions précédentes).

Aucun autre arcs peut être ajouté au coïncidence (Puisque toutes nœuds sont déjà jumelés) et non arcs doit être retiré du coïncident car tout remplacement possible arc aura au moins une valeur pondérale aussi élevée.

Si nous trouvons un correspondance maximale du subgrafo de G contenant seulement arcs de poids zéro , et ce n'est pas un match parfait , nous n'avons pas de solution complète (puisque la combinaison ce n'est pas parfaite ). Cependant, nous pouvons produire un nouveau digrafo H changer les poids de arcs dans G.setOfArcs pour que le nouveau poids 0 apparaisse arcs et la solution optimale de 'H' est la même que la solution optimale de 'G'. Puisque nous garantissons qu'au moins un arc poids zéro se produit à chaque itération, nous garantissons que nous atteindrons un match parfait en pas plus de | G.setOfNodes | 2 = N 2 dans de telles itérations.

Supposons que dans le bipartition bipartition, bipartition.firstPart contient nœuds représentant les travailleurs, et bipartition.secondPart Cela représente nœuds qui en même temps représente une preuve.

L'algorithme commence par générer un nouveau digrafo H. H.setOfNodes = G.setOfNodes. Certains arcs dans H ils sont nœuds n généré dans bipartition.firstPart. Chaque nœud n génère un arc b dans H.setOfArcs pour chaque arc a dans bipartition.G.setOfArcs où a.fromNode = n ou a.toNode = n, b=Arc(a.fromNode, a.toNode, a.datum.weight - z) où z=min(x.datum.weight for x in G.setOfArcs if ( (x.fromNode == n) or (x.toNode == n) )).

Plus arcs dans H sont générés par nœuds n dans bipartition.secondPart. Chacun de ces nœuds n génère un arc b dans H.setOfArcs pour chaque arc a dans bipartition.G.setOfArcs où a.fromNode = n ou a.toNode = n, b=Arc(a.fromNode, a.toNode, ArcWeightDatum(a.datum.weight - z)) où z=min(x.datum.weight for x in G.setOfArcs if ( (x.fromNode == n) or (x.toNode == n) )).

KMA: Puis formez un nouveau digrafo K composé uniquement du arcs de poids zéro de H, et le nœuds incident dans ces arcs . Formez un bipartition dans les nœuds dans K, puis utilisez solve_mbm( bipartition ) Pour en avoir un combinaison maximale (matching) dans K. Si matching c'est une combinaison parfaite dans H (Les arcs dans matching ils sont incident dans tous les nœuds dans H.setOfNodes) puis matching est une solution optimale pour problème d'affectation linéaire .

Sinon, si matching ce n'est pas parfait , génère le couverture minimale des nœuds de K en utilisant node_cover = get_min_node_cover(matching, bipartition(K)). Définissez ensuite z=min({a.datum.weight for a in H.setOfArcs if a not in node_cover}). Définissez 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)}. Le != le symbole dans l'expression ci-dessus agit comme un opérateur XOR . Puis arcs = arcs1.union(arcs2.union(arcs3)). Puis H=DiGraph(nodes,arcs). Revenir à la marque KMA . L'algorithme continue jusqu'à ce qu'un combinaison parfaite . Est combinaison est aussi la solution pour problème d'affectation linéaire .

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

Cette implémentation est O(N^{5}) car il génère un nouveau combinaison maximale matching à chaque itération; similaire aux deux implémentations précédentes de Edmonds-Karp Cet algorithme peut être modifié pour suivre le match et l'adapter intelligemment à chaque itération. Lorsque cela est fait, la complexité devient O(N^{4}). Une version plus avancée et plus récente de cet algorithme (nécessitant des structures de données plus avancées) peut être exécutée sur O (N ^ {3}). Les détails de l'implémentation plus simple ci-dessus et de l'implémentation plus avancée peuvent être trouvés sur ce post qui a motivé ce blog.

Aucune des opérations sur les poids du arc modifie l'affectation finale renvoyée par l'algorithme. Par conséquent: puisque nos graphiques d'entrée sont toujours graphiques bi-partitionnés complets , une solution doit attribuer à chaque nœud d'une partition à l'autre nœud dans la deuxième partition, à travers le arc entre ces deux nœuds . Notez que les opérations effectuées sur les poids de arc ne changez jamais l'ordre (trié par poids) des arcs incidents à aucun nœud particulier .

Par conséquent, lorsque l'algorithme se termine par un bi-partition parfaite et complète , chaque nœud se voit attribuer un poids d'arc nul , puisque l'ordre relatif des arcs de quoi nœud n'a pas changé pendant l'algorithme et depuis un arc poids zéro c'est l'arc le moins cher possible et le complément bipartite complet parfait garantit qu'il y a un arc pour chaque nœud . Cela signifie que la solution générée est en fait la même que la solution du problème cartographie linéaire d'origine sans aucune modification des poids de arc .

Allocations déséquilibrées

Il semble que l'algorithme soit assez limité, car comme décrit, il n'opère que sur graphiques bipartites complets (ceux où la moitié des nœuds font partie de la bipartition et l'autre moitié dans la seconde partie). Chez l'ouvrier, la motivation des tâches signifie qu'il y a autant d'ouvriers qu'il y a de tâches (cela semble assez limitant).

Cependant, il existe une transformation simple qui supprime cette restriction. Supposons qu'il y ait moins de travailleurs que de tâches, nous ajoutons des travailleurs fictifs (assez pour faire du graphique résultant un graphique bipartite complet ). Chaque travailleur fictif a un arc dirigé par le travailleur vers chacune des tâches. Chacun de ces arc a un poids 0 (le placer dans un match ne donne aucun avantage supplémentaire). Après ce changement, le graphique est un graphique bipartite complet que nous pouvons résoudre. Aucune tâche assignée à un travailleur fictif n'est démarrée.

Supposons qu'il y ait plus de tâches que de travailleurs. Nous avons ajouté quelques tâches factices (assez pour faire du graphique résultant un graphe bi-partitionné complet ). Chaque tâche fictive a un arc dirigé de chaque travailleur vers la tâche fictive. Chacun de ces arc il a un poids de 0 (le placer dans un match ne donne aucun avantage supplémentaire). Après ce changement, le graphique est un graphe bi-partitionné complet que nous pouvons résoudre. Tout travailleur affecté à la tâche fictive n'est pas employé pendant la période.

Exemple d'affectation linéaire

Enfin, nous allons faire un exemple avec le code que j'ai utilisé. Je vais modifier l'exemple de problème de ici . Nous avons 3 tâches: nous avons besoin nettoyer la salle de bain , balayer le sol , Y nettoyer les vitres .

Les travailleurs disponibles sont Alice , Bob , Charlie Oui Diane . Chacun des travailleurs nous donne le salaire dont il a besoin par tâche. Voici les salaires par travailleur:

Nettoyer la salle de bain Balayez le sol Nettoyer les vitres
Alice 2 $ 3 $ 3 $
Bob 3 $ 2 $ 3 $
Charlie 3 $ 3 $ 2 $
Diane 9 $ 9 $ 1 $

Si nous voulons payer le moins d'argent possible tout en réalisant toutes les tâches, qui devrait faire quelle tâche? Commencez par entrer une tâche factice pour le digraphe qui représente le problème bipartite.

Nettoyer la salle de bain Balayez le sol Nettoyer les vitres Ne fais rien
Alice 2 $ 3 $ 3 $ 0 $
Bob 3 $ 2 $ 3 $ 0 $
Charlie 3 $ 3 $ 2 $ 0 $
Diane 9 $ 9 $ 1 $ 0 $

En supposant que le problème est codé dans un digrafo , alors kuhn_munkres( bipartition(G) ) résoudra le problème et retournera la mission. Il est facile de vérifier que l'allocation optimale (coût le plus bas) coûtera 5 $.

Voici une visualisation de la solution que le code ci-dessus génère:

Affichage du débit de pointe

Affichage du débit de pointe (résiduel)

C'est tout . Vous savez maintenant tout ce que vous devez savoir sur le problème d'affectation linéaire.

Vous pouvez trouver tout le code de cet article sur GitHub .

Comment éviter la malédiction de l'optimisation prématurée

Gestion De Projet

Comment éviter la malédiction de l'optimisation prématurée
Laissez LoopBack le faire: une présentation du framework d'API Node dont vous rêvez

Laissez LoopBack le faire: une présentation du framework d'API Node dont vous rêvez

Back-End

Articles Populaires
Création d'une API REST Node.js / TypeScript, partie 2: modèles, middleware et services
Création d'une API REST Node.js / TypeScript, partie 2: modèles, middleware et services
Un didacticiel sur la radio définie par logiciel: images de la Station spatiale internationale et écoute de jambons avec un RTL-SDR
Un didacticiel sur la radio définie par logiciel: images de la Station spatiale internationale et écoute de jambons avec un RTL-SDR
Les drones commerciaux révolutionnent les opérations commerciales
Les drones commerciaux révolutionnent les opérations commerciales
Faire des affaires dans l'Union européenne
Faire des affaires dans l'Union européenne
AI vs BI: différences et synergies
AI vs BI: différences et synergies
 
Stratège de contenu produit
Stratège de contenu produit
Risque vs récompense: un guide pour comprendre les conteneurs logiciels
Risque vs récompense: un guide pour comprendre les conteneurs logiciels
Explorer SMACSS: architecture évolutive et modulaire pour CSS
Explorer SMACSS: architecture évolutive et modulaire pour CSS
Si vous n'utilisez pas de données UX, ce n'est pas de la conception UX
Si vous n'utilisez pas de données UX, ce n'est pas de la conception UX
Simplification de l'utilisation des API RESTful et de la persistance des données sur iOS avec Mantle et Realm
Simplification de l'utilisation des API RESTful et de la persistance des données sur iOS avec Mantle et Realm
Articles Populaires
  • lors de la conception d'une présentation, laquelle est correcte ?
  • laquelle des catégories de polices suivantes est décrite comme contemporaine/moderne ?
  • différence entre objectif c et swift
  • aliments entiers appartenant à walmart
  • comment évaluer une option d'achat
Catégories
  • Rise Of Remote
  • Outils Et Tutoriels
  • Équipes Distribuées
  • Mode De Vie
  • © 2022 | Tous Les Droits Sont Réservés

    portaldacalheta.pt