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

题目描述

问题背景

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

输入输出示例

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

    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(正确)

总结

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

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

相关推荐
Jayden_Ruan2 小时前
C++计算正方形矩阵对角线和
数据结构·c++·算法
李白同学3 小时前
C++:list容器--模拟实现(下篇)
开发语言·数据结构·c++·windows·算法·list
一丢沙3 小时前
Verilog 硬件描述语言自学——重温数电之典型组合逻辑电路
开发语言·算法·fpga开发·verilog
竹子_234 小时前
《零基础入门AI:YOLOv2算法解析》
人工智能·python·算法·yolo
卡尔曼的BD SLAMer6 小时前
计算机视觉与深度学习 | 基于深度学习的图像特征提取与匹配算法综述及MATLAB实现
人工智能·深度学习·算法·计算机视觉·matlab
0wioiw07 小时前
算法(③二叉树)
算法
WHS-_-20228 小时前
Carrier Aggregation Enabled MIMO-OFDM Integrated Sensing and Communication
算法
闻缺陷则喜何志丹8 小时前
【有序集合 有序映射 懒删除堆】 3510. 移除最小数对使数组有序 II|2608
c++·算法·力扣·有序集合·有序映射·懒删除堆
cheniie9 小时前
网格纹理采样算法
算法