数据结构 · 智慧农业大棚 · Java
课程任务单 · 数据结构 · Java

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

课程:数据结构 语言:Java
8 种核心数据结构 · 8 个工程子任务 · 1 个完整系统
乡村振兴战略 数字农业 国家粮食安全 农业现代化
点击封面打开  ·  按键翻页
Smart Agriculture · Data Structures · Java
第一章 · 国家政策背景

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

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

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

首次将"乡村振兴战略"写入党代会报告,推进农业现代化上升为国家战略

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

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

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

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

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

大力发展智慧农业,推广物联网、大数据在设施农业中的应用,建立农业数据共享机制

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

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

NOW
你的课程任务

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

1
第二章 · 系统架构与业务链路

智慧大棚系统整体架构

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

痛点:传统农业大棚依赖人工管理温湿度、灌溉和施肥,效率低、误差大,随大棚规模扩大,设备从几台增长到数百台,数据体量爆炸式增长,人工管理彻底失效。

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

感知层
温度传感器 湿度传感器 光照传感器 CO₂传感器
↓
数据处理层
历史查询(有序数组) 版本控制(栈) 指令队列(队列) 定时调度(最小堆)
↓
设备管理层
设备注册(哈希表) 权限树(树) 名称检索(前缀树)
↓
决策优化层
管网铺设(最小生成树) 最优调度
核心业务链路
01
设备上线注册

新设备接入,哈希表快速存储与检索设备信息

02
权限分配

基于树形结构的权限体系,管理不同角色对设备的访问控制

03
数据采集与存储

传感器持续产生温度等环境数据,以有序数组形式归档,支持快速查询

04
配置版本管理

每次设备配置变更压入栈,支持一键回滚至任意历史版本

05
指令下发执行

控制指令进入队列,按先入先出顺序保序执行,避免指令冲突

06
智能调度与优化

最小堆实现定时灌溉调度,最小生成树优化管网铺设路径

2
第三章 · 数据结构总览

数据结构与任务对应总览

序号任务名称数据结构核心特性
1历史温度数据快速查询有序数组二分查找 O(log n)
2设备配置版本控制栈后进先出 · 版本回滚
3指令推送与处理队列先进先出 · 顺序执行
4设备注册与信息管理哈希表均摊 O(1) · 快速检索
5设备权限管理树层级结构 · 继承传递
6设备名称检索前缀树前缀匹配 · 自动补全
7灌溉任务定时调度最小堆延迟队列 · 优先调度
8智能灌溉管网最优铺设最小生成树Kruskal / Prim · 最优路径
整体框架已经清晰,接下来我们将逐一深入每个子任务——每一项任务都是这个系统不可或缺的一块拼图。
阅读说明
翻页方式

点击左右箭头按钮,或按键盘 ← → 键 / PageUp PageDown 翻页

页内滚动

鼠标悬停在页面内容上,滚动滚轮即可上下浏览当页完整内容

合上书本

点击右上角"合上书本"按钮回到封面,或返回主页查看完整版

3
任务一 · 有序数组
01
有序数组 · Binary Search

历史温度数据快速查询

业务背景

大棚温度传感器持续采集数据,长期运行后积累大量历史记录。农业专家和种植人员需要查询某一时刻的历史温度,用于分析作物生长环境、排查异常和辅助决策。若对这些数据仍采用线性遍历,每次查询都需扫描全部记录,效率低下。

典型场景:种植户发现黄瓜大棚出现叶片萎蔫,需回看当天 14:30 的大棚温度,并结合前后时刻数据,分析该时段是否出现温度骤升或骤降。

数据特征:温度记录按时间升序存储,数据规模大,查询频繁;若目标时刻无精确记录,则返回其之前最近的一条数据。因此,这类问题适合利用数据的有序性提高查找效率。

有序数组配合二分查找,将"找最近温度"的复杂度从 O(n) 降至 O(log n),无需遍历全部数据即可快速定位目标记录。
为什么选有序数组?
✅ 有序数组
• 二分查找 O(log n) • 内存连续,缓存友好 • 天然有序,无需额外排序 • 实现简单直观
❌ 链表
• 只能顺序遍历 O(n) • 内存不连续,缓存命中率低 • 无法使用二分查找
❌ 哈希表
• 精确匹配 O(1) • 无法找"最近 ≤ target" • 不支持顺序关系查询
4
任务一 · 要求与实现
任务要求
  • 设计 TemperatureRecord 实体类,包含 time(格式 "HH:mm")和 temperature(double,单位 °C)字段
  • 维护一个按时间升序排列的 List<TemperatureRecord>
  • 实现 findLatestTemperature(List<TemperatureRecord> records, String targetTime):使用二分查找,返回 ≤ targetTime 的最近一条记录的温度值;若所有记录都晚于 targetTime,返回 null
  • 编写完整测试用例,覆盖:精确命中、落在两个时间之间、早于最早记录、晚于最后记录等边界情况
