课程:数据结构  |  语言:Java

智慧农业大棚
数据处理与优化
任务单

从传感器采集到智能决策,用 8 种核心数据结构构建完整的农业物联网系统

乡村振兴战略 数字农业 国家粮食安全 农业现代化
8 子任务
8 数据结构
1 完整系统

为什么智慧农业刻不容缓?

这不仅是一道课程作业,更是国家战略需求的技术缩影。理解背景,才能理解代码的意义。

智慧农业大棚物联网系统
现代智慧温室:传感器实时监测 + 物联网集中管控 + 数据驱动决策
18亿亩
耕地红线
守住"18亿亩耕地红线"是国家粮食安全的底线,大棚农业是有限耕地上增产提质的关键路径
14亿人
粮食保障
14亿人口的粮食供应安全,要求农业生产效率持续提升,智慧化管理是不可逆的历史趋势
2035年
现代化目标
《数字乡村发展战略纲要》要求到2035年基本实现农业农村现代化,数字技术是核心驱动力
4000亿+
市场规模
我国智慧农业市场规模预计2026年突破4000亿元,大棚物联网是其中增速最快的赛道

国家政策脉络

2017
党的十九大报告

首次将"乡村振兴战略"写入党代会报告,明确"农业农村农民问题是关系国计民生的根本性问题",推进农业现代化上升为国家战略

2019
《数字乡村发展战略纲要》

明确以信息化驱动农业现代化,部署建设智慧农业,重点支持物联网、大数据、人工智能等技术在设施农业中的规模化应用

2021
《"十四五"全国农业农村信息化发展规划》

将设施农业(大棚)物联网列为重点任务,明确推进智能传感、边缘计算、数据处理等技术在温室大棚中的落地应用

2022
中央一号文件·连续聚焦数字农业

强调大力发展智慧农业,推广物联网、大数据在设施农业中的应用,探索建立农业数据共享机制,提升农业生产经营智能化水平

2024
《农业农村部关于大力发展智慧农业的指导意见》

将智慧农业作为引领农业生产方式变革的战略举措,明确支持大棚传感器网络、智能控制系统、数据分析平台等基础设施建设

NOW
你的课程任务

用《数据结构》课程所学,用 Java 实现智慧大棚的核心数据处理模块——这不只是交作业,而是用代码呼应国家对数字农业人才的真实需求

政策提出方向,数据结构负责落地

国家战略勾勒了"智慧农业"的宏观蓝图,但真正让大棚"聪明"起来,靠的是扎实的底层数据结构。温度数据怎么快速查?设备指令怎么不乱序?灌溉任务怎么精准调度?每一个工程问题,背后都是一种数据结构的选择。

接下来,我们从系统整体架构出发,一步步拆解这8个核心工程问题。

进入系统概览

什么是智慧农业大棚系统?

在进入具体编码任务之前,我们首先需要理解这个系统要解决的现实问题,以及各项技术如何协同工作。

现实痛点

传统农业大棚依赖人工经验管理温湿度、灌溉和施肥,效率低、误差大,极易因环境突变导致减产。随着大棚规模扩大,设备数量从几台增长到数百台,数据体量爆炸式增长,人工管理模式彻底失效。

技术方向

构建一套以数据为核心的智慧大棚管理系统:传感器实时采集环境数据,控制器接收并执行指令,云端统一管理设备信息与权限,调度系统自动规划灌溉与管网铺设,形成感知—决策—执行的完整闭环。

系统整体架构

感知层
温度传感器 湿度传感器 光照传感器 CO₂传感器
数据处理层
历史查询 指令队列 定时调度 版本控制
设备管理层
设备注册 权限管理 名称检索
决策优化层
管网铺设 最优调度

核心业务链路

01
设备上线注册 新设备接入系统,哈希表快速存储与检索设备信息
02
权限分配 基于树形结构的权限体系,管理不同角色对设备的访问控制
03
数据采集与存储 传感器持续产生温度等环境数据,以有序数组形式归档,支持快速查询
04
配置版本管理 每次设备配置变更压入栈,支持一键回滚至任意历史版本
05
指令下发执行 控制指令进入队列,按先入先出顺序保序执行,避免指令冲突
06
智能调度与优化 最小堆实现定时灌溉调度,最小生成树优化管网铺设路径

数据结构与任务对应总览

序号 任务名称 数据结构 核心特性
1历史温度数据快速查询有序数组二分查找 O(log n)
2设备配置版本控制后进先出 · 版本回滚
3指令推送与处理队列先进先出 · 顺序执行
4设备注册与信息管理哈希表均摊 O(1) · 快速检索
5设备权限管理层级结构 · 继承传递
6设备名称检索前缀树前缀匹配 · 自动补全
7灌溉任务定时调度最小堆延迟队列 · 优先调度
8智能灌溉管网最优铺设最小生成树Kruskal / Prim · 最优路径

整体框架已经清晰,接下来我们将逐一深入每个子任务——每一项任务都是这个系统不可或缺的一块拼图。

八个子任务详解

每个子任务都有明确的业务背景、数据结构选型理由、接口设计和实现指引。

01
有序数组

历史温度数据快速查询

业务背景

大棚内的温度传感器持续采集环境数据,长期运行后会积累大量历史温度记录。农业专家和种植人员经常需要查询某一时刻的历史温度,用于分析作物生长环境、排查异常原因和辅助管理决策。如果面对大量按时间存储的数据仍采用线性遍历,每次查询都要扫描全部记录,效率较低,难以满足实际使用需求。

典型场景:种植户发现某天黄瓜大棚出现叶片萎蔫现象,需要回看当天 14:30 左右的大棚温度。系统需要先快速定位到 14:30 附近对应的历史温度记录,再结合其前后相邻时刻的数据,分析这一时间段内是否出现温度骤升、骤降等异常波动。

数据特征:温度记录按时间升序存储,数据规模大,查询频繁。查询目标是快速定位某一给定时刻附近对应的历史温度记录;若该时刻没有精确记录,则返回该时刻之前最近的一条温度数据。因此,这类问题适合使用二分查找提高查询效率。

有序数组配合二分查找,将"找最近温度"的复杂度从 O(n) 降至 O(log n),无需遍历全部数据即可快速定位目标记录。

为什么选有序数组?

✅ 有序数组
• 二分查找 O(log n) • 内存连续,缓存友好 • 天然有序,无需额外排序 • 实现简单直观
❌ 链表
• 只能顺序遍历 O(n) • 内存不连续,缓存命中率低 • 无法使用二分查找
❌ 哈希表
• 精确匹配 O(1) • 无法找"最近 ≤ target" • 不支持顺序关系查询

任务要求

  • 设计 TemperatureRecord 实体类,包含 time(格式 "HH:mm")和 temperaturedouble,单位 °C)字段
  • 维护一个按 时间升序排列List<TemperatureRecord>
  • 实现 findLatestTemperature(List<TemperatureRecord> records, String targetTime):使用二分查找,返回 ≤ targetTime 的最近一条记录的温度值;若所有记录都晚于 targetTime,返回 null
  • 编写完整测试用例,覆盖:精确命中、落在两个时间之间、早于最早记录、晚于最后记录等边界情况

实现步骤指引

Step 1
定义数据实体

创建 TemperatureRecord 类,包含 time(字符串 "HH:mm")和 temperature(double)两个字段及构造方法。

Step 2
初始化二分查找边界

left = 0right = records.size() - 1ans = -1(候选索引初值,-1 代表尚未找到任何满足条件的记录)。

Step 3
二分查找核心循环

计算 mid = left + (right - left) / 2,用 String.compareTo 比较时间字符串:
cmp == 0:精确命中,直接返回该温度;
cmp < 0(mid 时刻 < target):更新候选 ans = mid,向右收缩 left = mid + 1
cmp > 0(mid 时刻 > target):向左收缩 right = mid - 1

Step 4
返回结果

循环结束后,若 ans == -1 说明所有记录都晚于目标时刻,返回 null;否则返回 records.get(ans).temperature

Step 5
测试用例设计

覆盖:精确命中某时刻、落在两条记录之间、早于最早记录(应返回 null)、晚于最后记录(应返回最后温度)。

性能分析

操作时间复杂度说明
二分查找最近温度O(log n)52万数据约20次比较
线性遍历(对比)O(n)最坏需扫描全部记录

