高级贪心算法
高级贪心算法通过做出局部最优选择来解决复杂的优化问题。这些技术包括区间调度、分数背包、哈夫曼编码和各种图算法,展示了贪心策略在竞赛编程中的强大威力。
区间调度问题
区间调度是经典的贪心问题,我们需要选择最多的不重叠区间。关键洞察是按结束时间排序,然后贪心地选择区间。
区间调度及其变体
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
struct Interval {
int start, end;
int weight; // 用于加权版本
Interval(int s, int e, int w = 1) : start(s), end(e), weight(w) {}
};
class IntervalScheduling {
public:
// 经典区间调度 - 最大不重叠区间数
static int maxIntervals(vector<Interval>& intervals) {
if (intervals.empty()) return 0;
// 按结束时间排序
sort(intervals.begin(), intervals.end(),
[](const Interval& a, const Interval& b) {
return a.end < b.end;
});
int count = 1;
int lastEnd = intervals[0].end;
for (int i = 1; i < intervals.size(); i++) {
if (intervals[i].start >= lastEnd) {
count++;
lastEnd = intervals[i].end;
}
}
return count;
}
// 加权区间调度的贪心近似
static int maxWeightGreedy(vector<Interval>& intervals) {
if (intervals.empty()) return 0;
// 按权重/长度比排序(贪心启发式)
sort(intervals.begin(), intervals.end(),
[](const Interval& a, const Interval& b) {
double ratioA = (double)a.weight / (a.end - a.start);
double ratioB = (double)b.weight / (b.end - b.start);
return ratioA > ratioB;
});
int totalWeight = 0;
vector<bool> used(intervals.size(), false);
for (int i = 0; i < intervals.size(); i++) {
if (used[i]) continue;
totalWeight += intervals[i].weight;
used[i] = true;
// 标记重叠区间为已使用
for (int j = i + 1; j < intervals.size(); j++) {
if (!used[j] && intervalsOverlap(intervals[i], intervals[j])) {
used[j] = true;
}
}
}
return totalWeight;
}
// 覆盖一个点的最少区间数
static int minIntervalsToPoint(vector<Interval>& intervals, int point) {
vector<Interval> covering;
// 找覆盖该点的区间
for (const auto& interval : intervals) {
if (interval.start <= point && point <= interval.end) {
covering.push_back(interval);
}
}
if (covering.empty()) return -1; // 不可能
// 按开始时间排序,然后按结束时间
sort(covering.begin(), covering.end(),
[](const Interval& a, const Interval& b) {
if (a.start != b.start) return a.start < b.start;
return a.end > b.end;
});
return 1; // 至少有一个区间覆盖该点
}
private:
static bool intervalsOverlap(const Interval& a, const Interval& b) {
return !(a.end <= b.start || b.end <= a.start);
}
};
int main() {
vector<Interval> intervals = {
Interval(1, 3, 5), Interval(2, 5, 8), Interval(3, 6, 3),
Interval(4, 7, 6), Interval(5, 8, 4), Interval(6, 9, 7)
};
cout << "区间 (开始, 结束, 权重):" << endl;
for (const auto& interval : intervals) {
cout << "(" << interval.start << ", " << interval.end << ", " << interval.weight << ") ";
}
cout << endl;
// 测试经典区间调度
vector<Interval> copy1 = intervals;
int maxCount = IntervalScheduling::maxIntervals(copy1);
cout << "\n最大不重叠区间数: " << maxCount << endl;
// 测试加权区间调度
vector<Interval> copy2 = intervals;
int maxWeight = IntervalScheduling::maxWeightGreedy(copy2);
cout << "最大权重(贪心近似): " << maxWeight << endl;
// 测试点覆盖
int point = 4;
vector<Interval> copy3 = intervals;
int minCover = IntervalScheduling::minIntervalsToPoint(copy3, point);
cout << "点 " << point << " 可以被 " << minCover << " 个区间覆盖" << endl;
return 0;
}区间调度问题展示了关键的贪心原则:按正确标准排序(最大区间数按结束时间)并做出导致全局最优解的局部最优选择。
分数背包问题
与0/1背包问题不同,分数背包允许取物品的一部分。按价值重量比排序的贪心方法给出最优解。
分数背包实现
#include <iostream>
#include <vector>
#include <algorithm>
#include <iomanip>
using namespace std;
struct Item {
int value, weight;
double ratio;
int index;
Item(int v, int w, int idx) : value(v), weight(w), index(idx) {
ratio = (double)value / weight;
}
};
class FractionalKnapsack {
public:
struct Solution {
double maxValue;
vector<double> fractions; // 每个物品取的比例
Solution(int n) : maxValue(0), fractions(n, 0) {}
};
static Solution solve(vector<Item>& items, int capacity) {
int n = items.size();
Solution solution(n);
// 按价值重量比降序排序
sort(items.begin(), items.end(),
[](const Item& a, const Item& b) {
return a.ratio > b.ratio;
});
int remainingCapacity = capacity;
for (const auto& item : items) {
if (remainingCapacity >= item.weight) {
// 取整个物品
solution.fractions[item.index] = 1.0;
solution.maxValue += item.value;
remainingCapacity -= item.weight;
} else if (remainingCapacity > 0) {
// 取物品的一部分
double fraction = (double)remainingCapacity / item.weight;
solution.fractions[item.index] = fraction;
solution.maxValue += item.value * fraction;
remainingCapacity = 0;
break;
}
}
return solution;
}
// 变体:多个背包
static vector<Solution> multipleKnapsacks(vector<Item>& items, vector<int>& capacities) {
vector<Solution> solutions;
// 按比率排序物品
sort(items.begin(), items.end(),
[](const Item& a, const Item& b) {
return a.ratio > b.ratio;
});
for (int capacity : capacities) {
vector<Item> availableItems = items; // 为这个背包复制
Solution solution = solve(availableItems, capacity);
solutions.push_back(solution);
}
return solutions;
}
};
// 带时间约束的连续背包
class TimeConstrainedKnapsack {
public:
struct Task {
int value, time;
double efficiency;
int index;
Task(int v, int t, int idx) : value(v), time(t), index(idx) {
efficiency = (double)value / time;
}
};
static double maxValueInTime(vector<Task>& tasks, int totalTime) {
// 按效率排序(单位时间价值)
sort(tasks.begin(), tasks.end(),
[](const Task& a, const Task& b) {
return a.efficiency > b.efficiency;
});
double maxValue = 0;
int remainingTime = totalTime;
for (const auto& task : tasks) {
if (remainingTime >= task.time) {
maxValue += task.value;
remainingTime -= task.time;
} else if (remainingTime > 0) {
// 部分任务完成
double fraction = (double)remainingTime / task.time;
maxValue += task.value * fraction;
break;
}
}
return maxValue;
}
};
int main() {
// 测试分数背包
vector<Item> items = {
Item(60, 10, 0), Item(100, 20, 1), Item(120, 30, 2)
};
int capacity = 50;
cout << "物品 (价值, 重量, 比率):" << endl;
for (const auto& item : items) {
cout << "Item " << item.index << ": (" << item.value << ", " << item.weight
<< ", " << fixed << setprecision(2) << item.ratio << ")" << endl;
}
FractionalKnapsack::Solution solution = FractionalKnapsack::solve(items, capacity);
cout << "\n背包容量: " << capacity << endl;
cout << "最大价值: " << fixed << setprecision(2) << solution.maxValue << endl;
cout << "物品取用比例:" << endl;
for (int i = 0; i < solution.fractions.size(); i++) {
if (solution.fractions[i] > 0) {
cout << "Item " << i << ": " << fixed << setprecision(2)
<< solution.fractions[i] * 100 << "%" << endl;
}
}
// 测试时间约束任务
vector<TimeConstrainedKnapsack::Task> tasks = {
TimeConstrainedKnapsack::Task(100, 20, 0),
TimeConstrainedKnapsack::Task(60, 10, 1),
TimeConstrainedKnapsack::Task(140, 40, 2)
};
int totalTime = 50;
cout << "\n任务 (价值, 时间, 效率):" << endl;
for (const auto& task : tasks) {
cout << "Task " << task.index << ": (" << task.value << ", " << task.time
<< ", " << fixed << setprecision(2) << task.efficiency << ")" << endl;
}
double maxTaskValue = TimeConstrainedKnapsack::maxValueInTime(tasks, totalTime);
cout << "\n在 " << totalTime << " 时间单位内的最大价值: "
<< fixed << setprecision(2) << maxTaskValue << endl;
return 0;
}分数背包展示了当问题具有贪心选择性质时贪心算法的威力。按价值重量比排序确保最优子结构。
关键要点
- 高级贪心算法需要仔细分析贪心选择性质和最优子结构
- 按正确标准排序至关重要 - 区间按结束时间,背包问题按比率
- 贪心算法通常为分数问题提供最优解,为离散问题提供良好近似
- 理解何时贪心有效与何时需要动态规划对竞赛编程至关重要