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

news2025/6/6 8:18:43

在这里插入图片描述

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

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

问题定义

**带权任务间隔调度问题(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 {
    @Test
    public void testEmptyTasks() {
        Task[] tasks = {};
        assertEquals(0, WeightedIntervalScheduling.schedule(tasks));
    }
    
    @Test
    public void testSingleTask() {
        Task[] tasks = {new Task(1, 3, 5)};
        assertEquals(5, WeightedIntervalScheduling.schedule(tasks));
    }
    
    @Test
    public void testAllTasksConflict() {
        Task[] tasks = {
            new Task(1, 5, 3),
            new Task(2, 6, 4),
            new Task(3, 7, 2)
        };
        assertEquals(4, WeightedIntervalScheduling.schedule(tasks));
    }
    
    @Test
    public void testNoTasksConflict() {
        Task[] tasks = {
            new Task(1, 3, 5),
            new Task(4, 6, 3),
            new Task(7, 9, 2)
        };
        assertEquals(10, WeightedIntervalScheduling.schedule(tasks));
    }
    
    @Test
    public 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梦】!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2401432.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

用电脑控制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;适合大规模评测和自动化测试。…

【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;也就是互联网控制报文协议。 里面有个关键词 …

结构型设计模式之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;并供后面处理更大规模的问题时直接使用这些结果。 动态规划具…

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

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

鸿蒙开发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;让系统每次开机后自动挂载。 第一步. 确定硬盘的设备标识…

使用 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地…

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…

计算机网络备忘录

计算机网络 - 网络互联与互联网 计算机网络重点学习本章&#xff0c;属于核心知识 包含网络层和传输层 的 相关协议 计算机网络层次重点掌握网络层与传输层。其中网络层主要是IP协议&#xff0c;解决主机-主机通信&#xff0c;传输层主要是TCP/UDP 协议&#xff0c;解决应用-…