实现步骤指引
Step 1
定义数据实体

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

Step 2
初始化二分查找边界

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

Step 3
二分查找核心循环

计算 mid,用 String.compareTo 比较时间:精确命中直接返回;mid < target 则更新 ans = mid 并向右收缩;mid > target 则向左收缩

Step 4
返回结果

循环结束后,ans == -1 返回 null;否则返回 records.get(ans).temperature

Step 5
测试用例设计

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

性能分析
操作时间复杂度说明
二分查找最近温度O(log n)52万数据约20次比较
线性遍历(对比)O(n)最坏需扫描全部记录
5
任务一 · 接口设计与思考
代码框架
Java TemperatureSearch.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 的记录",代码应如何调整?
→ 过渡:温度数据已经可以高效检索,但大棚中的设备不是一成不变的——每次调整传感器灵敏度、更换控制策略,都需要修改设备配置。如果某次更新出现问题,能否快速回滚到上一个可用版本?这就引出了下一个任务——栈。
6
任务二 · 栈
02
栈 · LIFO

设备配置版本控制

业务背景

温室控制器的配置参数(如温度阈值、灌溉频率、报警上限)需要随季节和作物生长阶段频繁调整。每次更改配置后,如果效果不理想,运维人员希望能一键撤销,回到上次的配置状态。这种"撤销/重做"操作,天然契合后进先出(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 个版本),栈满时自动淘汰最旧版本(栈底元素)
  • (进阶)实现双栈设计:一个操作栈 + 一个撤销栈,支持"撤销"与"重做"双向操作
7
任务二 · 实现与接口
实现步骤指引
Step 1
选择底层实现

数组栈:用 top 指针标记栈顶位置;链表栈:每次 push 在头部插入节点

Step 2
实现基本栈操作

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

Step 3
处理栈满逻辑

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

Step 4
版本号管理

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

Step 5
深拷贝配置对象

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

性能分析
操作复杂度说明
push(提交配置)O(1)栈顶插入,常数时间
pop(回滚)O(1)栈顶弹出,常数时间
peek(预览当前)O(1)查看栈顶元素
遍历历史O(n)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() { ... }
}
思考提示
如何在有最大深度限制的情况下,既保留最新版本又不丢失最旧版本(循环数组)?
能否基于两个栈实现"撤销"与"重做"双向操作?
配置内容是否需要深拷贝?对象引用可能带来哪些问题?
8
任务三 · 队列
03
队列 · FIFO

指令推送与处理

业务背景

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

队列就像流水线传送带,指令从一端进入、从另一端依次取出执行,严格保序,绝不跳号。
任务要求
  • 设计 Command 指令类,包含指令ID、目标设备ID、指令类型、下发时间等字段
  • 基于循环数组手动实现 CircularQueue<T>,包含 enqueue、dequeue、isEmpty、isFull 方法
  • 模拟生产者-消费者场景:一个线程不断入队指令,另一个线程模拟设备执行器出队并处理
  • 实现队列满时的阻塞策略(简单起见可用轮询或 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 队列是否足够?能否引入优先级队列?
9
任务四 · 哈希表
04
哈希表 · HashMap

设备注册与信息管理

业务背景

系统中每台设备(传感器、控制器、摄像头)都有唯一的设备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) { ... }
    public Device query(String deviceId) { ... }
    public void updateStatus(String deviceId, boolean online) { ... }
    public void deregister(String deviceId) { ... }
    private void rehash() { ... }
}
思考提示
如何设计哈希函数使其分布均匀,减少冲突?
拉链法与开放地址法各有什么优劣?在大棚设备场景下哪个更合适?
rehash 时如何保证线程安全?
10
任务五 · 树
05
树 · N-ary Tree

设备权限管理

业务背景

大棚系统的用户角色形成天然的层级关系:超级管理员 → 区域负责人 → 技术员 → 操作员。父节点的权限自动传递给子节点,但子节点可以额外受限。同时,设备本身也组织成树形(大棚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)来高效表示多种权限的组合?
若一个用户属于多个角色(多重继承),如何处理权限合并?
11
任务六 · 前缀树
06
前缀树 · Trie

