贪心算法应用:航班起降问题详解

在这里插入图片描述

Java中的贪心算法应用:航班起降问题详解

贪心算法是一种在每一步选择中都采取当前状态下最优的选择,从而希望导致全局最优解的算法策略。在航班起降问题中,贪心算法可以有效地解决机场跑道调度问题,即如何安排航班的起降顺序以最大化利用跑道资源。

一、问题描述

航班起降问题(也称为区间调度问题)可以描述为:

给定一组航班的起降时间区间,每个区间表示为[start_i, end_i],其中start_i是航班i的起飞时间,end_i是航班i的降落时间。由于跑道资源有限,我们需要安排一个调度方案,使得在任意时刻跑道上最多只有一个航班在起降。我们的目标是安排尽可能多的航班使用跑道。

二、问题分析

这个问题可以转化为经典的区间调度问题,即在多个重叠的区间中选择尽可能多的互不重叠的区间。贪心算法是解决这类问题的有效方法。

关键点:

  1. 冲突定义:两个航班区间[s1, e1][s2, e2]冲突当且仅当它们有重叠,即s1 < e2s2 < e1
  2. 目标:选择最大数量的互不冲突的航班区间。
  3. 贪心策略:有多种贪心选择策略可以考虑:
    • 最早开始时间
    • 最短持续时间
    • 最早结束时间

实践证明,选择最早结束时间的策略可以得到最优解。

三、贪心算法解决方案

算法步骤:

  1. 将所有航班按照结束时间从小到大排序
  2. 初始化已选航班集合为空,记录最后一个选中航班的结束时间
  3. 遍历排序后的航班列表:
    • 如果当前航班的开始时间不早于最后一个选中航班的结束时间
    • 则选择该航班,并更新最后一个选中航班的结束时间
  4. 返回已选航班集合

Java实现代码:

