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

贪心算法应用:带权任务间隔调度问题详解

在这里插入图片描述

贪心算法应用:带权任务间隔调度问题详解

贪心算法是一种在每一步选择中都采取在当前状态下最好或最优(即最有利)的选择,从而希望导致结果是全局最好或最优的算法。带权任务间隔调度问题是贪心算法的一个经典应用场景。

问题定义

**带权任务间隔调度问题(Weighted Interval Scheduling Problem)**描述如下:

  • 给定一组任务,每个任务有一个开始时间、结束时间和权重(价值)
  • 目标:选择一组互不重叠的任务,使得这些任务的总权重最大

与普通间隔调度问题不同之处在于,这里每个任务有不同的权重,我们需要考虑权重而不仅仅是任务数量。

问题分析

输入表示

每个任务可以表示为三元组 (start, end, weight)

class Task {int start;int end;int weight;public Task(int start, int end, int weight) {this.start = start;this.end = end;this.weight = weight;}
}

关键概念

  1. 兼容任务:两个任务如果它们的执行时间不重叠,则称为兼容任务
  2. p(j):对于任务j,p(j)是最大的i < j且任务i与任务j兼容的任务索引

贪心算法解决方案

1. 动态规划解法(非贪心)

首先我们看一下动态规划解法,以便与贪心算法对比:

public int weightedIntervalScheduling(Task[] tasks) {// 按结束时间排序Arrays.sort(tasks, (a, b) -> a.end - b.end);int n = tasks.length;int[] dp = new int[n + 1];for (int j = 1; j <= n; j++) {int i = latestNonOverlapping(tasks, j - 1);int weight = tasks[j - 1].weight;dp[j] = Math.max(weight + (i != -1 ? dp[i + 1] : 0), dp[j - 1]);}return dp[n];
}private int latestNonOverlapping(Task[] tasks, int j) {for (int i = j - 1; i >= 0; i--) {if (tasks[i].end <= tasks[j].start) {return i;}}return -1;
}

这种方法时间复杂度为O(n²),我们可以用贪心算法优化。

2. 贪心算法优化

贪心算法的关键在于如何选择任务。我们可以通过以下步骤实现:

  1. 将所有任务按照结束时间排序
  2. 使用二分查找快速找到p(j)
  3. 使用记忆化或自底向上的动态规划

优化后的Java实现:

import java.util.Arrays;
import java.util.Comparator;public class WeightedIntervalScheduling {static class Task {int start;int end;int weight;public Task(int start, int end, int weight) {this.start = start;this.end = end;this.weight = weight;}}public static int schedule(Task[] tasks) {// 1. 按结束时间排序Arrays.sort(tasks, Comparator.comparingInt(a -> a.end));int n = tasks.length;int[] dp = new int[n + 1];// 预处理p数组,p[j]表示在任务j之前最后一个不与j冲突的任务索引int[] p = new int[n];for (int j = 0; j < n; j++) {p[j] = binarySearch(tasks, j);}// 动态规划填表dp[0] = 0;for (int j = 1; j <= n; j++) {int include = tasks[j - 1].weight;if (p[j - 1] != -1) {include += dp[p[j - 1] + 1];}dp[j] = Math.max(include, dp[j - 1]);}return dp[n];}// 使用二分查找找到最后一个不与tasks[j]冲突的任务private static int binarySearch(Task[] tasks, int j) {int low = 0;int high = j - 1;int key = tasks[j].start;int result = -1;while (low <= high) {int mid = (low + high) / 2;if (tasks[mid].end <= key) {result = mid;low = mid + 1;} else {high = mid - 1;}}return result;}public static void main(String[] args) {Task[] tasks = {new Task(1, 3, 5),new Task(2, 5, 6),new Task(4, 6, 5),new Task(6, 7, 4),new Task(5, 8, 11),new Task(7, 9, 2)};System.out.println("Maximum weight: " + schedule(tasks));}
}

3. 算法复杂度分析

  1. 排序:O(n log n)
  2. 预处理p数组:n次二分查找,每次O(log n),总共O(n log n)
  3. 动态规划填表:O(n)

总时间复杂度:O(n log n),比纯动态规划的O(n²)更高效。

贪心选择性质证明

贪心算法的正确性需要证明其具有贪心选择性质和最优子结构性质。

贪心选择性质:全局最优解可以通过局部最优(贪心)选择达到。

对于带权任务间隔调度问题:

  1. 设任务集按结束时间排序为1, 2, …, n
  2. 设O是最优解,且任务j是O中结束时间最早的任务
  3. 如果任务j不在贪心解中,可以用j替换O中第一个任务,不会降低总权重
  4. 因此存在包含任务j的最优解

最优子结构性质:问题的最优解包含子问题的最优解。

在选择任务j后,剩余问题是求解在j结束后开始的任务集合的最大权重调度,这正是原问题的子问题。

变种与扩展

1. 输出具体任务序列

除了计算最大权重,我们还可以记录具体选择了哪些任务:

public static List<Task> scheduleWithTasks(Task[] tasks) {Arrays.sort(tasks, Comparator.comparingInt(a -> a.end));int n = tasks.length;int[] dp = new int[n + 1];int[] choice = new int[n + 1]; // 记录选择int[] p = new int[n];for (int j = 0; j < n; j++) {p[j] = binarySearch(tasks, j);}dp[0] = 0;for (int j = 1; j <= n; j++) {int include = tasks[j - 1].weight;if (p[j - 1] != -1) {include += dp[p[j - 1] + 1];}if (include > dp[j - 1]) {dp[j] = include;choice[j] = 1; // 选择任务j} else {dp[j] = dp[j - 1];choice[j] = 0; // 不选择任务j}}// 回溯找出选择的任务List<Task> result = new ArrayList<>();int j = n;while (j > 0) {if (choice[j] == 1) {result.add(tasks[j - 1]);j = p[j - 1] + 1; // 跳到前一个兼容任务} else {j--;}}Collections.reverse(result);return result;
}

2. 资源分配扩展

当有多个资源(如多个会议室)时,问题变为区间图着色问题,可以使用贪心算法的最早结束时间策略,配合资源计数来解决。

实际应用场景

带权任务间隔调度算法在实际中有广泛应用:

  1. 会议室安排:选择最有价值的会议组合
  2. 作业调度:CPU任务调度,选择权重最高的任务组合
  3. 广告投放:在有限时间段选择最有价值的广告组合
  4. 课程安排:安排最有价值且不冲突的课程组合

性能优化技巧

  1. 预处理优化:提前计算并存储p(j)数组
  2. 记忆化搜索:使用递归+记忆化代替纯递归
  3. 迭代实现:使用迭代而非递归避免栈溢出
  4. 空间优化:只存储必要的DP状态,减少空间复杂度

与其他算法对比

  1. 与回溯算法对比

    • 回溯:时间复杂度O(2ⁿ),可以找到所有解
    • 贪心:时间复杂度O(n log n),只能找到一种最优解
  2. 与动态规划对比

    • 动态规划:通常需要填表,可能时间复杂度较高
    • 贪心:利用问题特性优化,通常更高效
  3. 与线性规划对比

    • 线性规划:更通用但实现复杂
    • 贪心:针对特定问题,实现简单高效

常见错误与陷阱

  1. 排序标准错误:必须按结束时间排序,而不是开始时间或权重
  2. 权重处理不当:不能简单地选择权重最大的任务,需要考虑兼容性
  3. 边界条件处理:注意空任务集和全冲突任务集的特殊情况
  4. 索引处理错误:在实现p(j)时容易出现的off-by-one错误

测试用例设计

好的测试用例应包括:

public class WeightedIntervalSchedulingTest {@Testpublic void testEmptyTasks() {Task[] tasks = {};assertEquals(0, WeightedIntervalScheduling.schedule(tasks));}@Testpublic void testSingleTask() {Task[] tasks = {new Task(1, 3, 5)};assertEquals(5, WeightedIntervalScheduling.schedule(tasks));}@Testpublic void testAllTasksConflict() {Task[] tasks = {new Task(1, 5, 3),new Task(2, 6, 4),new Task(3, 7, 2)};assertEquals(4, WeightedIntervalScheduling.schedule(tasks));}@Testpublic void testNoTasksConflict() {Task[] tasks = {new Task(1, 3, 5),new Task(4, 6, 3),new Task(7, 9, 2)};assertEquals(10, WeightedIntervalScheduling.schedule(tasks));}@Testpublic void testMixedCase() {Task[] tasks = {new Task(1, 3, 5),new Task(2, 5, 6),new Task(4, 6, 5),new Task(6, 7, 4),new Task(5, 8, 11),new Task(7, 9, 2)};assertEquals(16, WeightedIntervalScheduling.schedule(tasks));}
}

总结

带权任务间隔调度问题展示了贪心算法在解决特定类型优化问题时的强大能力。通过合理的问题分析和算法设计,我们可以将看似复杂的O(2ⁿ)问题转化为O(n log n)的高效解决方案。关键在于:

  1. 识别问题的贪心选择性质
  2. 设计合适的排序和选择策略
  3. 结合动态规划避免重复计算
  4. 正确处理边界条件和特殊情况

Java实现时需要注意任务表示、排序、二分查找优化等细节,以确保算法的高效性和正确性。

更多资源:

https://www.kdocs.cn/l/cvk0eoGYucWA

本文发表于【纪元A梦】!

相关文章:

贪心算法应用:带权任务间隔调度问题详解

贪心算法应用&#xff1a;带权任务间隔调度问题详解 贪心算法是一种在每一步选择中都采取在当前状态下最好或最优&#xff08;即最有利&#xff09;的选择&#xff0c;从而希望导致结果是全局最好或最优的算法。带权任务间隔调度问题是贪心算法的一个经典应用场景。 问题定义…...

用电脑控制keysight示波器

KEYSIGHT示波器HD304MSO性能 亮点&#xff1a; 体验 200 MHz 至 1 GHz 的带宽和 4 个模拟通道。与 12 位 ADC 相比&#xff0c;使用 14 位模数转换器 &#xff08;ADC&#xff09; 将垂直分辨率提高四倍。使用 10.1 英寸电容式触摸屏轻松查看和分析您的信号。捕获 50 μVRMS …...

LLaMA-Factory - 批量推理(inference)的脚本

scripts/vllm_infer.py 是 LLaMA-Factory 团队用于批量推理&#xff08;inference&#xff09;的脚本&#xff0c;基于 vLLM 引擎&#xff0c;支持高效的并行推理。它可以对一个数据集批量生成模型输出&#xff0c;并保存为 JSONL 文件&#xff0c;适合大规模评测和自动化测试。…...

React从基础入门到高级实战:React 高级主题 - 测试进阶:从单元测试到端到端测试的全面指南

React 测试进阶&#xff1a;从单元测试到端到端测试的全面指南 引言 在2025年的React开发环境中&#xff0c;测试不仅是代码质量的保障&#xff0c;更是提升开发效率和用户体验的关键支柱。随着React应用的复杂性不断增加&#xff0c;高级测试技术——如端到端&#xff08;E2…...

Ansible 剧本精粹 - 编写你的第一个 Playbook

Ansible 剧本精粹 - 编写你的第一个 Playbook 如果说 Ansible Ad-Hoc 命令像是你对厨房里的助手发出的零散口头指令(“切个洋葱”、“烧开水”),那么 Playbook 就是一份完整、详细、写在纸上的菜谱。它列明了所有需要的“食材”(变量),详细的“烹饪步骤”(任务),甚至还…...

【Elasticsearch】Elasticsearch 核心技术(二):映射

Elasticsearch 核心技术&#xff08;二&#xff09;&#xff1a;映射 1.什么是映射&#xff08;Mapping&#xff09;1.1 元字段&#xff08;Meta-Fields&#xff09;1.2 数据类型 vs 映射类型1.2.1 数据类型1.2.2 映射类型 2.实际运用案例案例 1&#xff1a;电商产品索引映射案…...

【计算机网络】网络层协议

1. ICMP协议的介绍及应用 IP协议的助手 —— ICMP 协议 ping 是基于 ICMP 协议工作的&#xff0c;所以要明白 ping 的工作&#xff0c;首先我们先来熟悉 ICMP 协议。 ICMP 全称是 Internet Control Message Protocol&#xff0c;也就是互联网控制报文协议。 里面有个关键词 …...

.NET Core接口IServiceProvider

.NET Core 接口 IServiceProvider 深度剖析 在 .NET Core 和 .NET 5 的世界里&#xff0c;依赖注入&#xff08;Dependency Injection&#xff0c;简称 DI&#xff09;是构建可维护、可测试应用程序的关键技术。而 IServiceProvider 接口&#xff0c;正是依赖注入机制中的核心…...

结构型设计模式之Proxy(代理)

结构型设计模式之Proxy&#xff08;代理&#xff09; 前言&#xff1a; 代理模式&#xff0c;aop环绕通知&#xff0c;动态代理&#xff0c;静态代理 都是代理的一种&#xff0c;这次主要是记录设计模式的代理demo案例&#xff0c;详情请看其他笔记。 1&#xff09;意图 为其…...

案例分享--汽车制动卡钳DIC测量

制动系统是汽车的主要组成部分&#xff0c;是汽车的主要安全部件之一。随着车辆性能的不断提高&#xff0c;车速不断提升&#xff0c;对车辆的制动系统也随之提出了更高要求&#xff0c;因此了解车辆制动系统中每个部件的动态行为成为了制动系统优化的主要途径&#xff0c;同时…...

Redis Set集合命令、内部编码及应用场景(详细)

文章目录 前言普通命令SADDSMEMBERSSISMEMBERSCARDSPOPSMOVESREM 集合间操作SINTERSINTERSTORESUNIONSUNIONSTORESDIFFSDIFFSTORE 命令小结内部编码使用场景 前言 集合类型也是保存多个字符串类型的元素的&#xff0c;但和列表类型不同的是&#xff0c;集合中 1&#xff09;元…...

C++算法动态规划1

DP定义&#xff1a; 动态规划是分治思想的延申&#xff0c;通俗一点来说就是大事化小&#xff0c;小事化无的艺术。 在将大问题化解为小问题的分治过程中&#xff0c;保存对这些小问题已经处理好的结果&#xff0c;并供后面处理更大规模的问题时直接使用这些结果。 动态规划具…...

【快速预览经典深度学习模型:CNN、RNN、LSTM、Transformer、ViT全解析!】

&#x1f680;快速预览经典深度学习模型&#xff1a;CNN、RNN、LSTM、Transformer、ViT全解析&#xff01; &#x1f4cc;你是否还在被深度学习模型名词搞混&#xff1f;本文带你用最短时间掌握五大经典模型的核心概念和应用场景&#xff0c;助你打通NLP与CV的任督二脉&#xf…...

KaiwuDB在边缘计算领域的应用与优势

KaiwuDB 在边缘计算场景中主要应用于 工业物联网&#xff08;IIoT&#xff09;、智能电网、车联网 等领域&#xff0c;通过其分布式多模架构和轻量化设计&#xff0c;在边缘侧承担 数据实时处理、本地存储与协同分析 的核心作用。以下是具体案例和功能解析&#xff1a; 1. 典型…...

如何避免二极管过载?

如何避免二极管过载&#xff1f; 二极管作为电路中的基础元件&#xff0c;其过载可能导致性能下降甚至烧毁。以下从选型、安装、保护设计及散热四方面提供实用解决二极管过载方案&#xff1a; 精准选型匹配需求 根据电路特性选择二极管类型&#xff1a;高频电路优先选用肖特基…...

Vue.js组件开发系统性指南

结合核心概念、最佳实践及性能优化策略,帮助您构建高效可维护的组件体系: 一、组件基础与核心结构 1.单文件组件(SFC)组织 模板:使用<template>定义HTML结构,遵循单根元素原则。 逻辑:在<script>中通过export default导出组件选项(数据、方法、生命周期钩…...

React---day9

11、css 11.1 styled的基本使用 CSS-in-JS的模式就是一种将样式&#xff08;CSS&#xff09;也写入到JavaScript中的方式&#xff0c;并且可以方便的使用JavaScript的状态&#xff1b; npm add styled-componentsconst Title styled.h1font-size: 1.5em;text-align: center…...

设计模式 - 模板方法模式

该模式将定义一个操作中的算法骨架&#xff0c;并将算法的一些步骤延迟到子类中实现&#xff0c;使得子类可以在不改变算法结构的情况下重定义算法的某些特定步骤。 例如&#xff0c;炒菜的步骤是固定的&#xff0c;具体可分为倒油、热油、倒蔬菜、倒调料品、翻炒等。通过模板…...

鸿蒙开发List滑动每项标题切换悬停

鸿蒙开发List滑动每项标题切换悬停 鸿蒙List滑动每项标题切换悬停&#xff0c;功能也很常见 一、效果图&#xff1a; 二、思路&#xff1a; ListItemGroup({ header: this.itemHead(secondClassify, index) }) 三、关键代码&#xff1a; build() {Column() {List() {ListIt…...

ubuntu开机自动挂载windows下的硬盘

我是ubuntu和windows的双系统开发&#xff0c;在ubuntu下如果想要访问windows的硬盘&#xff0c;需要手动点击硬盘进行挂载&#xff0c;这个硬盘我每次编译完都会使用&#xff0c;所以用下面的步骤简化操作&#xff0c;让系统每次开机后自动挂载。 第一步. 确定硬盘的设备标识…...

C# 实现软件开机自启动(不需要管理员权限)

本文参考C#/WPF/WinForm/程序实现软件开机自动启动的两种常用方法&#xff0c;将里面中的第一种方法做了封装成AutoStart类&#xff0c;使用时直接两三行代码就可以搞定。 自启动的原理是将软件的快捷方式创建到计算机的自动启动目录下&#xff08;不需要管理员权限&#xff0…...

使用 Golang `testing/quick` 包进行高效随机测试的实战指南

使用 Golang testing/quick 包进行高效随机测试的实战指南 Golang testing/quick 包概述testing/quick 包的功能和用途为什么选择 testing/quick 进行测试快速入门&#xff1a;基本用法导入 testing/quick 包基本使用示例&#xff1a;快速生成测试数据quick.Check 和 quick.Val…...

32 C 语言字符处理函数详解:isalnum、isalpha、iscntrl、isprint、isgraph、ispunct、isspace

1 isalnum() 函数 1.1 函数原型 #include <ctype.h>int isalnum(int c); 1.2 功能说明 isalnum() 函数用于检查传入的整数参数是否为 ASCII 编码的字母或数字字符&#xff08;A - Z、a - z、0 - 9&#xff0c;对应 ASCII 值 65 - 90、97 - 122、48 - 57&#xff09;。…...

Qt实现一个悬浮工具箱源码分享

一、效果展示 二、源码分享 hoverToolboxWidget.h #ifndef HOVERTOOLBOXWIDGET_H #define HOVERTOOLBOXWIDGET_H#include <QWidget> #include <QMouseEvent> #include <QPropertyAnimation> #include <QStyleOption> #include <QPainter>namespa…...

线夹金具测温在线监测装置:电力设备安全运行的“隐形卫士”

在电网系统中&#xff0c;线夹金具是连接导线与输电塔架的关键部件&#xff0c;其运行状态直接影响电力传输的稳定性。传统人工巡检方式存在效率低、盲区多、数据滞后等问题&#xff0c;而线夹金具测温在线监测装置的普及&#xff0c;正为电力设备运维带来革新。 一、工作原理&…...

《TCP/IP 详解 卷1:协议》第4章:地址解析协议

ARP 协议 地址解析协议&#xff08;ARP, Address Resolution Protocol&#xff09;是IPv4协议栈中一个关键的组成部分&#xff0c;用于在网络层的IP地址与数据链路层的硬件地址&#xff08;如MAC地址&#xff09;之间建立映射关系。它的主要任务是&#xff1a; 将32位的IPv4地…...

Dify 离线升级操作手册(适用于无外网企业内网环境)

一、准备工作 准备一台能访问互联网的外网机器 用于拉取最新的 Dify 镜像和代码建议使用 Linux 或 Windows Docker 环境 准备传输介质 U盘、移动硬盘&#xff0c;或企业内部网络共享路径 确认当前内网 Dify 版本和配置 确认版本号&#xff0c;备份配置文件和数据库 二、外…...

Windows下运行Redis并设置为开机自启的服务

下载Redis-Windows 点击redis-windows-7.4.0下载链接下载Redis 解压之后得到如下文件 右键install_redis.cmd文件&#xff0c;选择在记事本中编辑。 将这里改为redis.windows.conf后保存&#xff0c;退出记事本&#xff0c;右键后选择以管理员身份运行。 在任务管理器中能够…...

网络编程之网络基础

基础理论&#xff1a;IP、子网掩码、端口号、字节序、网络基础模型、传输协议 socket&#xff1a;TCP、UDP、广播、组播、抓包工具的使用、协议头、并发服务器 Modbus协议 、HTTP协议、HTML、 分析服务器 源码、数据库 一、认识网络 网络&#xff1a;实现多设备通信 二、IP地址…...

Spring AI(11)——SSE传输的MCP服务端

WebMVC的服务器传输 支持SSE&#xff08;Server-Sent Events&#xff09; 基于 Spring MVC 的服务器传输和可选的STDIO运输 导入jar <dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-starter-mcp-server-webmvc</a…...