智慧农业大棚
数据处理与优化任务单
课程:数据结构 | 语言:Java
8 种核心数据结构 · 8 个工程子任务 · 1 个完整系统
国家战略 · 农业现代化 · 数字乡村振兴
这不仅是一道课程作业,更是国家战略需求的技术缩影。理解背景,才能理解代码的意义。
首次将"乡村振兴战略"写入党代会报告,推进农业现代化上升为国家战略
明确以信息化驱动农业现代化,部署建设智慧农业,重点支持物联网、大数据在设施农业中规模化应用
将设施农业(大棚)物联网列为重点任务,推进智能传感、边缘计算、数据处理等技术在温室大棚中的落地
大力发展智慧农业,推广物联网、大数据在设施农业中的应用,建立农业数据共享机制
将智慧农业作为引领农业生产方式变革的战略举措,明确支持大棚传感器网络、智能控制系统、数据分析平台等基础设施建设
用《数据结构》课程所学,用 Java 实现智慧大棚的核心数据处理模块——这不只是交作业,而是用代码呼应国家对数字农业人才的真实需求
痛点:传统农业大棚依赖人工管理温湿度、灌溉和施肥,效率低、误差大,随大棚规模扩大,设备从几台增长到数百台,数据体量爆炸式增长,人工管理彻底失效。
方向:构建以数据为核心的智慧大棚管理系统:传感器实时采集环境数据,控制器接收并执行指令,云端统一管理设备信息与权限,调度系统自动规划灌溉与管网铺设,形成感知—决策—执行的完整闭环。
新设备接入,哈希表快速存储与检索设备信息
基于树形结构的权限体系,管理不同角色对设备的访问控制
传感器持续产生温度等环境数据,以有序数组形式归档,支持快速查询
每次设备配置变更压入栈,支持一键回滚至任意历史版本
控制指令进入队列,按先入先出顺序保序执行,避免指令冲突
最小堆实现定时灌溉调度,最小生成树优化管网铺设路径
| 序号 | 任务名称 | 数据结构 | 核心特性 |
|---|---|---|---|
1 | 历史温度数据快速查询 | 有序数组 | 二分查找 O(log n) |
2 | 设备配置版本控制 | 栈 | 后进先出 · 版本回滚 |
3 | 指令推送与处理 | 队列 | 先进先出 · 顺序执行 |
4 | 设备注册与信息管理 | 哈希表 | 均摊 O(1) · 快速检索 |
5 | 设备权限管理 | 树 | 层级结构 · 继承传递 |
6 | 设备名称检索 | 前缀树 | 前缀匹配 · 自动补全 |
7 | 灌溉任务定时调度 | 最小堆 | 延迟队列 · 优先调度 |
8 | 智能灌溉管网最优铺设 | 最小生成树 | Kruskal / Prim · 最优路径 |
点击左右箭头按钮,或按键盘 ← → 键 / PageUp PageDown 翻页
鼠标悬停在页面内容上,滚动滚轮即可上下浏览当页完整内容
点击右上角"合上书本"按钮回到封面,或返回主页查看完整版
大棚温度传感器持续采集数据,长期运行后积累大量历史记录。农业专家和种植人员需要查询某一时刻的历史温度,用于分析作物生长环境、排查异常和辅助决策。若对这些数据仍采用线性遍历,每次查询都需扫描全部记录,效率低下。
典型场景:种植户发现黄瓜大棚出现叶片萎蔫,需回看当天 14:30 的大棚温度,并结合前后时刻数据,分析该时段是否出现温度骤升或骤降。
数据特征:温度记录按时间升序存储,数据规模大,查询频繁;若目标时刻无精确记录,则返回其之前最近的一条数据。因此,这类问题适合利用数据的有序性提高查找效率。
TemperatureRecord 实体类,包含 time(格式 "HH:mm")和 temperature(double,单位 °C)字段List<TemperatureRecord>findLatestTemperature(List<TemperatureRecord> records, String targetTime):使用二分查找,返回 ≤ targetTime 的最近一条记录的温度值;若所有记录都晚于 targetTime,返回 null创建 TemperatureRecord 类,包含 time(字符串 "HH:mm")和 temperature(double)两个字段及构造方法
令 left = 0,right = records.size() - 1,ans = -1(候选索引初值,-1 代表尚未找到满足条件的记录)
计算 mid,用 String.compareTo 比较时间:精确命中直接返回;mid < target 则更新 ans = mid 并向右收缩;mid > target 则向左收缩
循环结束后,ans == -1 返回 null;否则返回 records.get(ans).temperature
覆盖:精确命中某时刻、落在两条记录之间、早于最早记录(返回 null)、晚于最后记录(返回最后温度)
| 操作 | 时间复杂度 | 说明 |
|---|---|---|
| 二分查找最近温度 | O(log n) | 52万数据约20次比较 |
| 线性遍历(对比) | O(n) | 最坏需扫描全部记录 |
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?温室控制器的配置参数(如温度阈值、灌溉频率、报警上限)需要随季节和作物生长阶段频繁调整。每次更改配置后,如果效果不理想,运维人员希望能一键撤销,回到上次的配置状态。这种"撤销/重做"操作,天然契合后进先出(LIFO)的栈结构。
典型场景:春季番茄种植,温控阈值从18°C调整到20°C,但第二天发现幼苗有轻微冻害风险,运维人员需要立即回退到上一版本配置。如果用数组记录,需要手动维护"当前版本指针";用栈则天然支持 push/pop。
数据特征:版本变更频率低(每周几次);回滚操作需要即时响应;需要保留完整历史链路供审计;旧版本按时间自然淘汰。
DeviceConfig 配置类,包含版本号(自增)、修改时间(时间戳)、操作人、配置内容(JSON或Map)等字段Stack<T> 类,不允许直接使用 java.util.Stackpush(config)(提交新配置)、pop()(回滚至上一版本)、peek()(预览当前配置),所有操作 O(1) 时间复杂度数组栈:用 top 指针标记栈顶位置;链表栈:每次 push 在头部插入节点
push:检查栈满,若满则淘汰栈底;pop:检查栈空,返回并移除栈顶;peek:只返回不移除
当栈满时需"挤出"栈底元素:数组实现整体前移;循环数组:移动起始指针(O(1))
每次 push 时自动递增版本号,记录操作时间和操作人,便于审计和追溯
push 时对配置对象进行深拷贝,避免外部修改影响栈内历史版本
| 操作 | 复杂度 | 说明 |
|---|---|---|
| push(提交配置) | O(1) | 栈顶插入,常数时间 |
| pop(回滚) | O(1) | 栈顶弹出,常数时间 |
| peek(预览当前) | O(1) | 查看栈顶元素 |
| 遍历历史 | O(n) | n 为栈中版本数 |
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() { ... } }
云端控制台会同时向多台设备推送指令(如"设备A开启浇水→设备B关闭风扇→设备C调温至25°C"),设备执行器每次只能处理一条指令。必须保证先推送的指令先执行(FIFO),否则可能出现"水还没关就开始施肥"的错误操作。队列是这一场景下最直接的解决方案。
Command 指令类,包含指令ID、目标设备ID、指令类型、下发时间等字段CircularQueue<T>,包含 enqueue、dequeue、isEmpty、isFull 方法wait/notify)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() { ... } }
系统中每台设备(传感器、控制器、摄像头)都有唯一的设备ID(如 DEV-20240901-001)。在发送指令、查询状态、更新配置时,都需要通过ID即时找到设备的完整信息。哈希表将字符串ID映射为数组索引,实现均摊 O(1) 时间复杂度的增删查改,是设备注册表的最佳选择。
Device 类,包含设备ID、名称、类型、IP地址、在线状态、最后心跳时间等字段DeviceHashMap,内部使用数组 + 链表(拉链法)解决哈希冲突register(注册设备)、query(查询设备)、update(更新状态)、deregister(注销设备)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() { ... } }
大棚系统的用户角色形成天然的层级关系:超级管理员 → 区域负责人 → 技术员 → 操作员。父节点的权限自动传递给子节点,但子节点可以额外受限。同时,设备本身也组织成树形(大棚A区 → 温室1号 → 传感器组1 → 具体传感器)。用树来建模权限,不仅直观,还能用遍历算法高效处理权限继承与校验。
PermissionNode 节点类,包含角色名称、权限集合(读/写/控制)、子节点列表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) { ... } }
当设备数量增长到数百乃至数千台时,管理员在控制台输入 "green",系统需要在毫秒内列出所有以 "green" 开头的设备名称(如 greenhouse-A1-fan、greenhouse-B2-sensor……)。哈希表无法支持前缀查询,有序数组前缀查询效率也有限,而前缀树专为字符串前缀匹配设计,每次查询只需遍历前缀字符串本身的长度。
TrieNode 节点,包含子节点映射 Map<Character, TrieNode> 和 isEnd 标志insert(deviceName):将设备名称插入前缀树search(name):精确匹配查询某设备名称是否存在startsWith(prefix):返回所有以给定前缀开头的设备名称列表(自动补全)delete(deviceName):删除设备名称并清理无用节点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字母)?各有什么空间和时间的权衡?智慧大棚中有多个独立灌溉区域,每个区域的灌溉任务设定了精确的执行时刻(Unix 时间戳)。调度系统需要在任务到期时立即触发,同时不断接收新的调度任务。最小堆天然支持"快速找到最小值",堆顶始终是最近要执行的任务,时间复杂度:插入 O(log n),取最小值 O(1),出堆 O(log n)。
IrrigationTask 类,包含任务ID、目标区域、计划执行时间(时间戳)、灌溉时长等字段,实现 Comparable 接口(按执行时间比较)MinHeap<T extends Comparable<T>>,包含 heapifyUp(上浮)和 heapifyDown(下沉)操作schedule(task)(添加调度任务)和 pollNext()(取出最近到期任务)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); // 避免忙等 } } }
PriorityQueue 如何实现延迟队列功能(DelayQueue)?与手动实现有何异同?大棚内有若干灌溉节点(水源、管道分叉点、末端出水口),节点间存在多条潜在连接路径,每条路径有对应的铺设成本。真正的工程挑战不只是"把所有节点连通",连通只是基本要求,成本最小才是优化目标——同样实现连通的方案之间,成本可以相差悬殊。
每个水源、分叉点、出水口对应图中一个顶点
两节点之间可铺设的路径对应一条无向边
与距离、地形相关的铺设代价作为边权
因此,本任务等价于在带权无向图中求解最小生成树(Minimum Spanning Tree)——选出若干边,使所有顶点连通且总权值最小。
随机选边虽然可能实现连通,但存在两类致命问题:① 选了成本偏高的边;② 形成了回路(多余连接,增加不必要成本)。我们需要一种有规则的选边策略。
将所有潜在管道路径按铺设成本从低到高排序,优先考虑便宜的边
从排好序的边集中,逐条取出当前成本最低的边进行判断
若加入此边后不形成回路,则保留;若成环(形成冗余连接),则舍弃
当已选边数 = 节点数 − 1 时,所有节点恰好连通,算法结束
Node(灌溉节点)和 Edge(管道路径,含两端节点与权重)类绘制流程图时,至少体现以下关键节点(缺少任意一项均影响逻辑完整性):
Node(灌溉节点)和 Edge(管道路径,含两端节点与权重)类find(路径压缩)和 union(按秩合并)两个核心方法创建 Node 和 Edge 类,Edge 需实现 Comparable(按权值升序比较)
创建 parent[] 数组,初始时每个节点的父节点指向自身
用 Collections.sort(edges) 将所有边按权值从小到大排列
遍历排序后的边,调用 find 判断两端是否同根(即是否成环),不成环则 union 并加入结果集
输出 MST 边集和总成本;构造随机连接方案并输出其总成本,验证算法优越性
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 路径压缩解决了什么性能问题?每一种数据结构都不是孤立的知识点,它们共同构成了智慧农业大棚的技术骨架。
希望通过这份任务单,你不仅能掌握这 8 种数据结构的实现细节,更能建立起"看到问题 → 抽象模型 → 选择结构 → 分析复杂度"的系统性思维,这正是一名优秀工程师最核心的能力。
课程:数据结构 | 语言:Java
8 种核心数据结构 · 8 个工程子任务 · 1 个完整系统
国家战略 · 农业现代化 · 数字乡村振兴