贪心算法解决活动选择问题:最多不重叠活动数量求解

题目描述

问题背景

活动选择问题是贪心算法的经典应用场景之一。假设有若干个活动,每个活动都有独立的开始时间结束时间 ,且同一时间只能进行一个活动。要求从这些活动中选择出最大数量的不重叠活动,即任意两个选中的活动,前一个活动的结束时间不晚于后一个活动的开始时间。

输入输出示例

  • 输入 (开始时间与结束时间分两行输入,空格分隔,回车结束):

    plaintext

    复制代码
    1 3 0 5 8 5  // 活动开始时间
    2 4 6 7 9 9  // 活动结束时间
  • 输出

    plaintext

    复制代码
    最大不重叠活动数量: 4
  • 解释 :最优选择为活动 [1,2][3,4][5,7][8,9],共 4 个不重叠活动。

解题思路:贪心算法的核心逻辑

为什么选择贪心算法?

活动选择问题的最优解具有 "贪心选择性质"------ 每次选择最早结束的活动,能为后续活动预留最多的时间,从而最大化最终选择的活动数量。这一策略无需回溯,直接通过局部最优选择即可得到全局最优解。

算法步骤

  1. 数据组织 :将每个活动的 "开始时间" 和 "结束时间" 组合成二维向量 vector<vector<int>>,每行代表一个活动(格式:[开始时间, 结束时间])。
  2. 排序 :按活动的结束时间升序排序(贪心策略的关键,确保优先选择早结束的活动)。
  3. 筛选不重叠活动
    • 初始选择第一个活动(最早结束的活动),记录其结束时间。
    • 遍历后续活动,若当前活动的 "开始时间 ≥ 上一个选中活动的结束时间",则选择该活动,并更新结束时间。
  4. 统计结果:记录最终选择的活动数量,即为答案。

完整 C++ 代码实现

cpp 复制代码
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

/**
 * @brief 计算最大不重叠活动数量(贪心算法)
 * @param activities 二维向量,每行存储一个活动的[开始时间, 结束时间]
 */
void selectMaxNonOverlappingActivities(vector<vector<int>>& activities) {
    // 边界处理:若没有活动,直接返回0
    if (activities.empty()) {
        cout << "最大不重叠活动数量: 0" << endl;
        return;
    }

    // 按活动结束时间升序排序(贪心算法核心:优先选早结束的活动)
    sort(activities.begin(), activities.end(), 
         [](const vector<int>& activity1, const vector<int>& activity2) {
             return activity1[1] < activity2[1]; // 按结束时间从小到大排序
         });

    int activityCount = 1;                  // 至少选择第一个活动
    int lastActivityEndTime = activities[0][1]; // 记录上一个选中活动的结束时间

    // 遍历剩余活动,筛选不重叠的活动
    for (size_t i = 1; i < activities.size(); ++i) {
        // 若当前活动的开始时间 ≥ 上一个活动的结束时间,说明不重叠
        if (activities[i][0] >= lastActivityEndTime) {
            activityCount++; // 选择当前活动
            lastActivityEndTime = activities[i][1]; // 更新结束时间为当前活动的结束时间
        }
    }

    // 输出结果
    cout << "最大不重叠活动数量: " << activityCount << endl;
}

int main() {
    vector<int> startTimes;  // 存储所有活动的开始时间
    vector<int> endTimes;    // 存储所有活动的结束时间
    vector<vector<int>> activities; // 存储所有活动([开始时间, 结束时间])
    int timeInput;           // 临时存储输入的时间值

    // 读取活动开始时间(空格分隔,回车结束)
    cout << "请输入所有活动的开始时间(空格分隔,回车结束):" << endl;
    while (cin >> timeInput) {
        startTimes.push_back(timeInput);
        // 检测到回车符,停止读取开始时间
        if (cin.peek() == '\n') {
            cin.ignore(); // 清空输入缓冲区的换行符,避免影响后续输入
            break;
        }
    }

    // 读取活动结束时间(空格分隔,回车结束)
    cout << "请输入所有活动的结束时间(空格分隔,回车结束):" << endl;
    while (cin >> timeInput) {
        endTimes.push_back(timeInput);
        if (cin.peek() == '\n') {
            break;
        }
    }

    // 输入合法性校验:开始时间和结束时间的数量必须一致
    if (startTimes.size() != endTimes.size()) {
        cerr << "输入错误:开始时间和结束时间的数量不匹配!" << endl;
        return 1; // 非0返回值表示程序异常退出
    }

    // 组合开始时间和结束时间,构建活动列表
    for (size_t i = 0; i < startTimes.size(); ++i) {
        activities.push_back({startTimes[i], endTimes[i]});
    }

    // 计算并输出最大不重叠活动数量
    selectMaxNonOverlappingActivities(activities);

    return 0;
}

