二分图算法
二分图
// 阿姨派单
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.*;
/**
* 请在此类中完成解决方案,实现process完成数据的处理逻辑。
*
* @author zhaoyiwei
* @date 2021年12月15日 上午11:18:32
*/
public class Solution {
List<OrderDetail> orderList = new ArrayList<>();
List<Cleaner> cleaners = new ArrayList<>();
List<PackageDetail> packages = new ArrayList<>();
int packageCount = 0;
int cleanerCount = 0;
int orderCount = 0;
// 邻接表存阿姨与打包订单之间的关系
List<List<Edge>> cleanerWithPackages;
List<List<Edge>> packageWithCleaner;
/**
* 主体逻辑实现demo,实现代码时请注意逻辑严谨性,涉及到操作文件时,保证文件有开有闭等。
*
* @param auntFile 阿姨信息文件
* @param orderFile 订单信息文件
*/
public void process(String auntFile, String orderFile) throws Exception {
getOrderList(orderFile);
// 按服务分排序
getCleanerList(auntFile);
// 已匹配的order
boolean[] matchOrder = new boolean[orderCount];
// 选择打包的第一个订单,如果被选中,选择下一个订单
for (int n = 0; n < orderCount; n++) {
if (matchOrder[n])
continue;
PackageDetail currentPackage = new PackageDetail();
currentPackage.list.add(n);
matchOrder[n] = true;
for (int i = n; i < orderCount; i++) {
// 选择下一个完成时间最小的点
double finishTime = Double.MAX_VALUE, waitTime = 0, distance = 0;
// 上一单的pos
int lastOrderPos = currentPackage.list.get(currentPackage.list.size() - 1);
int nextPos = -1;
for (int j = n + 1; j < orderCount; j++) {
if (matchOrder[j])
continue;
Pair finishInfo = getFinishTimeByPoint(orderList.get(lastOrderPos), orderList.get(j));
if (finishInfo.endTime < 0)
continue;
if (finishInfo.endTime < finishTime) {
nextPos = j;
finishTime = finishInfo.endTime;
waitTime = finishInfo.waitTime;
distance = finishInfo.distance;
}
}
// 将选中的点加入列表中
if (nextPos != -1) {
currentPackage.list.add(nextPos);
currentPackage.waitTime += waitTime;
currentPackage.totalDistance += distance;
matchOrder[nextPos] = true;
}
}
packages.add(currentPackage);
}
packageCount = packages.size();
packageWithCleaner = new ArrayList<>(packageCount);
for (int i = 0; i < orderCount; i++) {
if (!matchOrder[i])
System.out.println(i);
}
// 存在问题, 如果打包数量 大于 阿姨数量 会存在部分订单不匹配的问题?
// 初始化二分图
initBipartiteGraph();
// hungarian();
KM();
}
public void KM() throws Exception {
double maxValue = Double.MAX_VALUE;
double[] lx = new double[packageCount], ly = new double[cleanerCount], slack = new double[cleanerCount];
boolean[] visx = new boolean[packageCount], visy = new boolean[cleanerCount];
int[] matchList = new int[cleanerCount];
int[] fa = new int[packageCount + cleanerCount];
Arrays.fill(lx, 0);
Arrays.fill(matchList, -1);
// 初始化与cleaner的最大权值
for (int i = 0; i < packageCount; i++) {
for (Edge edge : packageWithCleaner.get(i))
lx[i] = Math.max(lx[i], edge.weight);
}
for (int i = 0; i < packageCount; i++) {
Arrays.fill(slack, maxValue);
Arrays.fill(fa, -1);
Arrays.fill(visx, false);
Arrays.fill(visy, false);
int leaf = -1;
boolean fir = true;
while (true) {
// System.out.println("匹配节点:第 " + i + "个cleaner,匹配的order Pos:" + leaf);
if (fir) {
leaf = findPath(i, matchList, fa, visx, visy, lx, ly, slack);
// System.out.println("if fir 匹配节点:第 " + i + " 个cleaner,匹配的order Pos:" + leaf);
fir = false;
} else {
for (int j = 0; j < packageCount; j++) {
if (slack[j] <= 0.000001 && slack[j] >= 0 ) {
slack[j] = maxValue;
leaf = findPath(j, matchList, fa, visx, visy, lx, ly, slack);
if (leaf > 0) break;
}
}
}
if (leaf > 0) {
int p = leaf;
while (p > 0) {
matchList[p - packageCount] = fa[p];
p = fa[fa[p]];
}
break;
} else {
double delta = maxValue;
for (int j = 0; j < packageCount; j++)
if (visx[j] && delta > slack[j])
delta = slack[j];
for (int j = 0; j < packageCount; j++)
if (visx[j]) {
lx[j] -= delta;
slack[j] -= delta;
}
for (int j = 0; j < cleanerCount; j++)
if (visy[j])
ly[j] += delta;
}
}
}
getResult(matchList);
}
public int findPath(int packagePos, int[] match, int[] fa, boolean[] visx, boolean[] visy, double[] lx, double[] ly, double[] slack) {
double tmpDelta;
visx[packagePos] = true;
List<Edge> edges = packageWithCleaner.get(packagePos);
for (Edge edge : edges) {
if (visy[edge.cleanerPos])
continue;
tmpDelta = lx[packagePos] + ly[edge.cleanerPos] - edge.weight;
if (tmpDelta <= 0.000001 && tmpDelta >= 0) {
visy[edge.cleanerPos] = true;
fa[edge.cleanerPos + packageCount] = packagePos;
if (match[edge.cleanerPos] == -1) {
return edge.cleanerPos + packageCount;
}
fa[match[edge.cleanerPos]] = edge.cleanerPos + packageCount;
int res = findPath(match[edge.cleanerPos], match, fa, visx, visy, lx, ly, slack);
if (res > 0)
return res;
} else if (slack[packagePos] > tmpDelta) {
slack[packagePos] = tmpDelta;
}
}
return -1;
}
public void hungarian() throws Exception {
boolean[] pathMark = new boolean[cleanerCount];
int[] matchList = new int[cleanerCount];
Arrays.fill(matchList, -1);
for (int i = 0; i < packageCount; i++) {
Arrays.fill(pathMark, false);
findArgumentPath(i, pathMark, matchList);
}
// for (int i = 0; i < packageCount; i++) {
// if (matchList[i] == -1) {
// for (Integer integer : packages.get(i).list) {
// System.out.println(integer);
// }
// }
// }
getResult(matchList);
}
public boolean findArgumentPath(int packagePos, boolean[] pathMark, int[] matchList) {
List<Edge> packages = packageWithCleaner.get(packagePos); // 阿姨对应的任务订单
for (Edge edge : packages) {
// 只匹配不存在于路径中的任务包 from 为阿姨下标
int cleanerPos = edge.cleanerPos;
if (!pathMark[cleanerPos]) {
pathMark[cleanerPos] = true;
if (matchList[cleanerPos] == -1 || findArgumentPath(matchList[cleanerPos], pathMark, matchList)) {
matchList[cleanerPos] = packagePos;
return true;
}
}
}
return false;
}
public void getResult(int[] matchList) {
boolean[] resultPos = new boolean[packageCount];
for (int i = 0; i < cleanerCount; i++) {
if (matchList[i] != -1) {
resultPos[matchList[i]] = true;
String cleanerId = cleaners.get(i).id.toString();
List<Integer> orderIds = packages.get(matchList[i]).list;
for (Integer id : orderIds) {
String orderId = orderList.get(id).id.toString();
try {
MainFrame.addSet(orderId, cleanerId);
} catch (Exception e) {
System.out.println(e);
}
}
}
}
for (int i = 0; i < packageCount; i++) {
if (!resultPos[i])
System.out.println(i);
}
}
// 二分图初始化
public void initBipartiteGraph() {
for (int i = 0; i < packageCount; i++) {
List<Edge> cleanerList = new ArrayList<>();
for (int j = 0; j < cleanerCount; j++) {
Edge edge = judgeReachable(i, j);
if (edge.canArrive)
cleanerList.add(edge);
}
packageWithCleaner.add(cleanerList);
}
}
// i为第i个 package 列表,j为第j个阿姨,
public Edge judgeReachable(int i, int j) {
PackageDetail packageDetail = packages.get(i);
Cleaner cleaner = cleaners.get(j);
OrderDetail orderDetail = orderList.get(packageDetail.list.get(0));
double distance = getDistanceByPoint(cleaner.x, cleaner.y, orderDetail.x, orderDetail.y);
double time = distance / 15.0;
if (time + 0.5 > orderDetail.beginTime) {
return new Edge(j, 0, false);
}
double cleanerScore = cleaner.score * packageDetail.list.size() / orderCount;
double distanceScore = 0.5 * (packageDetail.totalDistance / (orderCount * 15.0));
double weightTimeScore = 0.25 * (time + 0.5 + packageDetail.waitTime) / orderCount;
double weight = cleanerScore - distanceScore - weightTimeScore;
return new Edge(j, weight, true);
}
public Pair getFinishTimeByPoint(OrderDetail a, OrderDetail b) {
double distance = getDistanceByPoint(a.x, a.y, b.x, b.y);
double time = a.beginTime + a.unitTime + distance / 15.0;
if (time >= b.beginTime)
return new Pair(-1.0, -1.0, 0.0);
return new Pair(b.beginTime + b.unitTime, b.beginTime - time, distance);
}
public Double getDistanceByPoint(Long x1, Long y1, Long x2, Long y2) {
return Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)) / 1000;
}
public String[] readFile(String filePath) {
File file = new File(filePath);
long fileLength = file.length(); // 获取文件长度
byte[] fileContent = new byte[(int) fileLength];
try (FileInputStream in = new FileInputStream(file)) {
int read = in.read(fileContent);
} catch (IOException e) {
System.out.println(e);
}
return new String(fileContent).split("\n");
}
public void getOrderList(String filePath) {
String[] strings = readFile(filePath);
for (String lineInfo : strings) {
String[] split = lineInfo.split(",");
orderList.add(new OrderDetail(Integer.valueOf(split[0]), Long.valueOf(split[1]), Integer.valueOf(split[2]), Long.valueOf(split[3]), Long.valueOf(split[4])));
}
Collections.sort(orderList);
orderCount = orderList.size();
}
public void getCleanerList(String filePath) {
String[] strings = readFile(filePath);
for (String lineInfo : strings) {
String[] split = lineInfo.split(",");
cleaners.add(new Cleaner(Integer.valueOf(split[0]), Double.valueOf(split[1]), Long.valueOf(split[2]), Long.valueOf(split[3])));
}
Collections.sort(cleaners);
cleanerCount = cleaners.size();
cleanerWithPackages = new ArrayList<>(cleanerCount);
}
}
class PackageDetail {
public List<Integer> list = new ArrayList<>();
public Double waitTime = 0.0;
public Double totalDistance = 0.0;
}
class Pair {
public Double endTime;
public Double waitTime;
public Double distance;
public Pair(Double endTime, Double waitTime, Double distance) {
this.endTime = endTime;
this.waitTime = waitTime;
this.distance = distance;
}
}
class OrderDetail implements Comparable<OrderDetail> {
public Integer id;
public Double beginTime;
public Double unitTime; // 服务时长
public Double endTime; // 结束时间,对时间 * 2 处理,避免出现小数
public Long x;
public Long y;
public OrderDetail(Integer id, Long beginTime, Integer unitTime, Long x, Long y) {
this.id = id;
this.x = x;
this.y = y;
beginTime *= 1000;
// LocalDateTime date = LocalDateTime.parse(beginTime.toString());
Date date = new Date(beginTime);
int hour = date.getHours();
int minutes = date.getMinutes();
this.beginTime = hour + (minutes / 60.0);
this.unitTime = unitTime / 60.0;
// System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date));
this.endTime = this.beginTime + this.unitTime;
}
@Override
public int compareTo(OrderDetail o) {
if ((this.beginTime + this.unitTime) - (o.beginTime + o.unitTime) > 0)
return 1;
if ((this.beginTime + this.unitTime) - (o.beginTime + o.unitTime) == 0)
return 0;
return -1;
}
}
// 按分数排序
class Cleaner implements Comparable<Cleaner> {
public Integer id;
public Double score;
public Long x;
public Long y;
public Cleaner(Integer id, Double score, Long x, Long y) {
this.id = id;
this.score = score;
this.x = x;
this.y = y;
}
@Override
public int compareTo(Cleaner cleaner) {
if (cleaner.score > this.score) {
return 1;
} else if (cleaner.score.equals(this.score)) {
return 0;
} else {
return -1;
}
}
}
class Edge {
public int cleanerPos; // 与阿姨相连的package包的下标
public double weight;
public boolean canArrive = false;
public Edge(int to, double weight, boolean canArrive) {
this.cleanerPos = to;
this.weight = weight;
this.canArrive = canArrive;
}
}
宝剑锋从磨砺出 梅花香自苦寒来