贪心算法是一种在每一步选择中都采取当前状态下最优的选择,从而希望导致结果是全局最优的算法策略。在线租赁问题(Greedy Algorithm for Online Rentals)是一个经典的贪心算法应用场景,下面我将从多个维度全面详细地讲解这个问题及其Java实现。
在线租赁问题可以描述为:假设你经营一家设备租赁公司,有若干台相同的设备可供出租。客户会在一段时间内陆续提出租赁请求,每个请求包含开始时间和结束时间。你的目标是接受尽可能多的租赁请求,使得这些请求在时间上不冲突。
给定:
目标:
对于这类区间调度问题,常见的贪心策略有:
经过分析,最早结束时间优先的策略可以产生最优解:
要证明最早结束时间优先策略的正确性:
import java.util.Arrays;
import java.util.Comparator;
import java.util.ArrayList;
import java.util.List;
class RentalRequest {
int id;
int start;
int end;
public RentalRequest(int id, int start, int end) {
this.id = id;
this.start = start;
this.end = end;
}
@Override
public String toString() {
return "Request " + id + ": [" + start + ", " + end + "]";
}
}
public class OnlineRentalScheduler {
// 贪心算法解决在线租赁问题
public static List<RentalRequest> scheduleRentals(RentalRequest[] requests) {
// 1. 按照结束时间排序
Arrays.sort(requests, new Comparator<RentalRequest>() {
@Override
public int compare(RentalRequest r1, RentalRequest r2) {
return r1.end - r2.end;
}
});
List<RentalRequest> selected = new ArrayList<>();
int lastEndTime = 0;
// 2. 选择不冲突的请求
for (RentalRequest req : requests) {
if (req.start >= lastEndTime) {
selected.add(req);
lastEndTime = req.end;
}
}
return selected;
}
public static void main(String[] args) {
// 示例请求
RentalRequest[] requests = {
new RentalRequest(1, 1, 4),
new RentalRequest(2, 3, 5),
new RentalRequest(3, 0, 6),
new RentalRequest(4, 5, 7),
new RentalRequest(5, 3, 8),
new RentalRequest(6, 5, 9),
new RentalRequest(7, 6, 10),
new RentalRequest(8, 8, 11),
new RentalRequest(9, 8, 12),
new RentalRequest(10, 2, 13),
new RentalRequest(11, 12, 14)
};
List<RentalRequest> scheduled = scheduleRentals(requests);
System.out.println("Selected Rental Requests:");
for (RentalRequest req : scheduled) {
System.out.println(req);
}
System.out.println("Total scheduled: " + scheduled.size());
}
}
对于给定的示例请求:
Request 1: [1, 4]
Request 2: [3, 5]
Request 3: [0, 6]
Request 4: [5, 7]
Request 5: [3, 8]
Request 6: [5, 9]
Request 7: [6, 10]
Request 8: [8, 11]
Request 9: [8, 12]
Request 10: [2, 13]
Request 11: [12, 14]
排序后:
Request 3: [0, 6]
Request 1: [1, 4]
Request 2: [3, 5]
Request 4: [5, 7]
Request 5: [3, 8]
Request 6: [5, 9]
Request 7: [6, 10]
Request 8: [8, 11]
Request 9: [8, 12]
Request 10: [2, 13]
Request 11: [12, 14]
贪心选择过程:
最终选择4个请求,这是最大可能的不冲突集合。
当有k个相同的租赁设备时,问题变为k-区间调度问题:
public static List<List<RentalRequest>> scheduleRentalsWithKResources(RentalRequest[] requests, int k) {
Arrays.sort(requests, Comparator.comparingInt(r -> r.end));
List<List<RentalRequest>> resources = new ArrayList<>();
for (int i = 0; i < k; i++) {
resources.add(new ArrayList<>());
}
int[] lastEndTimes = new int[k];
for (RentalRequest req : requests) {
for (int i = 0; i < k; i++) {
if (req.start >= lastEndTimes[i]) {
resources.get(i).add(req);
lastEndTimes[i] = req.end;
break;
}
}
}
return resources;
}
如果每个请求有不同的权重(利润),贪心算法不再适用,需要使用动态规划:
public static int maxWeightSchedule(RentalRequest[] requests) {
Arrays.sort(requests, Comparator.comparingInt(r -> r.end));
int n = requests.length;
int[] dp = new int[n + 1];
for (int i = 1; i <= n; i++) {
int profit = requests[i-1].end - requests[i-1].start; // 假设权重为持续时间
int prevCompatible = findLastNonConflict(requests, i);
dp[i] = Math.max(dp[i-1], (prevCompatible == -1 ? 0 : dp[prevCompatible]) + profit);
}
return dp[n];
}
private static int findLastNonConflict(RentalRequest[] requests, int index) {
int low = 0, high = index - 1;
while (low <= high) {
int mid = (low + high) / 2;
if (requests[mid].end <= requests[index-1].start) {
if (requests[mid+1].end <= requests[index-1].start) {
low = mid + 1;
} else {
return mid + 1;
}
} else {
high = mid - 1;
}
}
return -1;
}
当请求实时到达无法预先排序时,可以使用在线算法:
public class OnlineRentalScheduler {
private int lastEndTime = 0;
public boolean processRequest(RentalRequest request) {
if (request.start >= lastEndTime) {
lastEndTime = request.end;
return true;
}
return false;
}
}
在实际应用中,可能需要:
可能需要考虑:
对于大规模数据:
特性 | 贪心算法 | 动态规划 |
---|---|---|
时间复杂度 | O(n log n) | O(n²)或O(n log n) |
适用问题 | 具有贪心选择性质的问题 | 具有最优子结构的问题 |
加权支持 | 不支持 | 支持 |
实现复杂度 | 简单 | 较复杂 |
贪心算法:
回溯算法:
解决方案:修改冲突检测逻辑,跟踪每个资源的最后使用时间。
解决方案:为每种设备类型维护单独的调度列表。
解决方案:使用合适的数据结构(如TreeSet)来高效插入和查询。
贪心算法在在线租赁问题中提供了高效且简单的解决方案。通过选择最早结束的请求,算法能够最大化可接受的请求数量。Java实现展示了如何通过排序和线性扫描来解决这个问题。虽然贪心算法不能解决所有变种问题(如加权情况),但对于基本的区间调度问题,它是最优的选择。
关键要点: