数据结构 · 智慧农业大棚 · 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")和 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,用 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>,包含 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 队列是否足够?能否引入优先级队列?
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-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) { ... }
    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
定义数据类

创建 NodeEdge 类,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 #智慧农业 #物联网 #算法与工程

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

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