二分圖演算法
阿新 • • 發佈:2022-01-10
二分圖
// 阿姨派單
寶劍鋒從磨礪出 梅花香自苦寒來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; } }