代码框架

JavaTemperatureSearch.java
import java.util.*;

public class TemperatureSearch {

    /**
     * 查找给定时间点的最近温度值
     * @param records    温度记录列表,按时间升序排列
     * @param targetTime 目标查询时间(格式:"HH:mm")
     * @return 该时刻或该时刻之前最近的温度值,若无则返回 null
     */
    public static Double findLatestTemperature(
            List<TemperatureRecord> records, String targetTime) {

    }

    public static void main(String[] args) {
        List<TemperatureRecord> records = Arrays.asList(
            new TemperatureRecord("08:00", 15.0),
            new TemperatureRecord("09:00", 17.0),
            new TemperatureRecord("10:00", 20.0),
            new TemperatureRecord("11:00", 23.0),
            new TemperatureRecord("12:00", 26.0)
        );
        runTests(records);
    }

    // …… runTests / 辅助方法见完整代码 ……
}

class TemperatureRecord {
    String time;         // 格式 "HH:mm"
    double temperature;  // 单位 °C
    public TemperatureRecord(String time, double temperature) {
        this.time = time;
        this.temperature = temperature;
    }
}

思考提示

为什么可以用 String.compareTo 直接比较 "HH:mm" 格式的时间字符串?
变量 ans 的作用是什么?为什么初始值设为 -1 而不是 0
若将需求改为"找第一个 ≥ target 的记录",代码应如何调整?

温度数据已经可以高效检索,但大棚中的设备不是一成不变的——每次调整传感器灵敏度、更换控制策略,都需要修改设备配置。如果某次更新出现问题,能否快速回滚到上一个可用版本?这就引出了下一个任务。

02

设备配置版本控制

业务背景

温室控制器的配置参数(如温度阈值、灌溉频率、报警上限)需要随季节和作物生长阶段频繁调整。每次更改配置后,如果效果不理想,运维人员希望能一键撤销,回到上次的配置状态。这种"撤销/重做"操作,天然契合后进先出(LIFO)的栈结构。

典型场景:春季番茄种植,温控阈值从18°C调整到20°C,但第二天发现幼苗有轻微冻害风险,运维人员需要立即回退到上一版本配置。如果用数组记录,需要手动维护"当前版本指针";用栈则天然支持 push/pop。

数据特征:版本变更频率低(每周几次);回滚操作需要即时响应;需要保留完整历史链路供审计;旧版本按时间自然淘汰。

每次配置变更就是一次"压栈",每次回滚就是一次"弹栈",历史操作链完整保留,随时可追溯。

为什么选栈?

✅ 栈(LIFO)
• 撤销/重做语义天然契合 • push/pop 均 O(1) • 实现简单直观 • 内存占用可控(限制深度)
❌ 队列(FIFO)
• 先进先出不符合回滚逻辑 • 需要额外指针跟踪当前版本
❌ 链表
• 需要手动维护头尾指针 • 遍历历史版本需 O(n)

任务要求

  • 设计 DeviceConfig 配置类,包含版本号(自增)、修改时间(时间戳)、操作人、配置内容(JSON或Map)等字段
  • 基于 数组或链表 手动实现一个泛型 Stack<T> 类,不允许直接使用 java.util.Stack
  • 实现 push(config)(提交新配置)、pop()(回滚至上一版本)、peek()(预览当前配置)操作,所有操作 O(1) 时间复杂度
  • 实现 历史版本列表:遍历栈中所有版本并展示版本信息(从栈顶到栈底)
  • 为栈设置最大深度限制(如保留最近 50 个版本),栈满时自动淘汰最旧版本(栈底元素)
  • (进阶)实现双栈设计:一个操作栈 + 一个撤销栈,支持"撤销"与"重做"双向操作

实现步骤指引

Step 1
选择底层实现

数组栈:用 top 指针标记栈顶位置,扩容时创建新数组。链表栈:每次 push 在头部插入节点。

Step 2
实现基本栈操作

push:检查栈满,若满则淘汰栈底;pop:检查栈空,返回并移除栈顶;peek:只返回不移除。

Step 3
处理栈满逻辑

当栈满时,需要"挤出"栈底元素。数组实现:整体前移;循环数组:移动起始指针。

