关于贪心算法和端点选择:
贪心算法的核心思想是在每一步做出当前最优的选择,希望最终达到全局最优解。在涉及区间或范围的问题中,选择使用左端点还是右端点排序取决于具体的问题和贪心策略。
为什么有时使用右端点:
任务调度问题:
当你需要安排多个任务,并且希望在最短时间内完成尽可能多的任务时,按照右端点排序通常是一个好的选择。例如,你有多个会议,每个会议有开始和结束时间,想要安排最多的会议,按照会议结束时间(右端点)排序可以让你优先安排结束早的会议,为后续会议留出更多时间,这是一种贪心策略。
资源分配问题:
当资源有限且需要分配给多个任务或区间,并且希望尽快完成任务释放资源时,按照右端点排序可以帮助你优先处理那些能尽快结束的任务,从而使资源更快地得到释放,以安排更多任务。
为什么有时使用左端点:
区间合并问题:
当你需要合并重叠的区间,或者将区间分成不重叠的组时,按照左端点排序可能更方便。例如,将一系列区间合并成不重叠的组,按照左端点排序可以让你依次检查每个区间是否可以加入已有组,或者创建新组。
连续处理问题:
当需要按顺序处理任务或区间,且处理顺序从左到右,按照左端点排序可以确保你按正确顺序处理,根据左端点和已有组的关系进行决策。
示例:
按照右端点的任务调度问题:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
struct Interval {
int start, end;
};
bool cmp(const Interval& a, const Interval& b) {
if (a.end == b.end) return a.start < b.start;
return a.end < b.end;
}
int main() {
vector<Interval> intervals = {{1, 3}, {2, 5}, {3, 7}, {4, 8}, {5, 9}};
sort(intervals.begin(), intervals.end(), cmp);
int count = 1;
int end = intervals[0].end;
for (int i = 1; i < intervals.size(); ++i) {
if (intervals[i].start >= end) {
count++;
end = intervals[i].end;
}
}
cout << count << endl;
return 0;
}
按照左端点的区间合并问题:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
struct Interval {
int start, end;
};
bool cmp(const Interval& a, const Interval& b) {
if (a.start == b.start) return a.end < b.end;
return a.start < b.start;
}
int main() {
vector<Interval> intervals = {{1, 3}, {2, 6}, {8, 10}, {15, 18}};
sort(intervals.begin(), intervals.end(), cmp);
vector<Interval> merged;
merged.push_back(intervals[0]);
for (int i = 1; i < intervals.size(); ++i) {
if (intervals[i].start <= merged.back().end) {
merged.back().end = max(merged.back().end, intervals[i].end);
} else {
merged.push_back(intervals[i]);
}
}
cout << merged.size() << endl;
return 0;
}
总结:
贪心算法中使用左端点还是右端点取决于具体问题和贪心策略。
右端点排序常用于需要优先完成任务或释放资源的情况,而左端点排序常用于按顺序处理或合并区间的情况。