设备名称检索

业务背景

当设备数量增长到数百乃至数千台时,管理员在控制台输入 "green",系统需要在毫秒内列出所有以 "green" 开头的设备名称(如 greenhouse-A1-fan、greenhouse-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) { ... }
    public int countWithPrefix(String prefix) { ... }
}
思考提示
节点的子节点用 HashMap 还是固定长度数组(26字母)?各有什么空间和时间的权衡?
删除操作如何识别"该节点不再有用"从而安全释放?
如果设备名称包含中文或特殊字符,如何扩展前缀树的字符集?
12
任务七 · 最小堆
07
最小堆 · Min-Heap

灌溉任务定时调度

业务背景

智慧大棚中有多个独立灌溉区域,每个区域的灌溉任务设定了精确的执行时刻(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() { ... }
    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)?与手动实现有何异同?
13
任务八 · 最小生成树
08
最小生成树 · MST

智能灌溉管网最优铺设

课前任务
上课前请完成以下准备:
灌溉区域节点图
4 6 8 3 5 7 9 2 6 10 A B C D E F 可选路径 n = 铺设成本
观察 图中有 6 个灌溉节点(A~F),虚线为可铺设路径,数字为该段铺设成本
尝试 选择其中若干条路径,确保所有节点都连通,把你选的路径圈出来
计算 把你选中路径的成本加起来,记录总成本,想想还有没有更省的连法
有的同学会先想"怎么把点连起来",有的同学会关注"哪种连法更省成本"——两个角度都对,课上我们会把它们合在一起讨论。
业务背景

大棚内有若干灌溉节点(水源、管道分叉点、末端出水口),节点间存在多条潜在连接路径,每条路径有对应的铺设成本。真正的工程挑战不只是"把所有节点连通",连通只是基本要求,成本最小才是优化目标——同样实现连通的方案之间,成本可以相差悬殊。

认知关键:"能连通"≠"方案最优"。随机连接虽然也能接通所有节点,但每次结果成本不稳定、普遍偏高。我们需要一种有规则的选边方法,才能稳定找到成本最低的方案。
建模抽象:从管网到图
灌溉节点
→ 图的顶点(Vertex)

每个水源、分叉点、出水口对应图中一个顶点

潜在管道
→ 图的边(Edge)

两节点之间可铺设的路径对应一条无向边

铺设成本
→ 边的权值(Weight)

与距离、地形相关的铺设代价作为边权

因此,本任务等价于在带权无向图中求解最小生成树(Minimum Spanning Tree)——选出若干边,使所有顶点连通且总权值最小。

14
任务八 · 克鲁斯卡尔算法
为什么不能随机连接?

随机选边虽然可能实现连通,但存在两类致命问题:① 选了成本偏高的边;② 形成了回路(多余连接,增加不必要成本)。我们需要一种有规则的选边策略。

克鲁斯卡尔算法:四步核心思路
Step 1
边按权值排序

将所有潜在管道路径按铺设成本从低到高排序,优先考虑便宜的边

Step 2
依次取出最小边

从排好序的边集中,逐条取出当前成本最低的边进行判断

Step 3
判断是否成环

若加入此边后不形成回路,则保留;若成环(形成冗余连接),则舍弃

Step 4
直到所有节点连通

当已选边数 = 节点数 − 1 时,所有节点恰好连通,算法结束

克鲁斯卡尔做了两件事:优先选成本低的边 + 避免形成回路。正因为同时满足这两点,才能在保证连通的前提下,逐步逼近总成本最小。
任务要求
  • 设计 Node(灌溉节点)和 Edge(管道路径,含两端节点与权重)类
  • 实现 Kruskal 算法:边集排序 + 并查集(Union-Find)判断是否成环,逐步选入最低成本边
  • 实现 Prim 算法:从任意节点出发,每次用最小堆贪心选取连接已选集合的最短边
  • 输出最终管道方案(边集)及总铺设成本,并与随机连接方案进行对比
  • (进阶)某条管道损坏后,如何快速重新规划,找出次优方案
15
任务八 · 流程图设计与代码实现
克鲁斯卡尔算法流程图要素

绘制流程图时,至少体现以下关键节点(缺少任意一项均影响逻辑完整性):