import java.util.Arrays;
import java.util.Comparator;public class FlightScheduling {static class Flight {int start;int end;public Flight(int start, int end) {this.start = start;this.end = end;}@Overridepublic String toString() {return "[" + start + ", " + end + "]";}}public static int maxFlights(Flight[] flights) {// 边界条件检查if (flights == null || flights.length == 0) {return 0;}// 按照航班结束时间升序排序Arrays.sort(flights, new Comparator<Flight>() {@Overridepublic int compare(Flight a, Flight b) {return a.end - b.end;}});int count = 1; // 至少可以安排第一个航班int lastEnd = flights[0].end;for (int i = 1; i < flights.length; i++) {if (flights[i].start >= lastEnd) {// 当前航班可以安排,不冲突count++;lastEnd = flights[i].end;}}return count;}public static Flight[] scheduleFlights(Flight[] flights) {if (flights == null || flights.length == 0) {return new Flight[0];}// 按照航班结束时间升序排序Arrays.sort(flights, new Comparator<Flight>() {@Overridepublic int compare(Flight a, Flight b) {return a.end - b.end;}});// 使用列表动态存储结果java.util.ArrayList<Flight> result = new java.util.ArrayList<>();result.add(flights[0]);int lastEnd = flights[0].end;for (int i = 1; i < flights.length; i++) {if (flights[i].start >= lastEnd) {result.add(flights[i]);lastEnd = flights[i].end;}}return result.toArray(new Flight[0]);}public static void main(String[] args) {Flight[] flights = {new Flight(1, 4),new Flight(3, 5),new Flight(0, 6),new Flight(5, 7),new Flight(3, 8),new Flight(5, 9),new Flight(6, 10),new Flight(8, 11),new Flight(8, 12),new Flight(2, 13),new Flight(12, 14)};System.out.println("最大可安排航班数量: " + maxFlights(flights));Flight[] scheduled = scheduleFlights(flights);System.out.println("具体安排的航班:");for (Flight f : scheduled) {System.out.println(f);}}
}

代码解析:

  1. Flight类:表示航班,包含开始时间和结束时间。
  2. maxFlights方法:计算可以安排的最大航班数量。
  3. scheduleFlights方法:返回具体的航班安排方案。
  4. 排序:按照航班结束时间升序排序,这是贪心算法的关键。
  5. 选择策略:每次选择结束时间最早且不与已选航班冲突的航班。

四、算法正确性证明

为了证明这个贪心算法的正确性,我们需要证明选择最早结束的航班可以得到最优解。

证明:

  1. 设贪心算法选择的航班序列为G = {g₁, g₂, …, gₘ}。
  2. 设最优解为O = {o₁, o₂, …, oₙ},且O是按结束时间排序的。
  3. 我们需要证明m = n。

归纳法证明:

  • 对于k=1:贪心算法选择最早结束的航班g₁,因为任何包含比g₁结束更晚的航班o₁的解,都可以用g₁替换o₁而不减少航班数量。
  • 假设对于前k个选择,贪心算法与某个最优解一致。
  • 对于第k+1个选择,贪心算法选择在gₖ结束后最早结束的航班gₖ₊₁。任何最优解中第k+1个航班oₖ₊₁的结束时间不会早于gₖ₊₁,因此可以用gₖ₊₁替换oₖ₊₁。

因此,贪心算法得到的解与最优解航班数量相同。

五、复杂度分析

  1. 时间复杂度

    • 排序:O(n log n),使用快速排序或归并排序。
    • 遍历:O(n)。
    • 总时间复杂度:O(n log n)。
  2. 空间复杂度

    • 排序:O(log n)的栈空间(对于快速排序)。
    • 存储结果:O(n)(如果需要存储具体安排)。
    • 如果只计算数量,空间复杂度为O(1)。

六、变种问题及解决方案

1. 最少跑道问题

问题:给定航班起降时间,计算至少需要多少条跑道才能满足所有航班需求。

解决方案

  • 这可以转化为计算最大重叠航班数的问题。
  • 将所有开始和结束时间点排序,扫描时间线统计最大重叠数。
public static int minRunways(Flight[] flights) {int n = flights.length;int[] starts = new int[n];int[] ends = new int[n];for (int i = 0; i < n; i++) {starts[i] = flights[i].start;ends[i] = flights[i].end;}Arrays.sort(starts);Arrays.sort(ends);int runways = 0;int maxRunways = 0;int i = 0, j = 0;while (i < n && j < n) {if (starts[i] < ends[j]) {runways++;maxRunways = Math.max(maxRunways, runways);i++;} else {runways--;j++;}}return maxRunways;
}

2. 加权区间调度

问题:每个航班有不同的优先级或权重,目标是选择一组互不冲突的航班使得总权重最大。

解决方案

  • 这无法用贪心算法解决,需要使用动态规划。
  • 仍然按结束时间排序,对每个航班i,找到最后一个不与i冲突的航班j,然后比较包含i和不包含i的情况。
public static int maxWeightSchedule(Flight[] flights, int[] weights) {int n = flights.length;Arrays.sort(flights, Comparator.comparingInt(a -> a.end));// 预处理:对于每个i,找到p[i] = 最后一个不与i冲突的航班int[] p = new int[n];for (int i = 0; i < n; i++) {p[i] = -1;for (int j = i - 1; j >= 0; j--) {if (flights[j].end <= flights[i].start) {p[i] = j;break;}}}int[] dp = new int[n + 1];dp[0] = 0;for (int i = 1; i <= n; i++) {int include = weights[i - 1] + (p[i - 1] != -1 ? dp[p[i - 1] + 1] : 0);int exclude = dp[i - 1];dp[i] = Math.max(include, exclude);}return dp[n];
}

七、实际应用中的考虑因素

在实际的航班调度系统中,还需要考虑以下因素:

  1. 航班优先级:紧急航班、VIP航班等可能需要优先安排。
  2. 缓冲时间:航班之间需要留出安全间隔时间。
  3. 多跑道协调:大型机场有多条跑道,需要考虑协同调度。
  4. 航班延误概率:考虑历史延误数据,提高调度鲁棒性。
  5. 连接航班:确保转机航班有足够的时间间隔。

八、测试用例设计

为了验证算法的正确性,需要设计全面的测试用例:

public static void testCases() {// 测试用例1:无航班Flight[] empty = {};assert maxFlights(empty) == 0;// 测试用例2:单个航班Flight[] single = {new Flight(1, 2)};assert maxFlights(single) == 1;// 测试用例3:无冲突的多个航班Flight[] noConflict = {new Flight(1, 2),new Flight(3, 4),new Flight(5, 6)};assert maxFlights(noConflict) == 3;// 测试用例4:完全冲突的航班Flight[] allConflict = {new Flight(1, 5),new Flight(2, 5),new Flight(3, 5)};assert maxFlights(allConflict) == 1;// 测试用例5:混合情况Flight[] mixed = {new Flight(1, 3),new Flight(2, 4),new Flight(3, 5),new Flight(4, 6)};assert maxFlights(mixed) == 2;// 测试用例6:边界情况,航班刚好不冲突Flight[] edge = {new Flight(1, 2),new Flight(2, 3),new Flight(3, 4)};assert maxFlights(edge) == 3;System.out.println("所有测试用例通过!");
}

九、性能优化技巧

  1. 预处理p数组的优化
    在加权区间调度中,可以使用二分查找来优化p数组的计算:

    for (int i = 0; i < n; i++) {int low = 0, high = i - 1;p[i] = -1;while (low <= high) {int mid = (low + high) / 2;if (flights[mid].end <= flights[i].start) {p[i] = mid;low = mid + 1;} else {high = mid - 1;}}
    }
    

    这样可以将预处理时间复杂度从O(n²)降低到O(n log n)。

  2. 并行处理
    对于大规模航班数据,可以将排序和扫描过程并行化处理。

  3. 内存优化
    如果只需要计算数量而不需要具体安排,可以避免存储整个结果数组。

十、与其他算法的比较

  1. 动态规划

    • 可以解决更一般的加权区间调度问题。
    • 时间复杂度通常更高(O(n²)或O(n log n))。
    • 适用于需要精确最优解的场景。
  2. 回溯法

    • 可以找到所有可能的调度方案。
    • 时间复杂度极高(O(2ⁿ))。
    • 仅适用于极小规模问题。
  3. 贪心算法

    • 简单高效,时间复杂度低(O(n log n))。
    • 只能解决特定类型的优化问题。
    • 适用于需要快速近似解的大规模问题。

十一、实际工程实现建议

在实际工程中实现航班调度系统时,建议:

  1. 模块化设计

    • 将航班数据加载、预处理、算法核心、结果输出分离。
    • 便于维护和扩展。
  2. 异常处理

    • 处理无效的航班数据(如结束时间早于开始时间)。
    • 处理大规模数据时的内存溢出问题。
  3. 日志记录

    • 记录算法执行的关键步骤和时间。
    • 便于调试和性能分析。
  4. 配置化

    • 使排序策略、缓冲时间等参数可配置。
    • 适应不同的业务场景。
  5. 可视化

    • 提供航班调度结果的图形化展示。
    • 便于人工验证和调整。

十二、扩展

  1. 更复杂的调度模型

    • 考虑航班类型(起飞/降落)的不同资源需求。
    • 考虑多跑道之间的依赖关系。
  2. 实时调度系统

    • 处理动态到达的航班请求。
    • 考虑航班延误的实时调整。
  3. 机器学习应用

    • 预测航班延误概率。
    • 基于历史数据优化调度策略。
  4. 分布式调度

    • 多机场协同调度。
    • 大规模航班数据的分布式处理。

总结

贪心算法在航班起降问题中提供了一种高效简洁的解决方案,通过选择最早结束的航班策略,可以在O(n log n)时间内找到最优解。虽然贪心算法不能解决所有变种问题,但对于基础的区间调度问题,它是最佳选择。理解这一算法的原理和实现细节,不仅有助于解决航班调度问题,也为处理其他类似的区间选择问题提供了思路框架。

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

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

相关文章

uniapp scroll-view 设置scrollTop无效

当我们使用 scroll-view的scroll-top的时候 默认想让它回到顶部&#xff0c;当我们设置值为0的时候会不生效&#xff0c;在实际运用过程中&#xff0c;发现设置了scroll-top无效&#xff0c;滚动条位置并没有发生变化&#xff0c;是因为微信小程序的官方框架处于性能考虑&#…

网络与通信

1.TCP协议与UDP协议TCP&#xff08;Transmission Control Protocol&#xff0c;传输控制协议&#xff09;和 UDP&#xff08;User Datagram Protocol&#xff0c;用户数据报协议&#xff09;是 TCP/IP 协议族中两种核心的传输层协议&#xff0c;它们在数据传输方式、可靠性、适…

Node.js中package.json详解

1. name&#xff08;名称&#xff09; 如果你计划发布你的包&#xff0c;package.json 中最重要的字段是 name 和 version&#xff0c;因为它们是必需的。name 和 version 共同组成一个假定完全唯一的标识符。包的更改应伴随版本号的更新。如果你不打算发布包&#xff0c;那么…

代码随想录第14天| 翻转、对称与深度

226.翻转二叉树 &#xff08;优先掌握递归&#xff09; 题目链接/文章讲解/视频讲解&#xff1a;翻转二叉树 交换的是指针&#xff0c;而不是数值&#xff0c;如果用数值做交换&#xff0c;需要交换的节点下面无法很好的操作。 使用递归来实现&#xff0c;但要提前清除是什么顺…

DNS-Windows上使用DNS

DNS-Windows上使用DNS一、查看与修改DNS配置1.1、查看当前DNS服务器设置1.2、临时修改 DNS 服务器&#xff08;命令行&#xff09;二、DNS缓存相关操作2.1、查看DNS缓存内容2.2、 刷新 DNS 缓存&#xff08;清除过期记录&#xff09;三、测试域名解析&#xff08;nslookup 工具…

3dsMax 2026 .NET Core 8 转型下的Maxscript脚本开发:动态编译模块的重构策略与兼容性升级路径

3ds Max 长期以来一直提供出色的 .NET 集成,使 Maxscript 能够无缝利用任何 .NET 库的强大功能。部分开发者在工具中广泛使用了 .NET 功能。 之前,3ds Max 依赖于 .NET Framework 4.8 并且最近更新到了 4.8.1,用于 2025 版本的发布。然而,随着 3ds Max 2026 的推出,Autod…

golang 做webrtc开发核心

在Golang中进行WebRTC开发&#xff0c;核心在于理解WebRTC协议的工作原理以及如何利用Go生态中的库来实现关键功能。以下是Golang WebRTC开发的核心要点&#xff1a; WebRTC基础概念 了解ICE&#xff08;Interactive Connectivity Establishment&#xff09;协议用于NAT穿越掌握…

RabbitMQ 异步化抗洪实战

说明&#xff1a;本文仅展示架构思路与安全片段&#xff0c;所有敏感字段已用占位符&#xff1b;不含可直接复刻的生产细节。数据与接口均为演示/虚拟。0. 背景与目标长耗时/不确定接口&#xff08;如对接第三方机器人平台&#xff09;的同步阻塞&#xff0c;容易造成请求堆积与…

接口返回 2 万条数据,easy-trans导致多了20s耗时排查过程

内网访问排版核料详情功能&#xff0c;用户反馈要等十几秒排查 sql&#xff1a;sql 比较简单排查内存计算&#xff1a;arthus trace 类名 方法名 总耗时2s排查页面渲染是否缓慢&#xff1a;F12 查看接口 等待服务器响应 20s 下载时间 30s, 故不考虑渲染问题排查请求响应日志打…

AIGC入门,手搓大模型客户端与MCP交互

概述 在现代应用开发中&#xff0c;将大语言模型&#xff08;LLM&#xff09;与专用工具服务相结合&#xff0c;可以构建出既能理解自然语言&#xff0c;又能准确执行专业任务的智能代理。本文介绍一个基于 MCP&#xff08;Model Context Protocol&#xff09;协议和 Ollama 本…

深度学习:从预备知识到未来展望

在当今数字化时代&#xff0c;深度学习正以前所未有的速度改变着我们的生活和工作方式。从智能语音助手到自动驾驶汽车&#xff0c;从精准医疗到个性化推荐系统&#xff0c;深度学习的应用无处不在。本文将从深度学习的预备知识入手&#xff0c;探讨其发展历程、关键技术和未来…

软考高级系统架构设计师之构件与中间件技术篇

一、构件的定义 定义1:软件构件是一种组装单元&#xff0c;它具有规范的接口规约和显式的语境依赖。软件构件可以被独立地部署并由第三方任意地组装。 定义2:构件是某系统中有价值的、几乎独立的并可替换的一个部分&#xff0c;它在良好定义的体系结构语境内满足某清晰的功能。…

Node.js 文件上传中文文件名乱码问题,为什么只有Node会有乱码问题,其他后端框架少见?

问题现象当用户上传包含中文字符的文件时&#xff0c;在服务器端获取到的文件名可能变成类似 ‹•–‡.txt 这样的乱码&#xff0c;而不是预期的中文文件名。为什么只有Node会乱码&#xff1f;很多后端框架&#xff08;如 Java Spring Boot、Python Django、PHP Laravel&#x…

学习英语音标 (从汉语角度看英语音标发音差异)

仅供参考, 跟着教学视频看不懂时再来看以下引导 以下只写容易出错的音标 发音视频: https://www.jiwake.com/yinbiaofayin/ 音标规则单词ɜː类似汉语e, 饿~urgeə类似汉语e, 饿goɔː类似汉语o, 哦~walkɒ类似汉语o, 哦washɪ/iː/的短语, 不止发声短,舌头不用隆起itʃ类似汉…

论文笔记(九十一)GWM: Towards Scalable Gaussian World Models for Robotic Manipulation

GWM: Towards Scalable Gaussian World Models for Robotic Manipulation文章概括摘要1. 引言2. 相关工作3. 高斯世界模型&#xff08;Gaussian World Model&#xff09;3.1. 世界状态编码&#xff08;World State Encoding&#xff09;3.2. 基于扩散的动态建模&#xff08;Dif…

apache phoenix sql 命令大全详解

这是一份非常详细的 Apache Phoenix SQL 命令大全和详解。Phoenix 作为 HBase 上的 SQL 层&#xff0c;其语法大部分与标准 SQL 兼容&#xff0c;但也有许多针对 HBase 的特性扩展。核心概念 在开始之前&#xff0c;请记住 Phoenix 的两个核心概念&#xff1a; 主键&#xff08…

【代码讲解】SO-ARM100 双场景演示:手柄驱动 Mujoco 仿真 + 实机控制

视频讲解&#xff1a; 【代码讲解】SO-ARM100 双场景演示&#xff1a;手柄驱动 Mujoco 仿真 实机控制今天介绍下使用使用北通手柄通过控制 Mujoco 中的 SO-ARM100 机械臂&#xff0c;然后将关节数据通过 zmq 通信转发控制实际机械臂。 本期中会涉及如下点&#xff0c;需要注意…

「数据获取」《中国教育经费统计年鉴》(1997-2024)

01、数据简介《中国教育经费统计年鉴》作为我国教育经费领域的核心统计典籍&#xff0c;全面系统地呈现了全国各级各类教育经费的来源构成、分配流向与使用成效。其统计范围覆盖学前教育、基础教育、中等职业教育、高等教育及特殊教育等全学段&#xff0c;数据维度涵盖财政性教…

使用 Logspout 收集所有容器的

1.将所有容器的输出路由到远程 rsyslog 服务器1.修改 rsyslog 配置文件/etc/rsyslog.conf, 从中找到 “# Provides UDP sysilog recepion"语句。并将该处的以下两行配置代码行首的“#”字符删除&#xff08;取消注释&#xff09;[roothost1 ~]# vi /etc/rsyslog.conf [roo…

【智能化解决方案】基于多目标优化检索增强生成的智能行程规划方案

&#x1f4dd; 基于多目标优化的智能行程规划方案 1 用户需求分析与矩阵构建 1.1 核心用户信息提取 根据用户提供的年龄、出发地、目的地、出行时间等基本信息&#xff0c;我们首先构建一个用户特征向量&#xff1a; U {Age, Origin, Destination, TravelDate, Duration, Budg…