二分图算法

二分图

// 阿姨派单

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;
    }
}

宝剑锋从磨砺出 梅花香自苦寒来
原文地址:https://www.cnblogs.com/GHzcx/p/15783756.html