zoukankan      html  css  js  c++  java
  • 二分图算法

    二分图

    // 阿姨派单

    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;
        }
    }
    
    
    宝剑锋从磨砺出 梅花香自苦寒来
  • 相关阅读:
    吊打996,来了?!
    微软开源浏览器自动化工具Playwright for Python(附源码)
    从0到1开始建设安全测试体系
    网友爆料vivo将取消大小周,不降薪,官方证实消息属实
    认识了一个在华为任职的50岁程序员!
    漫画:什么是自动驾驶?
    中国十大杰出人物
    最难调试修复的 bug 是怎样的?
    “十年不升职多得是,四年算什么”
    appium+pytest实现APP并发测试
  • 原文地址:https://www.cnblogs.com/GHzcx/p/15783756.html
Copyright © 2011-2022 走看看