一道笔试题:有n个餐厅和m个外卖员,每个餐厅在某个时间点会产生一个外卖订单,这些订单都有产生时间,所需送达时间和优先级。外卖员在空闲的时候会选择最优的订单来配送

272 阅读5分钟

有n个餐厅和m个外卖员,每个餐厅在某个时间点会产生一个外卖订单,这些订单都有产生时间,所需送达时间和优先级。外卖员在空闲的时候会选择最优的订单来配送,直到所有订单都被送达。

这是规则:

1.每个餐厅的订单,优先级高的订单优先,其次是所需送达时间最短的订单,再次是产生时间最早的订单。

2.外卖员在空闲的时候会从所有餐厅的最高优先级选一个所需送达时间最短的订单配送,如果所需送达时间相同则选择餐厅编号最小的。输入:第一行输入三个n m p,p表示订单数,随后有p行,每行4个数字 分别是餐厅编号,产生时间,优先级和所需送达时间。

输出:p行,每行对应表示每个订单被送达的时间点。

给定的订单信息:

  • Order 1: 餐厅编号 1,产生时间 1,优先级 2,送达时间 5
  • Order 2: 餐厅编号 1,产生时间 4,优先级 3,送达时间 2
  • Order 3: 餐厅编号 2,产生时间 2,优先级 1,送达时间 4
  • Order 4: 餐厅编号 2,产生时间 5,优先级 2,送达时间 1

顺序是:优先级>送达时间>产生时间>餐厅编号 这里很容易想到需要一个优先级队列:PriorityQueue来处理:

private static final PriorityQueue<Order> orderQueue = new PriorityQueue<>(new Comparator<Order>() {
    @Override
    public int compare(Order o1, Order o2) {
        if (o1.priority != o2.priority) {
            return o2.priority - o1.priority; // 优先级高的优先
        } else if (o1.deliveryTime != o2.deliveryTime) {
            return o1.deliveryTime - o2.deliveryTime; // 所需送达时间短的优先
        } else if (o1.createTime != o2.createTime) {
            return o1.createTime - o2.createTime; // 产生时间早的优先
        } else {
            return o1.res_id - o2.res_id; // 餐厅编号小的优先
        }
    }
});

然后可以分析一下整个调度的过程,实际上是一个生产者消费者模型。餐厅是生产者,优先级队列就是我们的消息队列,外卖员就是消费者。 餐厅出单了,就把餐放到队列里,外卖小哥从队列里拿。模拟一下整个过程就是: 第一个单位的时间:餐厅1出餐了,order1做好了,这个时候队列里只有order1,假设是外卖小哥1去送餐。

image.png

在第2个时间到第六个单位的时间里外卖小哥1都在送餐。

第2个时间里,餐厅2已经做好了order3,外卖小哥2空闲,所以这单他去送,时间是2+4=6。

image.png 此处的甘特图是这样的:

image.png 在第六个时间里,外卖1和外卖2才能把餐送完。

在第四个时间和第五个时间,order2和order4都做完了,但是由于order2的优先级更高,所以order2会排在order4的前面。

image.png 等到时间6完了,两个外卖哥都空闲了,外卖哥1送order2,外卖哥2送order4。

image.png 最后的甘特图如下: image.png 总结一下: order1 送到需要时间6 order2 送到需要时间8 order3 送到需要时间6 order4 送到需要时间7

代码实现

这里最难的还是代码实现,我们要知道,这个餐一被放到队列里,外卖员就要去送了,他去送餐的时间里,只有另一个外卖员空闲。其实外卖员就像两个并发执行的线程。我们这里只能通过唤醒和睡眠来模拟外卖员的行动了,当队列里有餐,就唤醒外卖员去送餐,他送餐的过程就休眠线程。

import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Main {
    // 声明为静态变量,使得所有外卖员线程共享
    private static final PriorityQueue<Order> orderQueue = new PriorityQueue<>(new Comparator<Order>() {
        @Override
        public int compare(Order o1, Order o2) {
            if (o1.priority != o2.priority) {
                return o2.priority - o1.priority; // 优先级高的优先
            } else if (o1.deliveryTime != o2.deliveryTime) {
                return o1.deliveryTime - o2.deliveryTime; // 所需送达时间短的优先
            } else if (o1.createTime != o2.createTime) {
                return o1.createTime - o2.createTime; // 产生时间早的优先
            } else {
                return o1.res_id - o2.res_id; // 餐厅编号小的优先
            }
        }
    });

    // ReentrantLock 和 Condition 用于同步和条件等待
    private static final Lock lock = new ReentrantLock();
    private static final Condition notEmpty = lock.newCondition();

    
    static class Order {
        int id;           // 订单ID(输入顺序)
        int res_id;       // 餐厅编号
        int createTime;   // 创建时间
        int priority;     // 优先级
        int deliveryTime; // 送达时间

        public Order(int id, int res_id, int createTime, int priority, int deliveryTime) {
            this.id = id;
            this.res_id = res_id;
            this.createTime = createTime;
            this.priority = priority;
            this.deliveryTime = deliveryTime;
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt(); // 餐厅数量
        int m = sc.nextInt(); // 外卖员数量
        int p = sc.nextInt(); // 订单数量

        List<Order> orderList = new ArrayList<>();
        // 读订单并加入队列
        for (int i = 0; i < p; i++) {
            int res_id = sc.nextInt();
            int createTime = sc.nextInt();
            int priority = sc.nextInt();
            int deliveryTime = sc.nextInt();
            Order order = new Order(i, res_id, createTime, priority, deliveryTime);
            //将订单加入到订单队列中
            orderList.add(order);
        }
        //订单列表中的订单按照createTime排序
        orderList.sort(Comparator.comparingInt(order -> order.createTime));
        int[] deliveryTimes = new int[p];
        // 启动外卖员线程
        for (int i = 0; i < m; i++) {
            new Thread(() -> {
                int currentTime = 0; // 每个外卖员都有自己的当前时间
                while (true) {
                    Order order;
                    lock.lock();
                    try {
                        //当队列为空,没有订单了,让外卖员线程等待
                        while (orderQueue.isEmpty()) {
                            try {
                                notEmpty.await(); // 等待有新订单进入队列
                            } catch (InterruptedException e) {
                                Thread.currentThread().interrupt();
                                return;
                            }
                        }
                        //取出订单
                        order = orderQueue.poll();
                    } finally {
                        lock.unlock();
                    }
                    try {
                        // 更新外卖员的当前时间,确保时间是累积的
                        currentTime = Math.max(currentTime, order.createTime) + order.deliveryTime;
                        deliveryTimes[order.id] = currentTime;
                        System.out.println("Order " + order.id + " delivered at time " + currentTime);
                    } catch (Exception e) {
                        Thread.currentThread().interrupt();
                    }
                }
            }).start();
        }

        int currentTime = 0;//当前时间
        int index = 0;
        //模拟时间的推移
        while (index < orderList.size()) {
            lock.lock();
            try {
                // 只在订单的 createTime 等于当前时间时,将订单放入队列
                while (index < orderList.size() && orderList.get(index).createTime == currentTime) {
                    orderQueue.offer(orderList.get(index));
                    notEmpty.signal(); // 唤醒等待的线程
                    index++;
                }
            } finally {
                lock.unlock();
            }
            Thread.sleep(1000L); // 模拟时间的流逝
            currentTime++;
        }
    }
}