假设现在有n个任务需要分配给m个审核员去完成,但是每个审核员手头上还有未完成的任务,且未完成的任务数不同。那么如何均匀的把这些任务分配给各个审核员呢?这里我提出了一种基于平均思想的任务分配算法(我称之为标杆算法)。
算法的主要思想是:首先找出所有审核员中手头未完成任务数最大的审核员,然后其它审核员以该审核员的未完成任务数为标杆,计算自己可容纳的任务数,最后所有审核员可容纳的任务数之和即为总的可容纳任务数(ava_task)。
这里有两种情况,第一种情况是:总的可容纳任务数小于或等于n个待分配的任务数,此时所有审核员以最大未完成任务数(max_task)为标杆,接收待分配的任务。如果刚好分配完,那么算法结束;如果还有剩下的任务未分配,那将剩下的任务抽取m个任务分配给每一个审核员,依次类推,直到剩下的未分配任务数小于m为止,然后再将这小于m的任务随机分配给相应数量的审核员。第二种情况是:总的可容纳任务数大于n个待分配的任务数,此时降低一个单位的标杆(max_task-1),然后循环计算可容纳的任务数,直到退出循环(循环终止条件为:ava_task - task_num <= lower_List.size(),lower_List.size()表示的是低于当前标杆的审核员数)。
接下来,我们将通过一个简单的例子来说明算法的流程,由于第一种情况比较简单,因此,该例子是基于第二种情况的,,如图1所示。
图1
假设有20个任务需要分配给8个审核员(对应8个条形图,实线条形图里的数字代表该审核员手头未完成的任务数)。首先找出这8个审核员中未完成任务数的最大值max_task=7,然后各审核员以max_task为标杆计算各自可容纳的任务数(对应虚线条形图里的数字),总的可容纳任务数为所有审核员可容纳的任务数之和,即ava_task=6+3+4+2+5+0+5+6=31,由图1可知,lower_List.size()=7,由于31-20>7,因此可降低一个单位的标杆,即max_task=max_task-1=6,如图2所示。
那么,ava_task=5+2+3+1+4+0+4+5=24,lower_List.size()=7,由于24-20<7,因此循环终止。由于可容纳的任务数仍然大于待分配的任务数,因此需要再降低一个单位的标杆(一定要考虑这种情况),max_task=max_task-1=5,此时ava_task=4+1+2+0+3+0+3+4=17,lower_List.size()=6,剩余待分配任务数为20-17=3,然后将这3个任务随机分配给低于当前标杆的6个审核员中的3个,每个审核员分配一个。当然算法中还考虑了很多种情况,具体请参见如下代码。由于任务一般按审核员ID来分配,且ID一般为字符串。为了存储方便,我定义了一个二维字符串类型的数组rev_task[i][j]来存储数据,i表示第i个审核员,rev_task[i][0]存放的是第i个审核员的ID,rev_task[i][1]存放的是第i个审核员当前未完成的任务数,rev_task[i][2]存放的是第i个审核员应当被分配的任务数。
算法工具类-AlgorithmUtils.java
package com.yushen.allocationAlgorithm;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Random;
public class AlgorithmUtils {
public static void taskAllocation(int task_num, int rev_num, String[][] rev_task) {
Random rd = new Random();
List<Integer> rdList = new ArrayList<>();
int temp;
//获得审核人员中的最大未完成任务数
int max_task = Integer.parseInt(rev_task[0][1]);
for(int i = 1; i < rev_num; i++){
if(max_task < Integer.parseInt(rev_task[i][1]))
max_task = Integer.parseInt(rev_task[i][1]);
}
//以最大待审核任务数为标杆,判断第一轮可容纳的任务数
int ava_task = 0;
List<Integer> lower_List = new ArrayList<>();
for(int i=0;i<rev_num;i++){
if((max_task-Integer.parseInt(rev_task[i][1])) > 0){
ava_task += (max_task-Integer.parseInt(rev_task[i][1]));
lower_List.add(i);
}
}
int task_rest;
int task_avg;
//第一种情况:第一轮可容纳的任务数小于待分配的任务数
if(ava_task - task_num <= 0) {
for(int i = 0; i < rev_num; i++) {
rev_task[i][2] = String.valueOf(max_task-Integer.parseInt(rev_task[i][1]));
}
task_rest = task_num-ava_task;
task_avg = task_rest/rev_num;
if(task_rest != 0) {
while(task_avg > 0) {
for(int i = 0; i < rev_num; i++) {
rev_task[i][2] = String.valueOf(Integer.parseInt(rev_task[i][2])+task_avg);
}
task_rest -= rev_num*task_avg;
task_avg = task_rest/rev_num;
}
rdList.removeAll(rdList);
while(rdList.size() < (task_rest+1)){
temp = rd.nextInt(rev_num);
if(!rdList.contains(temp)){
rdList.add(temp);
}
}
for(int i = 0; i < task_rest; i++) {
rev_task[rdList.get(i)][2] = String.valueOf(Integer.parseInt(rev_task[rdList.get(i)][2])+1);
}
}
}else {//第二种情况:第一轮可容纳的任务数大于待分配的任务数,此时降低一个单位的标杆(max_task-1),然后循环计算可容纳的任务数,直到退出循环
while(ava_task - task_num > lower_List.size()) {
max_task--;
ava_task = 0;
lower_List.removeAll(lower_List);
for(int i=0;i<rev_num;i++){
rev_task[i][2] = "0";
if((max_task-Integer.parseInt(rev_task[i][1])) > 0){
rev_task[i][2] = String.valueOf(max_task-Integer.parseInt(rev_task[i][1]));
ava_task += Integer.parseInt(rev_task[i][2]);
lower_List.add(i);
}
}
}
if(ava_task - task_num > 0) {//如果可容纳的任务数大于待分配的任务数,那么需要再再降低一个单位的标杆
max_task--;
ava_task = 0;
lower_List.removeAll(lower_List);
for(int i=0;i<rev_num;i++){
if((max_task-Integer.parseInt(rev_task[i][1])) >= 0){
rev_task[i][2] = String.valueOf(max_task-Integer.parseInt(rev_task[i][1]));
ava_task += Integer.parseInt(rev_task[i][2]);
lower_List.add(i);
}
}
task_rest = task_num - ava_task;
rdList.removeAll(rdList);
while(rdList.size() < (task_rest+1)){
temp = rd.nextInt(rev_num);
if((!rdList.contains(temp))&&(lower_List.contains(temp))){
rdList.add(temp);
}
}
for(int i = 0; i < task_rest; i++) {
rev_task[rdList.get(i)][2] = String.valueOf(Integer.parseInt(rev_task[rdList.get(i)][2])+1);
}
}else {
task_rest = task_num-ava_task;
if(task_rest != 0) {
rdList.removeAll(rdList);
while(rdList.size() < (task_rest+1)){
temp = rd.nextInt(rev_num);
if((!rdList.contains(temp))&&(lower_List.contains(temp))){
rdList.add(temp);
}
}
for(int i = 0; i < task_rest; i++) {
rev_task[rdList.get(i)][2] = String.valueOf(Integer.parseInt(rev_task[rdList.get(i)][2])+1);
}
}
}
}
//记录被分配的任务数
for(int i=0;i<rev_num;i++){
rev_task[i][1] = String.valueOf(Integer.parseInt(rev_task[i][1])+Integer.parseInt(rev_task[i][2]));
}
}
}
算法测试类-TestAlgorithm.java
package com.yushen.allocationAlgorithm;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.Scanner;
public class TestAlgorithm {
public static void main(String[] args) {
Scanner sc=new Scanner(System.in);
System.out.println("请输入任务数:");
int task_num = sc.nextInt();
System.out.println("请输入审核人员的当前未完成任务数组,整数数字输入时用英文逗号隔开:");
String inputString=sc.next().toString();
String stringArray[]=inputString.split(",");
int rev_num = stringArray.length;//审核人员总数
String[][] rev_task =new String[rev_num][3];
Random rd = new Random();
List<Integer> rdList = new ArrayList<>();
rdList.removeAll(rdList);
int temp;
while(rdList.size() < (rev_num+1)){
temp = rd.nextInt(100);
if(!rdList.contains(temp)){
rdList.add(temp);
}
}
System.out.println("算法前的任务分配:");
for(int i=0;i<rev_num;i++){
rev_task[i][0] = String.valueOf(rdList.get(i) + 1);
rev_task[i][1]= stringArray[i];
rev_task[i][2] = "0";
System.out.print(rev_task[i][0]+","+rev_task[i][1]+" ");
}
System.out.println();
AlgorithmUtils.taskAllocation(task_num, rev_num, rev_task);//调用算法工具类
System.out.println("算法后的任务分配:");
for(int i=0;i<rev_num;i++){
System.out.print(rev_task[i][0]+","+rev_task[i][1]+" ");
}
}
}
运行结果:
请输入任务数:
20
请输入审核人员的当前未完成任务数组,整数数字输入时用英文逗号隔开:
1,4,3,5,2,7,2,1
算法前的任务分配:
72,1 63,4 73,3 49,5 74,2 43,7 100,2 20,1
算法后的任务分配:
72,5 63,5 73,5 49,6 74,5 43,7 100,6 20,6
由运行结果可知,20个任务均衡的分配给了每个审核人员,达到了平均分配的目的!
本人才疏学浅,如有疏漏,请各位大虾不吝赐教!