活动选择问题一文详解

article/2025/6/8 15:32:56

活动选择问题一文详解

    • 一、活动选择问题描述
      • 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.hkcw.cn/article/wBDahfxRXF.shtml

相关文章

xmake的简易学习

文章目录 1. xmake是什么2. 一个可执行程序3. 一个库文件4. 遍历文件用法5. 第三方库3.1 系统安装库3.2 独立库 6. 后续 由于前一篇博客的最后说要做一些rknn的优化&#xff0c;其实这个工作很早就完成了&#xff0c;但是我是使用 xmake这个来做我的工程的构建的&#xff0c;不…

【网络安全 | 信息收集】灯塔(资产收集工具)安装教程

文章目录 简介安装教程1.创建文件2.执行命令3.运行程序简介 ARL(Asset Reconnaissance Lighthouse)资产侦察灯塔系统,旨在快速侦察与目标关联的互联网资产,构建基础资产信息库。 协助甲方安全团队或者渗透测试人员有效侦察和检索资产,发现存在的薄弱点和攻击面。 其特性如…

TCP小结

1. 核心特性 面向连接&#xff1a;通过三次握手建立连接&#xff0c;四次挥手终止连接&#xff0c;确保通信双方状态同步。 TCP连接建立的3次握手 抓包&#xff1a; client发出连接请求&#xff1b; server回应client请求&#xff0c;并且同步发送syn连接&#xff1b; clien…

Ansys Zemax | 手机镜头设计 - 第 3 部分:使用 STAR 模块和 ZOS-API 进行 STOP 分析

附件下载 联系工作人员获取附件 该系列文章将讨论智能手机镜头模组设计的挑战&#xff0c;从概念、设计到制造和结构变形的分析。本文是四部分系列的第三部分&#xff0c;它涵盖了使用 Ansys Zemax OpticStudio Enterprise 版本提供的 STAR 技术对智能手机镜头进行自动的结构…

【Redis】set 类型

set 一. set 类型介绍二. set 命令sadd、smembers、sismemberscard、spop、srandmembersmove、srem集合间操作交集&#xff1a;sinter、sinterstore并集&#xff1a;sunion、sunionstore差集&#xff1a;sdiff、sdiffstore 三. set 命令小结四. set 内部编码方式五. set 使用场…

006网上订餐系统技术解析:打造高效便捷的餐饮服务平台

网上订餐系统技术解析&#xff1a;打造高效便捷的餐饮服务平台 在数字化生活方式普及的当下&#xff0c;网上订餐系统成为连接餐饮商家与消费者的重要桥梁。该系统以菜品分类、订单管理等模块为核心&#xff0c;通过前台展示与后台录入的分工协作&#xff0c;为管理员和会员提…

Python趣学篇:Pygame重现经典打砖块游戏

名人说&#xff1a;路漫漫其修远兮&#xff0c;吾将上下而求索。—— 屈原《离骚》 创作者&#xff1a;Code_流苏(CSDN)&#xff08;一个喜欢古诗词和编程的Coder&#x1f60a;&#xff09; 专栏介绍&#xff1a;《Python星球日记》 目录 一、游戏背景与技术选型1. 打砖块游戏…

Transformer学习资料

​​核心论文​​ 原论文标题&#xff1a;《Attention Is All You Need》&#xff08;Transformer原始论文&#xff09; ​​Transformer学习资源​​ 视频教程&#xff1a; B站中文视频&#xff1a;Transformer详解 中文教程&#xff1a; GitHub项目&#xff1a;learn-nlp-wi…

AIGC 基础篇 高等数学篇 02导数与微分

声明&#xff1a;本文章仅用于复习&#xff0c;请不要将本文章当做预习篇或者讲解篇 此外&#xff0c;此文章不会包含全部的高等数学知识&#xff0c;仅仅是为了学习AI而进行的前期学习&#xff0c;因此知识含量不会很多&#xff0c;另外补充一句&#xff0c;博主已经对上一篇…

MQTTX连接阿里云的物联网配置