✅ 必须包含的步骤
① 输入图数据(节点 + 边) ② 边集按权值升序排序 ③ 循环:依次取出当前最小边 ④ 判断加入后是否成环 ⑤ 不成环 → 加入生成树 ⑥ 判断边数是否已达 n−1 ⑦ 输出最小生成树及总成本
❌ 常见错误
漏掉排序步骤(算法起点) "成环"与"不成环"未分支 结束条件写成"所有边遍历完" 把演示动画照搬,未提炼逻辑
课时过渡:流程图已经梳理完毕。下一课时将把上述流程逐步转化为 Java 代码,并通过与随机连接方案的对比,验证 Kruskal 算法的优越性。
16
任务八 · 第二课时
第一课时成果回顾
①
明确需求
连通是基本要求,总成本最小才是优化目标
②
建立图模型
节点→顶点,管道→边,成本→权值
③
设计流程图
选择 Kruskal 算法并整理出完整执行流程
第二课时 · 把流程落到程序中
现在我们已经清楚了算法的流程,接下来继续往前推进——把流程真正落到程序中。
第二课时编程任务
  • 设计 Node(灌溉节点)和 Edge(管道路径,含两端节点与权重)类
  • 实现 Kruskal 算法:将边集按权值排序,用并查集(Union-Find)判断是否成环,逐步选入最低成本边
  • 并查集需实现 find(路径压缩)和 union(按秩合并)两个核心方法
  • 输出最终管道方案(边集)及总铺设成本,并与随机连接方案对比验证
  • (进阶)实现 Prim 算法:从任意节点出发,每次用最小堆贪心选取最短边
实现步骤指引
Step 1
定义数据类

创建 Node 和 Edge 类,Edge 需实现 Comparable(按权值升序比较)

Step 2
初始化并查集

创建 parent[] 数组,初始时每个节点的父节点指向自身

Step 3
边集排序

用 Collections.sort(edges) 将所有边按权值从小到大排列

Step 4
贪心选边

遍历排序后的边,调用 find 判断两端是否同根(即是否成环),不成环则 union 并加入结果集

Step 5
验证与对比

输出 MST 边集和总成本;构造随机连接方案并输出其总成本,验证算法优越性

17
任务八 · 代码框架与思考
代码框架
JavaPipelineOptimizer.java
public class PipelineOptimizer {
    private List<Node> nodes;
    private List<Edge> edges;

    /** Kruskal:排序 + 并查集判环,返回 MST 边集 */
    public List<Edge> kruskal() { ... }

    /** 并查集 find(路径压缩) */
    private int find(int[] parent, int x) {
        if (parent[x] != x)
            parent[x] = find(parent, parent[x]);
        return parent[x];
    }

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

    /** (进阶)Prim:最小堆贪心扩展 */
    public List<Edge> prim(int start) { ... }

    /** 对比:MST 总成本 vs 随机连接总成本 */
    public void compareWithRandom() { ... }
}
性能分析
操作时间复杂度说明
边集排序O(E log E)E 为边数
并查集 find/union≈ O(1)路径压缩+按秩合并后接近常数
Kruskal 整体O(E log E)瓶颈在排序步骤
Prim(最小堆)O(E log V)适合稠密图,V 为顶点数
思考提示
并查集是如何判断"加入这条边会不会成环"的?find 路径压缩解决了什么性能问题?
Kruskal 适合稀疏图,Prim 适合稠密图——为什么?边数 E 和顶点数 V 的比值如何影响选择?
如果某两个节点之间必须铺管道(强制边),算法应如何调整?
18
总结 · 从数据结构到完整系统

八种数据结构 · 一个完整系统

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

有序数组
历史温度查询
二分查找 · O(log n)
栈
配置版本控制
LIFO · 撤销重做
队列
指令推送处理
FIFO · 顺序执行
哈希表
设备注册管理
O(1) · 即时检索
树
设备权限管理
层级结构 · 继承
前缀树
设备名称检索
前缀匹配 · O(m)
最小堆
灌溉定时调度
延迟队列 · 优先级
最小生成树
管网最优铺设
Kruskal · Prim
学习启示:数据结构的选择,本质上是对时间、空间、操作特性的综合权衡。在智慧农业这个真实场景中,我们看到了每种数据结构被选中的必然性——它们不是随机的,而是由业务需求倒逼出来的最优解。

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

19

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

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

8 种核心数据结构 · 8 个工程子任务 · 1 个完整系统

国家战略 · 农业现代化 · 数字乡村振兴

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

返回主页面(完整滚动版)

点击封面打开 · 箭头键 / 按钮翻页
封面