当前位置: 首页 > news >正文

活动选择问题一文详解

活动选择问题一文详解

    • 一、活动选择问题描述
      • 1.1 问题定义
      • 1.2 示例说明
    • 二、贪心算法求解策略
      • 2.1 贪心思想
      • 2.2 策略证明
      • 2.3 算法步骤
    • 三、代码实现
      • 3.1 Python 实现
      • 3.2 C++ 实现
      • 3.3 Java 实现
    • 四、复杂度分析
      • 4.1 时间复杂度
      • 4.2 空间复杂度
    • 五、应用拓展
      • 5.1 资源分配
      • 5.2 任务调度优化
      • 5.3 拓展问题
    • 总结

在项目管理/任务调度等实际场景中,如何在有限的时间资源下,完成尽可能多的任务是一个关键问题,活动选择问题(Activity Selection Problem)正是对这类场景的抽象建模,通过合理选择活动安排,实现资源利用的最大化。本文我将探讨活动选择问题的问题描述、贪心求解策略、多语言代码实现、复杂度分析等方面,帮你全面掌握这一经典算法问题。

一、活动选择问题描述

1.1 问题定义

假设有一系列活动集合 S = { a 1 , a 2 , ⋯ , a n } S = \{a_1, a_2, \cdots, a_n\} S={a1,a2,,an},每个活动 a i a_i ai都有一个开始时间 s i s_i si和结束时间 f i f_i fi,且 0 ≤ s i < f i < ∞ 0 \leq s_i < f_i < \infty 0si<fi< 。如果两个活动 a i a_i ai a j a_j aj的时间区间没有重叠(即 f i ≤ s j f_i \leq s_j fisj f j ≤ s i f_j \leq s_i fjsi),则称这两个活动是兼容的。活动选择问题的目标是从给定的活动集合中,选择出一个最大的兼容活动子集,使得该子集中的活动数量最多。
选择问题

1.2 示例说明

例如,给定以下活动集合:

活动 开始时间 s i s_i si结束时间 f i f_i fi
a 1 a_1 a11 4
a 2 a_2 a23 5
a 3 a_3 a30 6
a 4 a_4 a45 7
a 5 a_5 a53 8
a 6 a_6 a65 9
a 7 a_7 a76 10
a 8 a_8 a88 11
a 9 a_9 a98 12
a 10 a_{10} a102 13
a 11 a_{11} a1112 14

在这个例子中,最大兼容活动子集为 { a 1 , a 4 , a 8 , a 11 } \{a_1, a_4, a_8, a_{11}\} {a1,a4,a8,a11},共包含 4 个活动。

二、贪心算法求解策略

2.1 贪心思想

活动选择问题可以通过贪心算法高效求解。贪心算法的核心思想是在每一步选择中,都做出当前状态下看似最优的选择,希望通过局部最优选择最终得到全局最优解。对于活动选择问题,贪心策略是优先选择最早结束的活动。直观理解是,选择最早结束的活动,能够为后续活动腾出更多的时间,从而有更大的机会选择更多的活动。

2.2 策略证明

我们可以通过反证法证明选择最早结束的活动能得到全局最优解。假设存在一个最优解 A A A,其第一个活动不是最早结束的活动,设最早结束的活动为 a m a_m am。如果将 A A A中的第一个活动替换为 a m a_m am,由于 a m a_m am结束时间最早,那么替换后得到的活动集合 A ′ A' A仍然是兼容的,且活动数量不会减少。因此,选择最早结束的活动不会比其他选择更差,该贪心策略是正确的。

2.3 算法步骤

  1. 对活动按结束时间排序:将所有活动按照结束时间 f i f_i fi从小到大进行排序。排序后,最早结束的活动将排在集合的前面。

  2. 初始化选择集合:选择排序后第一个活动加入结果集合,并记录该活动的结束时间。

  3. 遍历剩余活动:从第二个活动开始依次遍历,对于每个活动,如果其开始时间大于等于上一个已选择活动的结束时间,则将该活动加入结果集合,并更新当前已选择活动的结束时间。

  4. 返回结果:遍历结束后,得到的结果集合即为最大兼容活动子集。

