算法:在给定P偏好的M个任务中最优地分配N个人

时间:2013-09-26 17:13:42

标签: algorithm

示例:对于3个可用作业,10个候选人各自给出2个首选项(第一个比第二个更优先),然后他们的老板必须最佳地分配(并均匀分配)他们均匀根据他们的偏好。显然,不需要的工作需要一些随机抽取。

我如何编写自动计算此最佳分配的算法?

我环顾四周,找到了bipartite graphs,这可能会给我一些线索,但是我无法绕过它!

对于游戏的“运气”方面,我已经实施了一个简单的Fisher Yates Shuffle。

偏好权重: 如果有2个偏好,当分配给工人时,获得第一个选择权重为+2,第二个选择+1,不需要的选择-1(例如)。 “最优性”目标是最大化聚合偏好。

3 个答案:

答案 0 :(得分:2)

你的问题非常具有挑战性,但我找到了一个有效的(可能不是最高效的)解决方案。我的示例是用PHP编写的,但您应该能够对其进行调整。我将尝试解释代码背后的“想法”。

注意:好像您之后添加了“10人,3个作业”限制 - 或者我简单地过度了。但是,我的代码应该为您提供一个可能能够适应该约束的示例。我的代码目前假设n人有n个工作。 (使其适应10/3标准的最简单方法是将3个工作分成10个相同的工作单元,假设有10个工人!)

首先,让我们创建一些基本的架构。我们需要personjob,显然是一个矩阵,代表一个人对工作的满意度。以下剪辑就是这样做的:

<?php
class Person{
  var $name;
  var $prim;
  var $sec;

  function __construct($name, $prim, $sec){
    $this->name = $name;
    $this->prim = $prim;
    $this->sec = $sec;
  }

  function likes($job){
    if ($job->type == $this->prim) return 2;
    if ($job->type == $this->sec) return 1;
    else return -1;
  }
}

class Job{
  var $name;
  var $type;

  function __construct($name, $type){
    $this->name = $name;
    $this->type = $type;
  }
}


$persons = array(
  "Max" => new Person("Max", "programing", "testing"),
  "Peter" => new Person("Peter", "testing", "docu"),
  "Sam" => new Person("Sam", "designing", "testing")
);

$jobs = array(
  "New Classes" => new Job("New Classes", "programing"),
  "Theme change" => new Job("Theme change", "designing"),
  "Test Controller" => new Job("Test Controller", "testing")
);


// debug: draw it: 
echo "<h2>Happines with Jobs</h2> ";
echo "<table border=1>";
$p=0;
echo "<tr>";       
foreach ($jobs AS $job){
  $j=0;
  foreach ($persons as $person){
    if ($p++==0){
      echo "<tr><td></td>";
      foreach ($persons as $per) {
        echo "<td>".$per->name."</td>";
      }                             
      echo "</tr>";
    }


    if ($j++==0){
      echo "<td>".$job->name."</td>";
    }

    echo "<td>".$person->likes($job)."</td>";
  }
  echo "</tr>";
}
echo "</table>";

这会给你一个这样的表:

Happiness with Jobs

第二,我们需要创建所有工作和人员的排列。 (实际上我们不需要,但这样做会告诉你原因,为什么我们不需要!)

要创建所有排列,我们只使用某个人或作业的名称。 (我们可以稍后将名称解析回实际对象)

//build up all permutations
$personNames = array();
foreach ($persons AS $person){
  $personNames[] = $person->name;
}

$jobNames = array();
foreach ($jobs AS $job){
  $jobNames[] = $job->name;
}              

$personsPerms = array();
pc_permute($personNames,$personsPerms);

$jobsPerms = array();
pc_permute($jobNames,$jobsPerms);     

function pc_permute($items, &$result, $perms = array( )) {
    if (empty($items)) { 
      $result[] = join('/', $perms);
    }  else {
        for ($i = count($items) - 1; $i >= 0; --$i) {
             $newitems = $items;
             $newperms = $perms;
             list($foo) = array_splice($newitems, $i, 1);
             array_unshift($newperms, $foo);
             pc_permute($newitems,$result, $newperms);
         }
    }
}

现在,我们有2个阵列:所有工作排列和所有人的排列。 对于上面给出的示例,数组将如下所示(每个3个元素,使每个数组3 * 2 * 1 = 6个排列):

Array
(
    [0] => Max/Peter/Sam
    [1] => Peter/Max/Sam
    [2] => Max/Sam/Peter
    [3] => Sam/Max/Peter
    [4] => Peter/Sam/Max
    [5] => Sam/Peter/Max
)
Array
(
    [0] => New Classes/Theme change/Test Controller
    [1] => Theme change/New Classes/Test Controller
    [2] => New Classes/Test Controller/Theme change
    [3] => Test Controller/New Classes/Theme change
    [4] => Theme change/Test Controller/New Classes
    [5] => Test Controller/Theme change/New Classes
)

