解决 带时间窗口的取送问题 (PDPTW),即在特定时间段内,用同一辆车完成所有货物的取货和送货。目标是 减少完成所有订单所需的总工时。
换句话说,货物必须由一辆车从一个地方收集并运送到另一个地方。显然,还存在排序或优先级约束,以确保在交付站点之前访问收集站点。
此外,还需要考虑司机的休息时间,主要为:
- 每天最多驾驶时间:9小时。
- 每 4.5小时 驾驶后,需休息 45分钟(可以拆分为15分钟 + 30分钟)。
- 每 6小时 工作后,需休息至少 15分钟,工作6到9小时需 30分钟 休息。
其中,最大连续行驶时间 :4.5 小时;每日最大驾驶时间 :9 小时;最长路线时间 :13 小时。
在这里使用贪心算法作为核心,列举一下核心部分的代码(Java):
import org.json.JSONArray;
import org.json.JSONObject;
import java.io.*;
import java.text.SimpleDateFormat;
import java.util.*;
......
private static void parseInputData(JSONObject inputData) {
// Parsing order, vehicle and matrix data
JSONArray ordersData = inputData.getJSONArray("Orders");
JSONArray vehiclesData = inputData.getJSONArray("Vehicles");
JSONObject matrixData = inputData.getJSONObject("Matrix");
JSONArray locationsData = matrixData.getJSONArray("Locations");
int numLocations = locationsData.length();
int[][] timeMatrix = new int[numLocations][numLocations];
int[][] distanceMatrix = new int[numLocations][numLocations];
// Initialize the time and distance matrix with the diagonal set to 0 and the rest set to Integer.MAX_VALUE
for (int i = 0; i < numLocations; i++) {
for (int j = 0; j < numLocations; j++) {
if (i == j) {
timeMatrix[i][j] = 0;
distanceMatrix[i][j] = 0;
} else {
timeMatrix[i][j] = Integer.MAX_VALUE;
distanceMatrix[i][j] = Integer.MAX_VALUE;
}
}
}
// Parse Matrix.Data
JSONArray dataOuter = matrixData.getJSONArray("Data"); // outer array
if (dataOuter.length() > 0) {
JSONArray data = dataOuter.getJSONArray(0); // Get 2D matrix
for (int i = 0; i < data.length(); i++) {
JSONArray row = data.getJSONArray(i);
for (int j = 0; j < row.length(); j++) {
if (i == j) {
continue;
}
JSONArray cell = row.optJSONArray(j); // Use optJSONArray in case it's null.
if (cell == null || cell.isNull(0)) {
// Keep Integer.MAX_VALUE
continue;
} else {
if (cell.length() >= 2) {
int distance = cell.getInt(0);
int timeInSeconds = cell.getInt(1);
int timeInMinutes = timeInSeconds / 60; // 转换为分钟
distanceMatrix[i][j] = distance;
timeMatrix[i][j] = timeInMinutes;
} else {
// If there is only one value, assume it is the distance and the time is set to 0
int distance = cell.getInt(0);
distanceMatrix[i][j] = distance;
timeMatrix[i][j] = 0;
}
}
}
}
}
// Parsing order data to create a list of Order objects
List<Order> orders = new ArrayList<>();
for (int i = 0; i < ordersData.length(); i++) {
JSONObject orderJson = ordersData.getJSONObject(i);
int orderId = i; // Use the order's index as the unique ID
// Creating Collection Tasks
Customer collectionCustomer = new Customer(
orderJson.optString("CollectId", ""),
"",
orderJson.optInt("CollectSiteId", 0),
0,
orderJson.optString("EarliestCollect1", ""),
orderJson.optString("LatestCollect1", ""),
"",
"",
orderJson.optInt("CollectTimeInMinutes", 0),
0,
orderJson.optInt("Weight", 0),
orderJson.optInt("Priority", 1), // Default priority is 1
true,
orderId
);
// Creating Delivery Tasks
Customer deliveryCustomer = new Customer(
"",
orderJson.optString("DeliverId", ""),
0,
orderJson.optInt("DeliverSiteId", 0),
"",
"",
orderJson.optString("EarliestDeliver1", ""),
orderJson.optString("LatestDeliver1", ""),
0,
orderJson.optInt("DeliverTimeInMinutes", 0),
orderJson.optInt("Weight", 0),
orderJson.optInt("Priority", 1), // Default priority is 1
false,
orderId
);
// Only add orders with valid collection and delivery tasks
if (!collectionCustomer.collectId.isEmpty() && !deliveryCustomer.deliverId.isEmpty()) {
orders.add(new Order(orderId, collectionCustomer, deliveryCustomer));
}
}
// Sorting Collection and Delivery Tasks in an Order by Priority and Earliest Time
List<Customer> customers = new ArrayList<>();
for (Order order : orders) {
customers.add(order.collectionCustomer);
customers.add(order.deliveryCustomer);
}
Collections.sort(customers); // sort
// Parses vehicle data and creates a list of Vehicle objects.
List<Vehicle> vehicles = new ArrayList<>();
for (int i = 0; i < vehiclesData.length(); i++) {
JSONObject vehicleData = vehiclesData.getJSONObject(i);
Vehicle vehicle = new Vehicle(
vehicleData.getString("Id"),
vehicleData.optInt("StartSite", 0),
vehicleData.optString("StartTime", "2022-01-01T08:00:00"), // Default start time
vehicleData.optInt("EndSite", 0),
vehicleData.getJSONArray("VehicleCapacity").getJSONObject(0).getInt("Weight")
);
vehicles.add(vehicle);
}
// Initialize the state of all vehicles
List<VehicleState> vehicleStates = new ArrayList<>();
for (Vehicle v : vehicles) {
vehicleStates.add(new VehicleState(v)); // Create a vehicle state object for each vehicle and add it to the list
}
// Tracking of assigned and unassigned customers
Set<Customer> assignedCustomers = new HashSet<>();
List<Customer> unassignedCustomers = new ArrayList<>();
// Uses a priority queue that sorts and assigns customers based on their priority and earliest available time
PriorityQueue<Customer> customerQueue = new PriorityQueue<>(customers);
int sequenceNo = 1; // Define the sequence number, starting with 1
SimpleDateFormat timeFormatter = new SimpleDateFormat("HH:mm"); // Format time to hours:minutes
// Total consumption time in minutes
int overallTotalConsumedTime = 0;
// Start assigning customers
while (!customerQueue.isEmpty()) {
Customer customer = customerQueue.poll();
if (assignedCustomers.contains(customer)) continue;
// If it's a delivery mission, make sure at least one vehicle has completed the corresponding collection mission
if (!customer.isCollection) {
boolean hasCollected = false;
for (VehicleState vs : vehicleStates) {
if (vs.collectedOrderIds.contains(customer.orderId)) {
hasCollected = true;
break;
}
}
if (!hasCollected) {
// Unable to assign for now, try again later
unassignedCustomers.add(customer);
continue;
}
}
// Find the most appropriate vehicle to assign to that customer
VehicleState bestVehicle = null;
int bestJourneyTime = 0;
int bestDistance = 0;
int bestWaitTime = 0;
Date bestArrivalTime = null;
// Iterate through all the vehicles to find the most appropriate one
for (VehicleState vs : vehicleStates) {
// If it is a delivery mission, make sure the vehicle has completed the corresponding collection mission
if (!customer.isCollection && !vs.collectedOrderIds.contains(customer.orderId)) {
continue; // This vehicle has not yet completed the corresponding collection task, skip the
}
// Calculate travel time and distance
int targetLocation = customer.isCollection ? customer.collectSiteId : customer.deliverSiteId;
int journeyTime = calculateJourneyTime(vs.currentLocation, targetLocation, timeMatrix);
int distance = calculateDistance(vs.currentLocation, targetLocation, distanceMatrix);
if (journeyTime == 0 && vs.currentLocation != targetLocation) continue; // Invalid route, skip
// Calculate arrival time
Date arrivalTime = new Date(vs.currentTime.getTime() + journeyTime * 60 * 1000);
// Time Window for Customer Acquisition
Date windowStart = customer.isCollection ? customer.earliestCollect1 : customer.earliestDeliver1;
Date windowEnd = customer.isCollection ? customer.latestCollect1 : customer.latestDeliver1;
if (windowStart == null || windowEnd == null) {
// If no time window is specified, it is assumed to be feasible
windowStart = vs.currentTime;
windowEnd = new Date(vs.currentTime.getTime() + MAX_ROUTE_DURATION * 60 * 1000);
}
// Check if the arrival time is within the time window
if (arrivalTime.after(windowEnd)) {
continue; // Cannot be assigned to that vehicle within the time window
}
// If arrival time is earlier than the earliest time, you need to wait
int waitTime = 0;
if (arrivalTime.before(windowStart)) {
waitTime = (int) ((windowStart.getTime() - arrivalTime.getTime()) / (60 * 1000));
arrivalTime = windowStart;
}
// Calculation of end-of-service time
Date departureTime = new Date(arrivalTime.getTime() + (customer.isCollection ? customer.collectTimeInMinutes : customer.deliverTimeInMinutes) * 60 * 1000);
// Calculate new cumulative driving time and hours worked
int newTotalDrivingTime = vs.totalDrivingTime + journeyTime;
int serviceTime = customer.isCollection ? customer.collectTimeInMinutes : customer.deliverTimeInMinutes;
int newTotalWorkingTime = vs.totalWorkingTime + journeyTime + waitTime + serviceTime;
// Check if limits are exceeded
if (newTotalDrivingTime > MAX_DRIVING_TIME || newTotalWorkingTime > MAX_ROUTE_DURATION) {
continue; // Exceeds limits, cannot be distributed
}
// Choose the earliest arriving vehicle
if (bestVehicle == null || arrivalTime.before(bestArrivalTime)) {
bestVehicle = vs;
bestArrivalTime = arrivalTime;
bestJourneyTime = journeyTime;
bestDistance = distance;
bestWaitTime = waitTime;
}
}
if (bestVehicle != null) { // If the right vehicle is found
// If this is the first time the vehicle has been assigned a task, add the task “start”.
if (bestVehicle.assignments.isEmpty()) {
Assignment startAssignment = new Assignment(
bestVehicle.vehicle.id,
"Vehicle " + bestVehicle.vehicle.id + " start",
"0h0m",
timeFormatter.format(bestVehicle.currentTime),
"0h0m",
"0h0m",
"0h0m",
formatDateTime(bestVehicle.currentTime),
"",
"",
"",
"",
0
);
bestVehicle.assignments.add(startAssignment);
}
int targetLocation = customer.isCollection ? customer.collectSiteId : customer.deliverSiteId;
int journeyTime = calculateJourneyTime(bestVehicle.currentLocation, targetLocation, timeMatrix);
int distance = calculateDistance(bestVehicle.currentLocation, targetLocation, distanceMatrix);
Date arrivalTime = new Date(bestVehicle.currentTime.getTime() + journeyTime * 60 * 1000);
// Time Window for Customer Acquisition
Date windowStart = customer.isCollection ? customer.earliestCollect1 : customer.earliestDeliver1;
Date windowEnd = customer.isCollection ? customer.latestCollect1 : customer.latestDeliver1;
if (windowStart == null || windowEnd == null) {
windowStart = bestVehicle.currentTime;
windowEnd = new Date(bestVehicle.currentTime.getTime() + MAX_ROUTE_DURATION * 60 * 1000);
}
// If arrival time is earlier than the earliest time, you need to wait
int waitTime = 0;
if (arrivalTime.before(windowStart)) {
waitTime = (int) ((windowStart.getTime() - arrivalTime.getTime()) / (60 * 1000));
arrivalTime = windowStart;
}
// Calculation of end-of-service time
Date departureTime = new Date(arrivalTime.getTime() + (customer.isCollection ? customer.collectTimeInMinutes : customer.deliverTimeInMinutes) * 60 * 1000);
// Create and add task assignments
Assignment assignment = new Assignment(
bestVehicle.vehicle.id,
customer.isCollection ? customer.collectId : customer.deliverId, // JobId
formatTime(journeyTime), // JourneyTime
timeFormatter.format(arrivalTime), // ArrivalTime
formatTime(waitTime), // WaitTime
"0h0m", // DelayTime
formatTime(customer.isCollection ? customer.collectTimeInMinutes : customer.deliverTimeInMinutes), // ServiceTime
formatDateTime(departureTime), // DepartureTime
"", // Break1Time
"", // Break1Duration
"", // Break2Time
"", // Break2Duration
distance // Distance
);
bestVehicle.assignments.add(assignment);
// Update Vehicle Status
bestVehicle.currentTime = departureTime;
bestVehicle.currentLocation = targetLocation;
bestVehicle.totalDrivingTime += journeyTime + (customer.isCollection ? customer.collectTimeInMinutes : customer.deliverTimeInMinutes);
bestVehicle.totalWorkingTime += journeyTime + waitTime + (customer.isCollection ? customer.collectTimeInMinutes : customer.deliverTimeInMinutes);
assignedCustomers.add(customer);
overallTotalConsumedTime += journeyTime + waitTime + (customer.isCollection ? customer.collectTimeInMinutes : customer.deliverTimeInMinutes);
// If it is a collection task, record the order ID that has been collected
if (customer.isCollection) {
bestVehicle.collectedOrderIds.add(customer.orderId);
}
// Check if you need to rest
boolean needBreak = bestVehicle.totalDrivingTime >= MAX_CONTINUOUS_DRIVING;
if (needBreak) {
// If a break is needed, calculate the time window before the break
Date afterBreak = new Date(bestVehicle.currentTime.getTime() + (MIN_BREAK1_DURATION + MIN_BREAK2_DURATION) * 60 * 1000);
// Get the end time of the time window for the current task
Date windowEndCurrent = customer.isCollection ? customer.latestCollect1 : customer.latestDeliver1;
// If the time after the break would exceed the end of the customer's time window, the break is not inserted
if (afterBreak.after(windowEndCurrent)) {
// No scheduled breaks to continue the mission
continue;
}
// Otherwise, arrange for rest
String break1Time = timeFormatter.format(bestVehicle.currentTime);
String break1Duration = formatTime(MIN_BREAK1_DURATION);
String break2Time = timeFormatter.format(new Date(bestVehicle.currentTime.getTime() + MIN_BREAK1_DURATION * 60 * 1000 + MIN_BREAK2_DURATION * 60 * 1000));
String break2Duration = formatTime(MIN_BREAK2_DURATION);
// Get the latest Assignment object
Assignment lastAssignment = bestVehicle.assignments.get(bestVehicle.assignments.size() - 1);
lastAssignment.break1Time = break1Time;
lastAssignment.break1Duration = break1Duration;
lastAssignment.break2Time = break2Time;
lastAssignment.break2Duration = break2Duration;
// Update Vehicle Status
bestVehicle.currentTime = afterBreak; // Update the current time of the vehicle
bestVehicle.totalDrivingTime = 0; // Reset accumulated driving time
bestVehicle.totalWorkingTime += MIN_BREAK1_DURATION + MIN_BREAK2_DURATION; // Accumulation of rest time to working time
overallTotalConsumedTime += MIN_BREAK1_DURATION + MIN_BREAK2_DURATION; // Increase rest time to overall exertion time
}
} else {
// If there are no vehicles available to assign to the customer, add to the unassigned list
unassignedCustomers.add(customer);
}
}
// All vehicles return to the finish line
for (VehicleState vs : vehicleStates) {
if (!vs.assignments.isEmpty()) { // Processing of utilized vehicles only
// Calculate travel time and distance back to the end point
int journeyToEndTime = calculateJourneyTime(vs.currentLocation, vs.vehicle.endSite, timeMatrix); // Calculation of travel time back to the end point
int distanceToEnd = calculateDistance(vs.currentLocation, vs.vehicle.endSite, distanceMatrix); // Calculate the distance back to the end point
Date arrivalAtEnd;
if (journeyToEndTime > 0) {
arrivalAtEnd = new Date(vs.currentTime.getTime() + journeyToEndTime * 60 * 1000); // Arrival time at return destination
} else {
arrivalAtEnd = vs.currentTime; // Already at the finish line
}
// Total time consumed for updating
overallTotalConsumedTime += journeyToEndTime;
// Create and add an “end” task
Assignment endAssignment = new Assignment(
vs.vehicle.id,
"Vehicle " + vs.vehicle.id + " end",
formatTime(journeyToEndTime),
timeFormatter.format(arrivalAtEnd),
"0h0m",
"0h0m",
"0h0m",
formatDateTime(arrivalAtEnd),
"",
"",
"",
"",
distanceToEnd
);
vs.assignments.add(endAssignment);
}
}
// Print header rows and output column names
System.out.println("VehicleName,JobId,JourneyTime,ArrivalTime,WaitTime,DelayTime,ServiceTime,DepartureTime,Break1Time,Break1Duration,Break2Time,Break2Duration,Distance,SequenceNo");
// Initialize the serial number
sequenceNo = 1;
// Prints all the tasks for each vehicle in the order in which they are ordered
for (VehicleState vs : vehicleStates) {
if (!vs.assignments.isEmpty()) {
for (Assignment assignment : vs.assignments) {
assignment.print(sequenceNo++);
}
}
}
// Handling of unassigned customers
if (!unassignedCustomers.isEmpty()) {
System.out.println("Unassigned Customers:"); // Printing information on unassigned customers
for (Customer uc : unassignedCustomers) {
if (uc.isCollection) {
System.out.printf("Collection Task - Customer ID: %s Cannot be assigned to any vehicle within the time window.%n", uc.collectId);
} else {
System.out.printf("Delivery Task - Customer ID: %s Cannot be assigned to any vehicle within the time window or the collection task has not been assigned.%n", uc.deliverId);
}
}
}
// Total Printing Consumption Time
//System.out.printf("Overall Total Consumed Time: %s%n", formatTime(overallTotalConsumedTime));
}
这里就是算法的核心部分了,这里把消耗的总时间注释了,需要显示时间的把注释符号删去即可。