三、代码实现

3.1 Python 实现

def activity_selection(activities):"""使用贪心算法解决活动选择问题:param activities: 活动列表,每个元素为 (开始时间, 结束时间) 元组:return: 最大兼容活动子集"""activities.sort(key=lambda x: x[1])  # 按结束时间排序selected_activities = [activities[0]]last_finish_time = activities[0][1]for activity in activities[1:]:start_time, finish_time = activityif start_time >= last_finish_time:selected_activities.append(activity)last_finish_time = finish_timereturn selected_activities# 示例活动集合
activities = [(1, 4), (3, 5), (0, 6), (5, 7), (3, 8), (5, 9), (6, 10), (8, 11), (8, 12), (2, 13), (12, 14)]
result = activity_selection(activities)
print("最大兼容活动子集:", result)

3.2 C++ 实现

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;// 定义活动结构体
struct Activity {int start;int finish;
};// 比较函数,用于按结束时间排序
bool compare(const Activity& a, const Activity& b) {return a.finish < b.finish;
}// 活动选择函数
vector<Activity> activitySelection(vector<Activity>& activities) {sort(activities.begin(), activities.end(), compare);vector<Activity> selected_activities;selected_activities.push_back(activities[0]);int last_finish_time = activities[0].finish;for (size_t i = 1; i < activities.size(); i++) {if (activities[i].start >= last_finish_time) {selected_activities.push_back(activities[i]);last_finish_time = activities[i].finish;}}return selected_activities;
}int main() {vector<Activity> activities = {{1, 4}, {3, 5}, {0, 6}, {5, 7}, {3, 8}, {5, 9}, {6, 10}, {8, 11}, {8, 12}, {2, 13}, {12, 14}};vector<Activity> result = activitySelection(activities);cout << "最大兼容活动子集: ";for (const auto& activity : result) {cout << "(" << activity.start << ", " << activity.finish << ") ";}cout << endl;return 0;
}

3.3 Java 实现

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;class Activity {int start;int finish;public Activity(int start, int finish) {this.start = start;this.finish = finish;}
}public class ActivitySelection {public static List<Activity> activitySelection(Activity[] activities) {Arrays.sort(activities, Comparator.comparingInt(a -> a.finish));List<Activity> selectedActivities = new ArrayList<>();selectedActivities.add(activities[0]);int lastFinishTime = activities[0].finish;for (int i = 1; i < activities.length; i++) {if (activities[i].start >= lastFinishTime) {selectedActivities.add(activities[i]);lastFinishTime = activities[i].finish;}}return selectedActivities;}public static void main(String[] args) {Activity[] activities = {new Activity(1, 4), new Activity(3, 5), new Activity(0, 6), new Activity(5, 7),new Activity(3, 8), new Activity(5, 9), new Activity(6, 10), new Activity(8, 11),new Activity(8, 12), new Activity(2, 13), new Activity(12, 14)};List<Activity> result = activitySelection(activities);System.out.print("最大兼容活动子集: ");for (Activity activity : result) {System.out.print("(" + activity.start + ", " + activity.finish + ") ");}}
}

四、复杂度分析

4.1 时间复杂度

活动选择问题的时间复杂度主要由两部分组成:

  • 排序时间:对活动按结束时间排序,通常使用比较排序算法(如快速排序、归并排序),时间复杂度为 O ( n log ⁡ n ) O(n \log n) O(nlogn),其中 n n n是活动的数量。

  • 遍历时间:遍历排序后的活动列表,时间复杂度为 O ( n ) O(n) O(n)

因此,整体时间复杂度为 O ( n log ⁡ n + n ) = O ( n log ⁡ n ) O(n \log n + n) = O(n \log n) O(nlogn+n)=O(nlogn),排序操作是时间复杂度的主要影响因素。