现在,我们可以创建一个nXn表,其中包含所有可能的工作分配的总体满意度的所有值:

// debug: draw it:            
echo "<h2>Total Happines of Combination (full join)</h2> ";
echo "<table border=1>";
$p=0;
echo "<tr>";
$row = 0;
$calculated = array();       
foreach ($jobsPerms AS $jobComb){
  $j=0;
  $jobs_t = explode("/", $jobComb);
  foreach ($personsPerms as $personComb){

    if ($p++==0){
      echo "<tr><td></td>";
      foreach ($personsPerms as $n) {
        echo "<td>".$n."</td>";
      }                             
      echo "</tr>";
    }


    if ($j++==0){
      echo "<td>".$jobComb."</td>";
    }

    $persons_t = explode("/", $personComb);
    $h = 0;


    echo "<td>";
    for ($i=0; $i< count($persons_t); $i++){      
      $h += $persons[$persons_t[$i]]->likes($jobs[$jobs_t[$i]]);
    }
    echo $h;
    echo "</td>";

  }
  $col=0;
  $row++;
  echo "</tr>";
}
echo "</table>";

enter image description here

让我们称这个矩阵为“M”

此矩阵包含“批次”的双重组合:(a / b)TO(1/2)等于(b / a)到(2/1)等...

毕竟:我们简单可以忽略:

  • 每一行&gt; 1
  • OR每列&gt; 1

忽略所有列&gt; 1:

echo "<h2>Total Happines of Combination (ignoring columns)</h2> ";
echo "<table border=1>";
$p=0;
echo "<tr>";
$row = 0;
$calculated = array();       
foreach ($jobsPerms AS $jobComb){
  $j=0;
  $jobs_t = explode("/", $jobComb);
  $col = 0;
  $personComb = $personsPerms[0];

  if ($p++==0){
    echo "<tr><td></td>";
    echo "<td>".$personsPerms[0]."</td>";        
    echo "</tr>";
  }


  if ($j++==0){
    echo "<td>".$jobComb."</td>";
  }

  $persons_t = explode("/", $personComb);
  $h = 0;


  echo "<td>";
  for ($i=0; $i< count($persons_t); $i++){      
    $h += $persons[$persons_t[$i]]->likes($jobs[$jobs_t[$i]]);
  }
  echo $h;
  echo "</td>";

  $col=0;
  $row++;
  echo "</tr>";
}
echo "</table>";

输出:

enter image description here

你去吧!在这个例子(其中一个)中,最令人满意的解决方案是:

  • Max - &gt;新课程(+2)
  • 彼得 - &gt;测试控制器(+2)
  • Sam - &gt;主题变化(+2)

- &GT;幸福:6。

还有其他相同的分布。

示例:6人/ 6个工作:

$persons = array(
  "Max" => new Person("Max", "programing", "testing"),
  "Peter" => new Person("Peter", "testing", "docu"),
  "Sam" => new Person("Sam", "designing", "testing"),
  "Jeff" => new Person("Jeff", "docu", "programing"),
  "Fred" => new Person("Fred", "programing", "designing"),
  "Daniel" => new Person("Daniel", "designing", "docu") 
);

$jobs = array(
  "New Classes" => new Job("New Classes", "programing"),
  "Theme change" => new Job("Theme change", "designing"),
  "Test Controller" => new Job("Test Controller", "testing"),
  "Create Manual" => new Job("Create Manual", "docu"),
  "Program more!" => new Job("Program more!", "programing"),
  "Style the frontend" => new Job("Style the frontend", "designing")
);

enter image description here

结果(人物:Max / Peter / Sam / Jeff / Fred / Daniel)

enter image description here

答案 1 :(得分:1)

假设通过“均匀分配”表示您知道必须为每个项目分配多少人,这是weighted matching problem (又名“最大基数二分匹配”)。只需将每个打开的位置(而不是每个作业)视为一个节点 - 因此,具有3个位置的作业将具有三个节点。

维基百科文章提供了几种解决方案。

答案 2 :(得分:-1)

伪代码

for(n to number of jobs left)
{
  job n = a random candidate
  if(random candidate first preference == job n)
    remove random candidate from list and remove job from list
}

if(jobs left)
{
  for(n to number of jobs left)
    for(i to number of candidates)
      if(candidate first preference == job n)
      {
          job n = candidate i
          remove candidate i from list and remove job n from list
      }
      else if(candidate second preference == job n)
      {
          job n = candidate i
      }
}