Mini-max算法是一种递归或回溯算法,用于决策和博弈论。它为资源分配提供了一个最佳的分配方式,最近在项目中将这种算法应用到了资源分配。当每个需求方的权重相等时,需求方对于资源都是公平的,应该按照先后顺序分配,直到资源分配完,或者全部满足即分配结束。
-
下面的代码为基础的无权分配方式,即所有权重都是一样的
最大最小公平算法定义如下(不带权):
1、资源按照需求递增的顺序进行分配;
2、不存在用户获得的资源超过自身的需求;
3、对于未满足的用户,等价分享剩余资源。
-
实现逻辑描述:首先假定用户集合有n个用户,1到n,他们对资源的需求已经排序完毕,满足s1<s2< .... <sn,资源总量为S。
-
将资源S/n分配给需求最小的1用户,这很可能已经超出了用户1的需求;
-
将超出的部分回收,再次将(S-s1)/(n-1)的资源分配给用户2,依次次重复上述过程,直到某一次分给该用户的资源不满足该用户的需求;
package com.yzf.task.decision.utils;
import java.util.*;
/**
* @Author PSY
* @Date 2022/3/24 21:34
* 最大最小公平算法分配,不带权
* @Desc
*/
public class MaxMinAllocate {
public static void main(String[] args) {
//总共资源数
int total = 137;
List<Integer> needList = new ArrayList<>(Arrays.asList(10, 22, 33, 45, 1, 2, 34, 12));
int n = needList.size();
//求出平均值
int average = total / n;
System.out.println("资源的平均数为:" + average);
//结果集
List<Integer> result = new ArrayList<>();
//剩余数目
int remain = 0;
//存入满足的坐标
Map<Integer, Boolean> okMap = new HashMap<>();
// 第一轮分配
for (int i = 0; i < n; i++) {
if (needList.get(i) >= average) {
result.add(i, average);
} else {
result.add(i, needList.get(i));
okMap.put(i, true);
remain += average - needList.get(i);
}
}
System.out.println("分配需求数据:" + needList);
System.out.println("第一次分配结果:" + result + ",剩下的结果:" + remain);
//如果还有剩余继续分配
while (remain > 0) {
if (n == okMap.size()) break;
average = remain / (n - okMap.size());
remain = 0;
for (int i = 0; i < n; i++) {
if (Objects.nonNull(okMap.get(i)) && okMap.get(i)) {
continue;
}
if (needList.get(i) >= average + result.get(i)) {
result.set(i, average + result.get(i));
} else {
System.out.println("第" + i + "个已经满足需求");
okMap.put(i, true);
remain += average + result.get(i) - needList.get(i);
result.set(i, needList.get(i));
}
}
}
System.out.println("分配需求数据:" + needList);
System.out.println("最终分配结果:" + result + ",剩下资源:" + remain);
}
}
二:带有权重的分配方式
最大最小公平算法定义如下(带权):
1、通过权重实现分配的标准化;
2、不存在用户得到的资源超过自己的需求;
3、未得到满足的用户,按照权重共享资源。
创建一个需求方对象
package com.yzf.task.decision.utils;
import lombok.Data;
/**
* @Author PSY
* @Date 2022/3/24 21:51
* 最大最小分配实例
* @Desc
*/
@Data
public class AllocateCaseInfo {
/**
* 需求方id
*/
private Long caseId;
/**
* 分配的个数,即计算的理应分配的数量
*/
private Integer allocateCount;
/**
* 权重,如果没有,默认为1
*/
private Integer weight = 1;
/**
* 需要的数量
*/
private Integer needCount;
}
2:这里计算权重会出现小数点的情况,我这里采用向上取整的方式进行统计,因为只需要一个需求量,至于具体的分配动作由另外的程序控制。
package com.yzf.task.decision.utils;
import cn.hutool.core.util.NumberUtil;
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.util.*;
import java.util.stream.Collectors;
/**
* @Author PSY
* @Date 2022/3/24 22:15
* 带有权重的分配实现逻辑,完美兼容无权的分配,
* jar包中需要导入hutool工具类作为运算工具
* @Desc
*/
public class MaxMinAllocateWithWeight {
public static void main(String[] args) {
// 总资源数
int total = 1;
//构造数据,导致数据少一个或者多一个
DecimalFormat decimalFormat = new DecimalFormat("######0");
decimalFormat.setRoundingMode(RoundingMode.UP);
AllocateCaseInfo allocateCaseInfo = new AllocateCaseInfo();
allocateCaseInfo.setCaseId(1L);
allocateCaseInfo.setWeight(1);
allocateCaseInfo.setNeedCount(5);
AllocateCaseInfo allocateCaseInfo1 = new AllocateCaseInfo();
allocateCaseInfo1.setCaseId(22222L);
allocateCaseInfo1.setWeight(2);
allocateCaseInfo1.setNeedCount(6);
AllocateCaseInfo allocateCaseInfo2 = new AllocateCaseInfo();
allocateCaseInfo2.setWeight(1);
allocateCaseInfo2.setCaseId(3L);
allocateCaseInfo2.setNeedCount(44);
AllocateCaseInfo allocateCaseInfo3 = new AllocateCaseInfo();
allocateCaseInfo3.setWeight(1);
allocateCaseInfo3.setCaseId(83L);
allocateCaseInfo3.setNeedCount(31);
List<AllocateCaseInfo> needCaseList = new ArrayList<>();
needCaseList.add(allocateCaseInfo);
needCaseList.add(allocateCaseInfo1);
//第一波分配,按照权重计算出比例,多退少补
int sumWeight = needCaseList.stream().mapToInt(AllocateCaseInfo::getWeight).sum();
needCaseList.forEach(needCase -> {
double div = NumberUtil.div((double) needCase.getWeight(), (double) sumWeight);
//计算的结果
double allocateCount = NumberUtil.mul((float) total, div);
int weightCount = Integer.parseInt(decimalFormat.format(allocateCount));
//第一轮的权重
needCase.setAllocateCount(weightCount);
});
Map<Long, AllocateCaseInfo> resultMap = new HashMap<>();
//满足条件的计算
List<Long> okList = new ArrayList<>();
for (AllocateCaseInfo caseInfo : needCaseList) {
int allocateCount = caseInfo.getAllocateCount();
int needCount = caseInfo.getNeedCount();
//分配的结果是否大于需要的数量
if (allocateCount < needCount) {
caseInfo.setAllocateCount(allocateCount);
} else {
okList.add(caseInfo.getCaseId());
caseInfo.setAllocateCount(needCount);
}
resultMap.put(caseInfo.getCaseId(), caseInfo);
}
//剩余的数量
int remain = total - needCaseList.stream().mapToInt(AllocateCaseInfo::getAllocateCount).sum();
System.out.println("第一轮计算剩余:" + remain + ",满足的条件的个数为:" + okList.size() + ",结果大小:" + resultMap.size());
for (Long key : resultMap.keySet()) {
AllocateCaseInfo info = resultMap.get(key);
System.out.println("权重:" + info.getWeight() + ",需要的数量为" + info.getNeedCount() + ",结果:" + info.getAllocateCount());
}
while (remain > 0) {
if (okList.size() == needCaseList.size()) break;
List<AllocateCaseInfo> needContinueList = needCaseList.stream().filter(filterCase -> !okList.contains(filterCase.getCaseId())).collect(Collectors.toList());
//剩余的权重总和
int sumContinueWeight = needContinueList.stream().mapToInt(AllocateCaseInfo::getWeight).sum();
int finalRemain = remain;
//进行剩下的权重计算
needContinueList.forEach(needCase -> {
double div = NumberUtil.div((double) needCase.getWeight(), (double) sumContinueWeight);
//计算的结果
double allocateCount = NumberUtil.mul((float) finalRemain, div);
int continueWeight = Integer.parseInt(decimalFormat.format(allocateCount));
//计算的结果
needCase.setAllocateCount(needCase.getAllocateCount() + continueWeight);
});
for (AllocateCaseInfo caseInfo : needContinueList) {
int allocateCount = caseInfo.getAllocateCount();
int needCount = caseInfo.getNeedCount();
if (allocateCount < needCount) {
caseInfo.setAllocateCount(allocateCount);
} else {
okList.add(caseInfo.getCaseId());
caseInfo.setAllocateCount(needCount);
}
resultMap.put(caseInfo.getCaseId(), caseInfo);
}
remain = finalRemain - needContinueList.stream().mapToInt(AllocateCaseInfo::getAllocateCount).sum();
System.out.println("继续下次计算剩余:" + remain);
}
//分配结束,存在小数点的情况,继续按照个数进行分配
List<AllocateCaseInfo> results = new ArrayList<>();
for (Long key : resultMap.keySet()) {
AllocateCaseInfo info = resultMap.get(key);
results.add(info);
}
int allRemain = total - results.stream().mapToInt(AllocateCaseInfo::getAllocateCount).sum();
while (allRemain > 0 && results.stream().anyMatch(th -> !th.getAllocateCount().equals(th.getNeedCount()))) {
//对其按照权重进行排序
results.sort(Comparator.comparing(AllocateCaseInfo::getWeight).reversed());
//优先将最高权限的分配到最多,兼容小数点的问题,权重为1的话
for (AllocateCaseInfo temp : results) {
if (allRemain > 0 && temp.getAllocateCount() < temp.getNeedCount()) {
temp.setAllocateCount(temp.getAllocateCount() + 1);
}
allRemain = allRemain - 1;
}
}
results = results.stream().sorted(Comparator.comparing(AllocateCaseInfo::getWeight).reversed()).collect(Collectors.toList());
System.out.println("总数:" + total + "最终计算的结果:");
for (AllocateCaseInfo info : results) {
System.out.println("权重:" + info.getWeight() + ",需要的数量为" + info.getNeedCount() + ",结果:" + info.getAllocateCount());
}
}
}