Step 4
版本号管理

每次 push 时自动递增版本号,记录操作时间和操作人,便于审计和追溯。

Step 5
深拷贝配置对象

push 时对配置对象进行深拷贝,避免外部修改影响栈内历史版本。

性能分析

操作时间复杂度说明
push(提交配置)O(1)栈顶插入,常数时间
pop(回滚)O(1)栈顶弹出,常数时间
peek(预览当前)O(1)查看栈顶元素
遍历历史O(n)n 为栈中版本数
栈满淘汰(数组)O(n)数组整体前移

核心接口设计

JavaConfigVersionStack.java
public class ConfigVersionStack {

    private DeviceConfig[] stack;
    private int top = -1;
    private final int MAX_VERSIONS = 50;

    /** 提交新配置(压栈) */
    public void commit(DeviceConfig config) { ... }

    /** 回滚至上一版本(弹栈) */
    public DeviceConfig rollback() { ... }

    /** 查看当前生效配置(不弹出) */
    public DeviceConfig currentConfig() { ... }

    /** 打印所有历史版本 */
    public void printHistory() { ... }
}

思考提示

如何在有最大深度限制的情况下,既保留最新版本又不丢失最旧版本(循环数组)?
能否基于两个栈实现"撤销"与"重做"双向操作?
配置内容是否需要深拷贝?对象引用可能带来哪些问题?

配置管理的"回溯"问题解决了。配置确定之后,系统需要向数百台设备下发控制指令——开启风扇、启动灌溉、调节温控。这些指令必须按照严格的顺序执行,不能乱序,不能丢失。接下来,我们用队列来保障指令的有序传递。

03
队列

指令推送与处理

业务背景

云端控制台会同时向多台设备推送指令(如"设备A开启浇水→设备B关闭风扇→设备C调温至25°C"),设备执行器每次只能处理一条指令。必须保证先推送的指令先执行(FIFO),否则可能出现"水还没关就开始施肥"的错误操作。队列是这一场景下最直接的解决方案。

队列就像流水线传送带,指令从一端进入、从另一端依次取出执行,严格保序,绝不跳号。

