Algorithme hongrois, ou comment les mathématiques aident à assigner des devoirs

Bonjour mes amis! Dans cet article, je voudrais parler d'un algorithme intéressant de la discipline «recherche opérationnelle», à savoir sur la méthode hongroise et comment résoudre les problèmes d'affectation avec son aide. Je vais aborder la théorie dans quels cas et pour quelles tâches cet algorithme est applicable, je l’analyserai pas à pas sur mon exemple inventé, et partagerai ma modeste esquisse du code pour son implémentation dans le langage R. Commençons!

image

Quelques mots sur la méthode


Afin de ne pas peindre beaucoup de théorie avec des termes et des définitions mathématiques, je propose d'envisager quelques options pour construire le problème d'affectation, et je pense que vous comprendrez immédiatement dans quels cas nous appliquerons la méthode hongroise:

  • La tâche de nommer des employĂ©s Ă  des postes. Il est nĂ©cessaire de rĂ©partir les travailleurs Ă  des postes afin que l'efficacitĂ© maximale soit atteinte, ou qu'il y ait un coĂ»t de travail minimal.
  • Affectation de machines aux sections de production. La rĂ©partition des machines de sorte que lors de leur fonctionnement la production soit aussi rentable que possible, ou le coĂ»t de leur maintenance est minime.
  • La sĂ©lection des candidats pour divers postes vacants est estimĂ©e. Nous analyserons cet exemple ci-dessous.

Comme vous pouvez le voir, il existe de nombreuses options pour lesquelles la méthode hongroise est applicable, et des tâches similaires se posent dans de nombreux domaines d'activité.

En conséquence, la tâche doit être résolue de sorte qu'un interprète (personne, machine, outil, ...) ne puisse effectuer qu'un seul travail, et chaque travail a été effectué par un seul interprète.

Une condition nécessaire et suffisante pour résoudre un problème est son type fermé. C'est-à-dire lorsque le nombre d'interprètes = le nombre d'œuvres (N = M). Si cette condition n'est pas remplie, vous pouvez ajouter des interprètes fictifs ou des œuvres fictives dont les valeurs dans la matrice seront nulles. Cela n'affectera pas la solution du problème, il ne lui donnera que le type fermé nécessaire.

Algorithme pas Ă  pas par exemple


Énoncé du problème: Qu'une conférence scientifique importante soit planifiée. Pour le diriger, vous devez configurer le son, la lumière, les images, enregistrer les invités et préparer les pauses entre les représentations. Il y a 5 organisateurs pour cette tâche. Chacun d'eux a certaines estimations de la performance d'un travail particulier (supposons que ces estimations soient définies comme la moyenne arithmétique des évaluations de leurs employés). Il est nécessaire de répartir les organisateurs afin que leur score total soit maximal. La tâche a la forme suivante:

image

Si le problème est résolu au maximum (comme dans notre cas), alors dans chaque ligne de la matrice, il est nécessaire de trouver l'élément maximum, de le soustraire de chaque élément de la ligne correspondante et de multiplier la matrice entière par -1. Si le problème est résolu au minimum, cette étape doit être ignorée.

image

Dans chaque ligne et dans chaque colonne, il ne devrait y avoir qu'un seul zéro sélectionné. (c'est-à-dire que lorsque zéro a été choisi, les zéros restants dans cette ligne ou dans cette colonne ne sont plus pris en compte). Dans ce cas, cela ne peut pas être fait:

image

( Si le problème est résolu au minimum, vous devez commencer par cette étape ). Nous continuons la solution plus loin. Réduction de la matrice par lignes (recherchez l'élément minimum dans chaque ligne et soustrayez-le de chaque élément, respectivement):

image

Parce que Étant donné que tous les éléments minimaux sont nuls, la matrice n'a pas changé. Nous effectuons la réduction des colonnes:

image

Encore une fois, nous cherchons à ce que dans chaque colonne et dans chaque ligne, il n'y ait qu'un seul zéro sélectionné. Comme on peut le voir ci-dessous, dans ce cas, il est impossible de le faire. J'ai présenté deux options pour choisir les zéros, mais aucune n'a donné le résultat souhaité:

image

Nous continuons la décision. Biffez les lignes et les colonnes qui ne contiennent aucun élément ( IMPORTANT! Le nombre de croisements doit être minimal ). Parmi les éléments restants, nous recherchons le minimum, le soustrayons des éléments restants (qui ne sont pas barrés) et ajoutons aux éléments qui sont situés à l'intersection des lignes et des colonnes barrées (ce qui est marqué en vert y est soustrait; ce qui est marqué en or y est résumé; puis, ce qui n'est pas peint - ne pas toucher):

image

Comme vous pouvez le voir maintenant, dans chaque colonne et ligne, il n'y a qu'un seul zéro sélectionné. Nous complétons la solution du problème!

image

