贪心算法经典应用:活动选择问题(C++实现)

问题描述

假设有 n 个活动,每个活动都有开始时间和结束时间。我们的目标是选择尽可能多的活动,使得它们在时间上不冲突(即任意两个活动的时间区间不重叠)。

这是一个经典的贪心算法应用场景,也被称为"区间调度问题"。

解题思路

核心思想

在每一步都做出当前看起来最好的选择,最终得到全局最优解。

贪心策略选择

关键问题:按照什么标准来贪心选择?

可能的策略:

  1. 选择持续时间最短的活动
  2. 选择开始时间最早的活动
  3. 选择结束时间最早的活动 ✅

为什么选择结束时间最早的活动是最优的?

  • 直观理解:越早结束,留给后续活动的时间就越多

  • 形式化证明:假设存在一个最优解不包含最早结束的活动,我们可以用最早结束的活动替换其中某个活动,得到同样优或更优的解

    时间轴示例:

    a₁: |-----| (结束时间最早)
    aᵢ₁: |-------| (原最优解的第一个活动)
    aᵢ₂: |-------| (原最优解的第二个活动)

    关键点:

    • aᵢ₁ 与 aᵢ₂ 不冲突 → aᵢ₂.start ≥ aᵢ₁.finish
    • a₁.finish ≤ aᵢ₁.finish → aᵢ₂.start ≥ a₁.finish
    • 所以 a₁ 与 aᵢ₂ 也不冲突!

算法步骤

  1. 将所有活动按照结束时间升序排序
  2. 选择第一个活动(结束时间最早的)
  3. 从剩余活动中,选择开始时间不早于上一个已选活动结束时间的活动
  4. 重复步骤3,直到没有可选活动

时间复杂度

  • 排序:O(n log n)
  • 选择过程:O(n)
  • 总体:O(n log n)

C++ 实现

cpp 复制代码
#include <iostream>
#include <vector>
#include <algorithm>

// 活动结构体
struct Activity {
    int start;  // 开始时间
    int finish; // 结束时间
    int id;     // 活动编号(用于输出)
    
    // 构造函数
    Activity(int s, int f, int i) : start(s), finish(f), id(i) {}
};

// 比较函数:按结束时间升序排序
bool compareActivities(const Activity& a, const Activity& b) {
    return a.finish < b.finish;
}

// 贪心活动选择算法
std::vector<Activity> activitySelection(std::vector<Activity>& activities) {
    // 1. 按结束时间排序
    std::sort(activities.begin(), activities.end(), compareActivities);
    
    std::vector<Activity> selected;
    
    // 2. 选择第一个活动(结束时间最早的)
    selected.push_back(activities[0]);
    int lastSelectedIndex = 0;
    
    // 3. 贪心选择后续活动
    for (int i = 1; i < activities.size(); i++) {
        // 如果当前活动的开始时间 >= 上一个选中活动的结束时间
        if (activities[i].start >= activities[lastSelectedIndex].finish) {
            selected.push_back(activities[i]);
            lastSelectedIndex = i;
        }
    }
    
    return selected;
}

// 打印活动信息
void printActivities(const std::vector<Activity>& activities) {
    std::cout << "选中的活动:\n";
    std::cout << "活动ID\t开始时间\t结束时间\n";
    for (const auto& activity : activities) {
        std::cout << activity.id << "\t" << activity.start << "\t\t" << activity.finish << "\n";
    }
}

int main() {
    // 测试数据:每个活动的 {开始时间, 结束时间, ID}
    std::vector<Activity> activities = {
        Activity(1, 4, 1),
        Activity(3, 5, 2),
        Activity(0, 6, 3),
        Activity(5, 7, 4),
        Activity(3, 9, 5),
        Activity(5, 9, 6),
        Activity(6, 10, 7),
        Activity(8, 11, 8),
        Activity(8, 12, 9),
        Activity(2, 14, 10),
        Activity(12, 16, 11)
    };
    
    std::cout << "原始活动列表:\n";
    std::cout << "活动ID\t开始时间\t结束时间\n";
    for (const auto& act : activities) {
        std::cout << act.id << "\t" << act.start << "\t\t" << act.finish << "\n";
    }
    
    std::cout << "\n" << std::string(40, '-') << "\n";
    
    // 执行活动选择算法
    std::vector<Activity> selected = activitySelection(activities);
    
    // 输出结果
    printActivities(selected);
    std::cout << "\n总共选择了 " << selected.size() << " 个活动\n";
    
    return 0;
}

算法正确性证明(简要)

贪心选择性质:存在一个最优解包含最早结束的活动。

证明思路

  • 设 S 是一个最优解,其中第一个活动是 a_k(不是最早结束的 a_1)
  • 由于 a_1 结束时间 ≤ a_k 结束时间,所以用 a_1 替换 a_k 后,不会与 S 中其他活动冲突
  • 因此 S' = (S - {a_k}) ∪ {a_1} 也是一个最优解,且包含贪心选择

最优子结构:在做出贪心选择后,剩余子问题的最优解与贪心选择组合构成原问题的最优解。

运行结果示例

复制代码
原始活动列表:
活动ID	开始时间	结束时间
1	1		4
2	3		5
3	0		6
4	5		7
5	3		9
6	5		9
7	6		10
8	8		11
9	8		12
10	2		14
11	12		16

----------------------------------------
选中的活动:
活动ID	开始时间	结束时间
1	1		4
4	5		7
8	8		11
11	12		16

总共选择了 4 个活动

扩展思考

  1. 变种问题:如果活动有不同权重,目标是最大化总权重而不是活动数量,这时贪心算法不再适用,需要使用动态规划。

  2. 实际应用

    • 会议室调度
    • 任务调度
    • 广告插播安排
    • 课程安排
  3. 为什么贪心有效 :这个问题具有贪心选择性质最优子结构,这是贪心算法适用的关键条件。

相关推荐
好大哥呀2 分钟前
C++ IDE
开发语言·c++·ide
CS创新实验室11 分钟前
《计算机网络》深入学:海明距离与海明码
计算机网络·算法·海明距离·海明编码
WW_千谷山4_sch14 分钟前
MYOJ_10599:CSP初赛题单10:计算机网络
c++·计算机网络·算法
YuTaoShao34 分钟前
【LeetCode 每日一题】1458. 两个子序列的最大点积——(解法三)状态压缩
算法·leetcode·职场和发展
位东风42 分钟前
希尔排序(Shell Sort)详解
算法·排序算法
梵尔纳多1 小时前
绘制一个矩形
c++·图形渲染·opengl
AI科技星1 小时前
光速飞行器动力学方程的第一性原理推导、验证与范式革命
数据结构·人工智能·线性代数·算法·机器学习·概率论
橘颂TA1 小时前
【剑斩OFFER】算法的暴力美学——leetCode 946 题:验证栈序列
c++·算法·leetcode·职场和发展·结构与算法
闻缺陷则喜何志丹1 小时前
【状态机动态规划】3686. 稳定子序列的数量|1969
c++·算法·动态规划·力扣·状态机动态规划