任务要求

  • 设计 Command 指令类,包含指令ID、目标设备ID、指令类型、下发时间等字段
  • 基于循环数组手动实现 CircularQueue<T>,包含 enqueuedequeueisEmptyisFull 方法
  • 模拟生产者-消费者场景:一个线程不断入队指令,另一个线程模拟设备执行器出队并处理
  • 实现队列满时的阻塞策略(简单起见可用轮询或 wait/notify
  • 统计队列的吞吐量(单位时间内处理的指令数量)

核心接口设计

JavaCommandQueue.java
public class CommandQueue {

    private Command[] buffer;
    private int head = 0, tail = 0, size = 0;

    /** 入队:推送新指令 */
    public synchronized void enqueue(Command cmd) throws InterruptedException { ... }

    /** 出队:取出下一条待执行指令 */
    public synchronized Command dequeue() throws InterruptedException { ... }

    /** 查看队头指令(不出队) */
    public Command peek() { ... }

    /** 当前队列中待处理指令数 */
    public int pendingCount() { ... }
}

思考提示

循环数组如何区分"队满"与"队空"(head==tail 时的歧义问题)?
如果某条指令执行失败,是否需要重新入队?如何避免死循环?
对于紧急指令(如火灾报警),普通 FIFO 队列是否足够?能否引入优先级队列?

指令的流转顺序有了保障。但问题随之而来——当系统规模扩展到数百台设备时,"这条指令该发给哪台设备?这台设备的IP是什么?它的型号、状态如何?"每次都要线性扫描整个设备列表,效率无法接受。我们需要一种能够根据设备ID 即时定位设备信息的结构。

04
哈希表

设备注册与信息管理

业务背景

系统中每台设备(传感器、控制器、摄像头)都有唯一的设备ID(如 DEV-20240901-001)。在发送指令、查询状态、更新配置时,都需要通过ID即时找到设备的完整信息。哈希表将字符串ID映射为数组索引,实现均摊 O(1) 时间复杂度的增删查改,是设备注册表的最佳选择。

哈希表就像一本精准的电话簿,无论设备有多少台,通过ID查找设备信息的时间始终接近于常数。

任务要求

  • 设计 Device 类,包含设备ID、名称、类型、IP地址、在线状态、最后心跳时间等字段
  • 手动实现 DeviceHashMap,内部使用数组 + 链表(拉链法)解决哈希冲突
  • 自定义哈希函数,将字符串ID均匀映射到数组槽位(可参考 djb2 或 FNV 算法)
  • 实现 register(注册设备)、query(查询设备)、update(更新状态)、deregister(注销设备)
  • 实现负载因子检测,当负载超过 0.75 时自动 rehash 扩容

核心接口设计

JavaDeviceRegistry.java
public class DeviceRegistry {

    private LinkedList<Device>[] buckets;
    private int capacity, size;
    private static final float LOAD_FACTOR = 0.75f;

    /** 注册新设备 */
    public void register(Device device) { ... }

    /** 根据ID查询设备信息 */
    public Device query(String deviceId) { ... }

    /** 更新设备在线状态 */
    public void updateStatus(String deviceId, boolean online) { ... }

    /** 注销设备 */
    public void deregister(String deviceId) { ... }

    /** 负载超阈值时触发扩容 */
    private void rehash() { ... }
}

思考提示

如何设计哈希函数使其分布均匀,减少冲突?
拉链法与开放地址法各有什么优劣?在大棚设备场景下哪个更合适?
rehash 时如何保证线程安全?

设备信息管理游刃有余。但大棚系统不只有一名管理员——有农场主、技术员、实习生、第三方监测人员……不同角色对设备的操作权限截然不同。"超级管理员可以操作所有设备,普通员工只能查看,实习生连查看都受限制。"这种层级化的权限结构,最适合用来建模。

05

设备权限管理

业务背景

大棚系统的用户角色形成天然的层级关系:超级管理员 → 区域负责人 → 技术员 → 操作员。父节点的权限自动传递给子节点,但子节点可以额外受限。同时,设备本身也组织成树形(大棚A区 → 温室1号 → 传感器组1 → 具体传感器)。用树来建模权限,不仅直观,还能用遍历算法高效处理权限继承与校验。

权限树的每一层代表一个管理粒度,从最高权限到最细粒度的设备访问,每一次权限校验都是一次从叶到根的路径回溯。

任务要求

  • 设计 PermissionNode 节点类,包含角色名称、权限集合(读/写/控制)、子节点列表
  • 构建多叉树(N-ary Tree)表示组织权限结构,根节点为超级管理员
  • 实现权限继承:子节点默认继承父节点的所有权限
  • 实现权限校验:给定用户角色和目标设备,判断是否有操作权限
  • 实现DFS 遍历打印完整权限树,展示各节点权限
  • 实现权限剪枝:将某个子树的所有节点降权(如临时收回某区域的控制权)

核心接口设计

JavaPermissionTree.java
public class PermissionTree {

    private PermissionNode root;

    /** 在指定父节点下添加新角色节点 */
    public void addRole(String parentRole, PermissionNode child) { ... }

    /** 校验指定角色是否有操作某设备的权限 */
    public boolean hasPermission(String role, String deviceId, String action) { ... }

    /** DFS 遍历打印完整权限树 */
    public void printTree(PermissionNode node, int depth) { ... }

    /** 对某子树内所有节点批量降权 */
    public void revokeSubTree(String rootRole, String action) { ... }
}

思考提示

DFS 与 BFS 在遍历权限树时各有什么优势?
如何设计权限位掩码(bitmask)来高效表示多种权限的组合?
若一个用户属于多个角色(多重继承),如何处理权限合并?

权限体系建立完毕,现在来到了运维日常中最高频的操作之一:搜索设备。大棚中有上百台设备,名称各异(如 greenhouse-A1-temp-sensor-01),运维人员习惯输入前几个字符快速定位目标设备。这种"前缀匹配 + 自动补全"的能力,正是前缀树(Trie)的看家本领。

06
前缀树(Trie)

设备名称检索

业务背景

当设备数量增长到数百乃至数千台时,管理员在控制台输入 "green",系统需要在毫秒内列出所有以 "green" 开头的设备名称(如 greenhouse-A1-fangreenhouse-B2-sensor……)。哈希表无法支持前缀查询,有序数组前缀查询效率也有限,而前缀树专为字符串前缀匹配设计,每次查询只需遍历前缀字符串本身的长度。

前缀树将所有设备名称的公共前缀共享存储,搜索时间只与前缀长度相关,而与设备总数无关——这是它区别于其他数据结构的核心优势。

任务要求

  • 设计 TrieNode 节点,包含子节点映射 Map<Character, TrieNode>isEnd 标志
  • 实现 insert(deviceName):将设备名称插入前缀树
  • 实现 search(name):精确匹配查询某设备名称是否存在
  • 实现 startsWith(prefix):返回所有以给定前缀开头的设备名称列表(自动补全)
  • 实现 delete(deviceName):删除设备名称并清理无用节点
  • (进阶)统计以某前缀开头的设备数量,不必枚举所有结果

核心接口设计

JavaDeviceTrie.java
public class DeviceTrie {

    private final TrieNode root = new TrieNode();

    /** 插入设备名称 */
    public void insert(String deviceName) { ... }

    /** 精确查询设备名称是否存在 */
    public boolean search(String name) { ... }

    /** 前缀搜索:返回所有匹配的设备名称 */
    public List<String> autocomplete(String prefix) { ... }

    /** 删除设备名称(后序递归清理空节点) */
    public boolean delete(String deviceName) { ... }

    /** 统计以 prefix 开头的设备数量 */
    public int countWithPrefix(String prefix) { ... }
}

思考提示

节点的子节点用 HashMap 还是固定长度数组(26字母)?各有什么空间和时间的权衡?
删除操作如何识别"该节点不再有用"从而安全释放?
如果设备名称包含中文或特殊字符,如何扩展前缀树的字符集?

运维人员现在可以秒速找到任何设备。接下来进入系统最核心的业务场景之一:自动灌溉调度。大棚有数十个灌溉区域,每个区域的浇水任务都有各自的计划执行时间——有的在凌晨3点,有的在上午8点。系统需要保证最早到期的任务优先执行,这正是最小堆(延迟队列)大显身手的地方。

07
最小堆(延迟队列)

灌溉任务定时调度

业务背景

智慧大棚中有多个独立灌溉区域,每个区域的灌溉任务设定了精确的执行时刻(Unix 时间戳)。调度系统需要在任务到期时立即触发,同时不断接收新的调度任务。最小堆天然支持"快速找到最小值",堆顶始终是最近要执行的任务,时间复杂度:插入 O(log n),取最小值 O(1),出堆 O(log n)。

最小堆就像一个高效的闹钟管理器,无论你设了多少个闹钟,下一个最早响的闹钟永远在堆顶,一眼即知。

任务要求

  • 设计 IrrigationTask 类,包含任务ID、目标区域、计划执行时间(时间戳)、灌溉时长等字段,实现 Comparable 接口(按执行时间比较)
  • 基于数组手动实现最小堆 MinHeap<T extends Comparable<T>>,包含 heapifyUp(上浮)和 heapifyDown(下沉)操作
  • 实现 schedule(task)(添加调度任务)和 pollNext()(取出最近到期任务)
  • 实现调度主循环:轮询堆顶,当堆顶任务的执行时间 ≤ 当前时间时,出堆并执行
  • 支持取消任务:给定任务ID,将其从堆中移除并重新堆化

核心接口设计

JavaIrrigationScheduler.java
public class IrrigationScheduler {

    private MinHeap<IrrigationTask> taskHeap;

    /** 添加灌溉任务(自动按时间排序) */
    public void schedule(IrrigationTask task) { ... }

    /** 取出最近到期任务(不等待) */
    public IrrigationTask pollNext() { ... }

    /** 取消指定ID的任务 */
    public boolean cancel(String taskId) { ... }

    /** 调度主循环(阻塞式,直到有任务到期) */
    public void runScheduleLoop() {
        while (true) {
            IrrigationTask top = taskHeap.peek();
            if (top != null && top.executeAt <= System.currentTimeMillis()) {
                execute(taskHeap.poll());
            }
            // 短暂休眠,避免忙等
            Thread.sleep(100);
        }
    }
}

思考提示

手动实现最小堆时,父子节点的索引关系是什么?
如果同一时刻有多个任务到期,如何设计"次优先级"规则(如按区域ID)?
Java 内置的 PriorityQueue 如何实现延迟队列功能(DelayQueue)?与手动实现有何异同?

调度器已经能够精准触发每一次灌溉任务。但灌溉系统还有一个更底层的工程问题:如何在大棚内铺设水管网络?大棚中有多个灌溉节点(水源和出水口),它们之间可以用不同路径连接,每段管道的铺设费用不同。如何选择连接方案,使所有节点连通的同时总成本最低?这是一个经典的图论优化问题——最小生成树。

08
最小生成树

智能灌溉管网最优铺设

业务背景

大棚内有 N 个灌溉节点(水源、主管道分叉点、末端出水口),节点之间有 M 条潜在的连接路径,每条路径有对应的管道铺设成本(与距离、地形相关)。目标是从所有潜在路径中选出若干条路径,使所有节点恰好连通,且选出的路径总成本最小。这正是最小生成树(Minimum Spanning Tree, MST)问题,可用 Kruskal 或 Prim 算法求解。

最小生成树保证:用最少的管道总长度连通大棚内所有灌溉节点,每节省一米管道都是真实的降本增效。

任务要求

  • 设计 Node(灌溉节点)和 Edge(潜在管道路径,含起点、终点、权重)类
  • 构建图的邻接表或边集表示
  • 实现 Kruskal 算法:将所有边按权重排序,使用并查集(Union-Find)判断是否成环,逐步选入最短边
  • 实现 Prim 算法:从任意节点出发,每次用最小堆贪心选取连接已选集合的最短边
  • 输出最终选定的管道方案(边集)及总成本
  • (进阶)在 MST 基础上,考虑某条管道损坏后的最快修复重规划

核心接口设计

JavaPipelineOptimizer.java
public class PipelineOptimizer {

    private List<Node> nodes;
    private List<Edge> edges;

    /** Kruskal 算法求 MST */
    public List<Edge> kruskal() {
        // 1. 按边权重升序排序
        // 2. 初始化并查集
        // 3. 遍历每条边,若不成环则加入 MST
        ...
    }

    /** Prim 算法求 MST */
    public List<Edge> prim(int startNode) {
        // 1. 初始化访问集合和最小堆
        // 2. 每次取堆中最短边,若目标节点未访问则加入 MST
        ...
    }

    /** 并查集:查找根节点(路径压缩) */
    private int find(int[] parent, int x) { ... }

    /** 并查集:合并两个集合(按秩合并) */
    private void union(int[] parent, int[] rank, int a, int b) { ... }

    /** 计算选定方案的总成本 */
    public double totalCost(List<Edge> mst) { ... }
}

思考提示

Kruskal 与 Prim 的时间复杂度分别是多少?各适合什么规模的图?
并查集的路径压缩和按秩合并如何协同让 find/union 接近 O(1)?
如果部分节点之间必须连接(强制边),如何修改算法?

从数据结构到完整系统

每一种数据结构都不是孤立的知识点,它们共同构成了智慧农业大棚的技术骨架。

有序数组
历史温度查询
二分查找 · O(log n)
配置版本控制
LIFO · 撤销重做
队列
指令推送处理
FIFO · 顺序执行
哈希表
设备注册管理
O(1) · 即时检索
设备权限管理
层级结构 · 继承
前缀树
设备名称检索
前缀匹配 · O(m)
最小堆
灌溉定时调度
延迟队列 · 优先级
最小生成树
管网最优铺设
Kruskal · Prim

学习启示

数据结构的选择,本质上是对时间、空间、操作特性的综合权衡。在智慧农业这个真实场景中,我们看到了每种数据结构被选中的必然性——它们不是随机的,而是由业务需求倒逼出来的最优解。

希望通过这份任务单,你不仅能掌握这 8 种数据结构的实现细节,更能建立起"看到问题 → 抽象模型 → 选择结构 → 分析复杂度"的系统性思维,这正是一名优秀工程师最核心的能力。

#数据结构 #Java #智慧农业 #物联网 #算法与工程