本文的目标是通过MQTTX的客户端&#xff0c;连接到阿里云的物联网的平台&#xff0c;发送温度信息&#xff0c;在阿里云的平台中显示出来。阿里云免费注册&#xff0c;免费有一个MQTT的服务器。有数量限制&#xff0c;但是对于测试来讲&#xff0c;已经足够。 1、注册阿里云的物…

06-排序

排序 1. 排序的概念及其应用 1.1 排序的概念 排序&#xff1a;所谓排序&#xff0c;就是使一串记录&#xff0c;按照其中的某个或某些关键字的大小&#xff0c;递增或递减的排列起来的操作。 稳定性&#xff1a;假定在待排序的记录序列中&#xff0c;存在多个具有相同的关键…

MS1023/MS1224——10MHz 到 80MHz、10:1 LVDS 并串转换器(串化器)/串并转换器(解串器)

产品简述 MS1023 串化器和 MS1224 解串器是一对 10bit 并串 / 串并转 换芯片&#xff0c;用于在 LVDS 差分底板上传输和接收 10MHz 至 80MHz 的并行字速率的串行数据。起始 / 停止位加载后&#xff0c;转换为负载编 码输出&#xff0c;串行数据速率介于 120Mbps…

Cyber Weekly #58

赛博新闻 1、DeepSeek新版R1更新&#xff0c;幻觉率大幅降低 5月28日&#xff0c;DeepSeek-R1模型已升级至DeepSeek-R1-0528版本&#xff0c;核心在于显著提升模型的思维深度与推理能力。该版本基于DeepSeek V3 Base模型&#xff0c;通过强化后训练显著优化了在数学、编程及通…

换一条宽带ip地址会变吗?同一个宽带如何不同ip地址

宽带IP地址是否变化取决于更换的方式&#xff0c;以及你使用的是公网IP还是内网IP。以下是具体分析&#xff0c;并附上同一个宽带下切换IP的实用方法&#xff1a; &#x1f310; 一、更换宽带是否会改变IP地址&#xff1f; 1. 更换宽带线路&#xff08;如从电信换到移动&#x…

环境对象以及回调函数

1.环境对象 2.回调函数

SQL Indexes(索引)

目录 Indexes Using Clustered Indexes Using Nonclustered Indexes Declaring Indexes Using Indexes Finding Rows Without Indexes Finding Rows in a Heap with a Nonclustered Index Finding Rows in a Clustered Index Finding Rows in a Clustered Index with …

graphviz, dot, Error: lost rA sA edge; 独立的模块

1) 有向图dot文件 digraph R { node [shaperecord]; { ranksame rA sA tA } { ranksame uB vB wB } rA -> sA; sA -> vB; t -> rA; uB -> vB; wB -> u; wB -> tA; } 2&#xff09;出现报警信息 Warning: flat edge between adjacent …

SpringBoot接入Kimi实践记录轻松上手

kimi简单使用 什么是Kimi API 官网&#xff1a;https://platform.moonshot.cn/ Kimi API 并不是一个我所熟知的广泛通用的术语。我的推测是&#xff0c;你可能想问的是关于 API 的一些基础知识。API&#xff08;Application Programming Interface&#xff0c;应用程序编程接…

Windows版PostgreSQL 安装 vector 扩展

问题 spring-ai在集成PGVector向量存储的时候会报错如下&#xff0c;那么就需要安装pgsql的vector扩展。 SQL [CREATE EXTENSION IF NOT EXISTS vector]; 错误: 无法打开扩展控制文件 "C:/Program Files/PostgreSQL/9.6/share/extension/vector.control": No such …

【操作系统原理08】文件管理

文章目录 零.大纲一.文件管理0.大纲1.文件管理1.1 **文件属性**1.2 文件内部数据组织1.3 文件之间的组织1.4操作系统提供功能1.5 文件在外存存放 二.文件的逻辑结构0.大纲1.无结构文件2.有结构文件 三.文件目录0.大纲1.文件控制块2.目录结构3.索引节点(FCB改进) 四.文件共享0.大…