Remplacez dans le tableau initial l'emplacement des zéros sélectionnés. Ainsi, nous obtenons le plan optimal, ou optimal, dans lequel les organisateurs sont répartis entre le travail et la somme des estimations s'est avérée maximale:

image

Si vous résolvez le problème et qu'il vous est toujours impossible de choisir des zéros pour qu'il n'y en ait qu'un dans chaque colonne et ligne, nous répétons l'algorithme à partir de l'endroit où la réduction par lignes a été effectuée (l'élément minimum dans chaque ligne).

Implémentation du langage de programmation R


L'algorithme hongrois est implémenté à l'aide de récursions. J'espère que mon code ne causera pas de difficultés. Vous devez d'abord compiler trois fonctions, puis lancer les calculs.

Les données pour résoudre le problème proviennent du fichier example.csv qui a la forme:

image

Le code a été ajouté au spoiler car l'article serait trop volumineux à cause de cela
#     library(dplyr) # csv  (  -  ;   -  ) table <- read.csv("example.csv",header=TRUE,row.names=1,sep=";") #  unique_index <- hungarian_algorithm(table,T) # cat(paste(row.names(table[as.vector(unique_index$row),])," - ",names(table[as.vector(unique_index$col)])),sep = "\n") #   cat("  -",sum(mapply(function(i, j) table[i, j], unique_index$row, unique_index$col, SIMPLIFY = TRUE))) #____________________  __________________________________ hungarian_algorithm <- function(data,optim=F){ # optim = T,       if(optim==T) { data <- data %>% apply(1,function(x) (x-max(x))*(-1)) %>% t() %>% as.data.frame() optim <- F } #    data <- data %>% apply(1,function(x) x-min(x)) %>% t() %>% as.data.frame() #    zero_index <- which(data==0, arr.ind = T) #  ""  - unique_index <- from_the_beginning(zero_index) #  ""        , .. if(nrow(unique_index)!=nrow(data)) #.. ""  - unique_index <- from_the_end(zero_index) #    ,     if(nrow(unique_index)!=nrow(data)) { #    data <- data %>% apply(2,function(x) x-min(x)) %>% as.data.frame() zero_index <- which(data==0, arr.ind = T) unique_index <- from_the_beginning(zero_index) if(nrow(unique_index)!=nrow(data)) unique_index <- from_the_end(zero_index) if(nrow(unique_index)!=nrow(data)) { #""        (!     ) index <- which(apply(data,1,function(x) length(x[x==0])>1)) index2 <- which(apply(data[-index,],2,function(x) length(x[x==0])>0)) #     min_from_table <- min(data[-index,-index2]) #     data[-index,-index2] <- data[-index,-index2]-min_from_table #  ,        data[index,index2] <- data[index,index2]+min_from_table zero_index <- which(data==0, arr.ind = T) unique_index <- from_the_beginning(zero_index) if(nrow(unique_index)!=nrow(data)) unique_index <- from_the_end(zero_index) #    ""        , .. if(nrow(unique_index)!=nrow(data)) #..    hungarian_algorithm(data,optim) else #  ""  unique_index } else #  ""  unique_index } else #  ""  unique_index } #_________________________________________________________________________________ #__________   ""  -___________ from_the_beginning <- function(x,i=0,j=0,index = data.frame(row=numeric(),col=numeric())){ #  ,      i,   j find_zero <- x[(!x[,1] %in% i) & (!x[,2] %in% j),] if(length(find_zero)>2){ #     i <- c(i,as.vector(find_zero[1,1])) #     j <- c(j,as.vector(find_zero[1,2])) #   data frame (     ) index <- rbind(index,setNames(as.list(find_zero[1,]), names(index))) #         from_the_beginning(find_zero,i,j,index)} else rbind(index,find_zero) } #_________________________________________________________________________________ #__________   ""  -___________ from_the_end <- function(x,i=0,j=0,index = data.frame(row=numeric(),col=numeric())){ find_zero <- x[(!x[,1] %in% i) & (!x[,2] %in% j),] if(length(find_zero)>2){ i <- c(i,as.vector(find_zero[nrow(find_zero),1])) j <- c(j,as.vector(find_zero[nrow(find_zero),2])) index <- rbind(index,setNames(as.list(find_zero[nrow(find_zero),]), names(index))) from_the_end(find_zero,i,j,index)} else rbind(index,find_zero) } #_________________________________________________________________________________ 


Le résultat du programme:

image
image


En conclusion


Merci beaucoup d'avoir pris le temps de lire mon article. Je fournirai tous les liens utilisés ci-dessous. J'espère que vous avez appris quelque chose de nouveau par vous-même ou mis à jour vos anciennes connaissances. Tout succès, bonne et bonne chance!

Ressources utilisées


1. Wikipédia pris d' assaut
2. Et d' autres bons sites
3. A souligné pour moi-même certaines informations de cet article.

Source: https://habr.com/ru/post/fr422009/


All Articles