4.2 空间复杂度

算法的空间复杂度主要取决于存储活动集合和结果集合所需的空间。在最坏情况下,需要存储所有活动,空间复杂度为 O ( n ) O(n) O(n)。此外,排序操作可能需要额外的辅助空间(如归并排序),但在使用原地排序算法(如快速排序)时,空间复杂度可以优化到 O ( 1 ) O(1) O(1)。因此,总体空间复杂度为 O ( n ) O(n) O(n)

五、应用拓展

5.1 资源分配

在资源分配场景中,如会议室安排、设备调度等,都可以将其抽象为活动选择问题。例如,有多个会议需要使用同一间会议室,每个会议都有开始时间和结束时间,通过活动选择算法可以确定最多能安排多少个会议,实现会议室资源的高效利用。

5.2 任务调度优化

在多任务处理系统中,当多个任务对同一资源(如 CPU 核心、内存等)有使用需求时,可将任务视为活动,任务的开始和结束时间对应资源占用的时间段。利用活动选择算法,可以选择出能够在给定时间段内执行的最大任务集合,提高系统的任务处理效率。

5.3 拓展问题

活动选择问题还有许多拓展变体,例如:

  • 加权活动选择问题:每个活动增加一个权重(如收益、重要程度等),目标是选择一个兼容活动子集,使得子集的总权重最大。这种情况下,贪心算法不再适用,通常需要使用动态规划算法求解。

  • 多个资源的活动选择问题:当存在多个资源时,每个活动需要占用一个或多个资源,且资源在不同时间的可用状态不同,此时问题的复杂度大幅增加,需要更复杂的算法进行处理。

总结

活动选择问题作为贪心算法的经典应用,通过优先选择最早结束的活动这一简单而有效的贪心策略,能够快速找到最大兼容活动子集。

That’s all, thanks for reading!
觉得有用就点个赞、收进收藏夹吧!关注我,获取更多干货~

http://www.xdnf.cn/news/782893.html

相关文章:

  • 【论文阅读】Dolphin: Document Image Parsing via Heterogeneous Anchor Prompting
  • CppCon 2014 学习:The Perils of Strict Aliasing
  • 业务材料——半导体行业MES系统核心功能工业协议AI赋能
  • 不确定性分析在LEAP能源-环境系统建模中的整合与应用
  • 论文中pdf图片文件太大怎么办
  • GPTBots在AI大语言模型应用中敏感数据匿名化探索和实践
  • 无人机自主降落论文解析
  • TypeScript 高级类型深度指南:从类型体操到实战设计
  • vue入门环境搭建及demo运行
  • 生成JavaDoc文档
  • 用 Vue 做一个轻量离线的“待办清单 + 情绪打卡”小工具
  • 项目课题——基于ESP32的智能插座
  • 华为数据之道 精读——【173页】读书笔记【附全文阅读】
  • VsCode 安装 Cline 插件并使用免费模型(例如 DeepSeek)
  • SQL进阶之旅 Day 13:CTE与递归查询技术
  • 3.2 HarmonyOS NEXT跨设备任务调度与协同实战:算力分配、音视频协同与智能家居联动
  • 【Harmony OS】数据存储
  • VScode自动添加指定内容
  • NLP学习路线图(二十一): 词向量可视化与分析
  • 大语言模型评测体系全解析(上篇):基础框架与综合评测平台
  • 虚荣虚无的对立统一
  • 电阻电容的选型
  • html基础01:前端基础知识学习
  • webstrom中git插件勾选提交部分文件时却出现提交全部问题怎么解决
  • SpringBoot3.2新特性:JdbcClient
  • Trae CN IDE自动生成注释功能测试与效率提升全解析
  • 在linux系统上搭建git服务器(ssh协议)
  • RTC实时时钟DS1338Z-33/PT7C433833WEX国产替代FRTC1338S
  • 【Kotlin】高阶函数Lambda内联函数
  • Elasticsearch | 如何将修改已有的索引字段类型并迁移数据