代码细节解析

1. 边界处理与输入校验

  • 空活动判断:若输入为空(无任何活动),直接输出 0,避免数组越界。
  • 输入合法性校验:确保 "开始时间数量" 与 "结束时间数量" 一致,若不一致则提示错误并退出,避免后续逻辑异常。
  • 输入缓冲区清理 :使用 cin.ignore() 清除第一个输入后的换行符,防止换行符被第二个输入循环误读。

2. 排序逻辑(Lambda 表达式)

cpp

运行

复制代码
sort(activities.begin(), activities.end(), 
     [](const vector<int>& activity1, const vector<int>& activity2) {
         return activity1[1] < activity2[1];
     });
  • 使用 Lambda 表达式作为排序的自定义比较函数,简洁高效。
  • 按活动的结束时间(activity[1])升序排序,是贪心策略的核心 ------ 优先选择早结束的活动,为后续活动预留更多时间。

3. 活动筛选逻辑

  • 初始选择第一个活动(排序后最早结束的活动),activityCount 初始化为 1。
  • 遍历后续活动时,通过 activities[i][0] >= lastActivityEndTime 判断是否重叠:
    • 若满足条件:选择该活动,activityCount 加 1,并更新 lastActivityEndTime 为当前活动的结束时间。
    • 若不满足:跳过该活动,继续遍历下一个。

算法效率分析

时间复杂度 空间复杂度 说明
O(n log n) O(n) 时间复杂度由排序操作主导(sort 函数的时间复杂度为 O (n log n));空间复杂度用于存储活动列表,为 O (n)(n 为活动数量)。

该效率是活动选择问题的最优解 ------ 贪心算法无需额外的动态规划数组,在时间和空间上均优于其他解法。

测试用例验证

测试用例 1:常规输入(示例输入)

  • 开始时间:1 3 0 5 8 5
  • 结束时间:2 4 6 7 9 9
  • 排序后活动:[1,2]、[3,4]、[0,6]、[5,7]、[5,9]、[8,9]
  • 选中活动:[1,2]、[3,4]、[5,7]、[8,9]
  • 输出:4(正确)

测试用例 2:空输入

  • 开始时间:(直接回车)
  • 结束时间:(直接回车)
  • 输出:0(正确)

测试用例 3:所有活动重叠

  • 开始时间:1 2 3
  • 结束时间:4 5 6
  • 选中活动:[1,4]
  • 输出:1(正确)

测试用例 4:活动无重叠

  • 开始时间:1 3 5
  • 结束时间:2 4 6
  • 选中活动:[1,2]、[3,4]、[5,6]
  • 输出:3(正确)

总结

活动选择问题是贪心算法的典型应用,核心在于 "优先选择最早结束的活动" 这一局部最优策略。本文的实现通过清晰的数据组织、严格的输入校验和高效的排序筛选逻辑,确保了代码的正确性和可读性。

该解法不仅适用于经典的活动选择场景,还可扩展到类似问题(如会议安排、任务调度等),只需将 "活动" 替换为对应的场景实体(如会议、任务),逻辑完全复用。

相关推荐
江畔柳前堤8 小时前
大语言模型分布式训练:从并行策略到万卡工程的系统梳理
人工智能·分布式·深度学习·算法·目标检测·机器学习·语言模型
Forever Nore9 小时前
LeetCode 4 寻找两个正序数组的中位数 - 二分
算法·leetcode
罗西的思考10 小时前
【OpenClaw具身硬件】MiniClaw 阅读笔记---(1)基础
人工智能·算法·机器学习
蛋先生DX11 小时前
大模型参数存储格式揭秘:BF不是男朋友
深度学习·算法·llm
爱跳舞的烤冷面12 小时前
自学嵌入式第22天(数据结构——哈希)
数据结构·算法·哈希算法
猎嘤一号12 小时前
博弈论(Game Theory)的理论、算法与工程
人工智能·算法·安全·博弈论
月光船幽幽12 小时前
影子模式下保护 logits 不被修改
人工智能·python·算法
马拉AI12 小时前
腾讯开源 Agent 记忆系统,AI“换对话就忘”的问题有了新解法(附安装使用教程)
人工智能·算法·开源·科研
地平线开发者14 小时前
征程6工具链模型X86推理方式说明
算法
地平线开发者14 小时前
【征程6】校准量化中HistogramObserver解析
算法