地铁客流预测系统:从Hadoop到深度学习的完整大数据项目复盘 地铁客流预测系统到底怎么落地从Hadoop到深度学习的完整大数据项目复盘地铁调度员最怕的不是早晚高峰而是不知道明天早高峰到底会有多少人涌进站台。发车间隔排密了运力浪费、成本烧钱排疏了站台堆积、乘客投诉严重时还会触发限流甚至安全事故。所以各大城市地铁都在做客流预测本质上是想用历史数据回答一个问题明天早上8点到9点这个站会进多少人。我做的这套地铁客流量预测与数据可视化分析系统就是用Hadoop、Spark、Hive这套大数据技术栈把海量刷卡数据、天气数据、节假日数据全部管起来再配合机器学习与深度学习模型做短时客流预测最后用可视化把预测结果和历史规律直观呈现出来。这篇文章适合正在准备大数据方向课程设计、毕业设计或者想完整体验一个数据采集—数据仓库—特征工程—模型训练—可视化呈现全流程项目的朋友。我会把架构选型、数据落地、特征工程、模型对比、可视化实现以及我踩过的坑全部写出来都是可以直接复用的实践经验。1. 为什么地铁客流预测要上整套大数据栈技术选型的真实思考先回答一个很多人会问的问题预测客流而已拉出历史数据用Python的pandas读进来跑个机器学习模型不就行了吗为什么要上Hadoop、Spark、Hive这一大堆东西我的回答是单机方案在数据量小的时候确实没问题但一旦数据规模上来单机方案会同时死在三个地方——内存、计算时间和扩展性。1.1 数据规模决定架构地铁刷卡数据的量级估算先算一笔账。假设一个城市的地铁网络有200个站点每个站点每天大约有5万次进出站记录那么全城每天产生的交易记录就是200乘以5万等于1000万条。如果保存3年的历史数据大约就是10亿条级别的记录。每条记录至少包含卡号、站点编号、进出站时间、交易类型等十几个字段算下来单日原始数据就有几个GB。这种量级的数据单机pandas读进来非常吃力——DataFrame占内存经常是原始文件的好几倍10亿条记录直接就把普通开发机的内存撑爆了。所以这个项目必须用分布式存储和分布式计算。HDFS负责把这些数据分散存储在多台机器的磁盘上每一份数据保存多个副本即使某台机器挂了数据也不丢。Spark负责在内存里做分布式计算把原来单机跑不动的大查询拆成无数个小任务并行执行。Hive则起到数据仓库的作用把底层复杂的文件操作全部屏蔽掉让我们可以用SQL的方式查询数据大大降低开发成本。1.2 整个技术栈的分工逻辑谁负责存储、谁负责计算、谁负责建模很多初学者容易搞混Hadoop、Spark、Hive之间的关系。我用一个做饭的类比来解释HDFS是冰箱负责把食材原始数据储存起来Hive是菜谱整理员它不负责炒菜只负责把你想做什么菜SQL查询翻译成厨房的指令Spark是厨师真正负责炒菜分布式计算的人机器学习和深度学习的模型则是这家餐厅的招牌菜研发师它们读取前几个环节准备好的特征数据训练出预测模型。在这种分工下Hadoop负责解决数据放哪里的问题Spark负责解决数据怎么快速处理的问题Hive负责解决数据分析师怎么能用熟悉的方式访问数据的问题。机器学习和深度学习框架则站在最上层直接对接Spark产出的特征宽表。整个链路是清晰且各司其职的任何一个环节被替换或者省略都会在其他环节暴露问题。1.3 这套架构相比单机方案的优势到底在哪里除了容量上的优势分布式架构带来的第二个核心价值是计算效率。比如要统计过去一年全城200个站点每个小时的平均客流量单机可能要跑几个小时Spark用分布式的思想把一年的数据切割成多个分片在多个节点上同时做group by聚合通常几分钟就出结果了。这对于后面做特征工程非常重要因为时间窗口特征比如过去7天同一时段平均客流需要频繁做全局统计单机方案在迭代效率和交互体验上完全无法忍受。第三个优势是可扩展性。项目上线后如果数据量翻倍单机方案只能换更贵的服务器而分布式方案只需要往集群里加机器。Hadoop和Spark天生就是为横向扩展设计的这是生产环境最看重的能力。2. 数据落地的关键动作Hive表结构设计、分区策略与数据清洗整个项目的数据来源主要有四块AFC自动售检票系统的进出站刷卡记录、气象站发布的天气数据、节假日安排数据以及地铁线路站点的基础信息数据。其中刷卡记录数据量最大是预测模型的主要输入天气和节假日数据用来做辅助特征因为雨天和节假日对客流量的影响非常大。2.1 为什么把数据先放进Hive而不是直接给Spark读文件我在项目里选择了先把数据统一导入Hive这一步看起来多绕了一圈但实际收益非常大。Hive提供了表结构约束、分区管理和类SQL查询接口数据有了统一的Schema后续无论是Spark SQL读取还是临时统计都方便得多。如果没有这一层每次写Spark程序都要手动解析文件格式、处理脏数据工作量大且容易出错。来看一下我实际的建表语句这里面有几个设计细节值得展开说明CREATE EXTERNAL TABLE dwd_afc_record ( card_id STRING COMMENT 卡号, station_id INT COMMENT 站点编号, line_id INT COMMENT 线路编号, device_type STRING COMMENT 设备类型进站/出站, trans_time STRING COMMENT 交易时间 yyyy-MM-dd HH:mm:ss, is_peak_flag INT COMMENT 是否高峰期 1是 0否 ) PARTITIONED BY (dt STRING COMMENT 天分区) STORED AS PARQUET LOCATION /data/dw/dwd_afc_record;第一点我选择用外部表EXTERNAL TABLE而不是内部表。因为原始数据文件是由数据采集程序上传到HDFS的用外部表可以让Hive和原始文件互相独立删除表不会连带删除数据文件降低了误操作风险。第二点存储格式用了Parquet而不是普通文本格式因为Parquet是列式存储在查询时只需要读取涉及的列可以大幅减少I/O开销。在这个数据量级下文件格式的差别能带来好几倍的性能差距。第三点我用了天级分区每天的数据放进一个独立的HDFS目录查询时间范围时只需要扫描对应分区避免了全表扫描。2.2 分区字段、字段类型和Parquet格式的选择经验字段类型上我想多说一句。交易时间我一开始用的是TIMESTAMP类型后来发现各种日期函数转换起来效率不高而且前端可视化工具读取时经常出格式问题干脆在ODS层就转成STRING类型格式化好的yyyy-MM-dd HH:mm:ss字符串要算小时、算星期几直接用字符串函数截取就行。这里面没有绝对对错关键是和你后续的消费方式匹配。分区策略方面天级分区是最稳的方案。如果上线早高峰预测模型查询条件是某个具体日期甚至某个小时天级分区可以非常高效地剪裁数据。我也见过有人用小时级分区但那更适合实时流处理场景。对于离线训练集天级分区足够了。如果你要处理的数据量特别大还可以考虑在分区内再加一个bucket分桶按站点ID做哈希分桶这样站点维度的join和group操作会更加高效。2.3 数据清洗阶段那些不处理就会让模型学歪的脏数据数据清洗是数据项目中投入时间最多的环节没有之一。我在清洗中发现的问题主要有三类。第一类是重复数据同一张卡在完全相同的秒级时间戳下出现了两次进出站记录这种情况通常是因为前端系统重发了消息。解决方法是按照去重键card_id, station_id, trans_time做DISTINCT去重。第二类是缺失数据某个站点的设备在某个时段宕机导致该时段数据完全缺失。如果直接用有缺失的历史数据训练模型会认为那个时段客流为零这是非常危险的。我采取的策略是用同时段相邻站点的客流均值做插补如果缺失时段位于早高峰就取该站点前后两周同一时段的均值填充。第三类是异常数据卡号为空、进出站时间早于当天零点、进出站间隔超过24小时之类的逻辑异常这些记录要么删除要么打标不能直接参与特征计算。在数据清洗阶段我见过很多项目犯一个共同的错误——直接用原始数据建模不检查数据质量。结果模型训练出来之后效果差得离谱排查了半天才发现是数据源的问题。我的建议是清洗阶段宁可多花时间也要把每个字段的分布、极值、缺失率全部看一遍形成数据质量报告。这一步会为后续省下大量排查时间。3. Spark在项目中的角色ETL提速与特征工程实战细节数据落到Hive之后接下来最核心的工作就是基于这些原始数据计算特征。这个环节我选择Spark而不是直接用Hive SQL核心原因是Spark在迭代计算和复杂特征加工的灵活性上比Hive强太多。3.1 用Spark SQL把客流数据聚合成站点-时间段维度预测模型不是直接吃原始交易记录而是需要先把数据聚合成特定时间粒度的客流量。这个项目的预测粒度是小时所以第一步要按站点、按小时做聚合统计。代码逻辑如下val sparksql spark.sql( |SELECT station_id | ,dt | ,hour(from_unixtime(unix_timestamp(trans_time,yyyy-MM-dd HH:mm:ss),HH)) AS hour_slot | ,COUNT(DISTINCT card_id) AS passenger_cnt |FROM ods_afc_record |WHERE dt 2024-01-01 |GROUP BY station_id, dt, hour_slot .stripMargin)这个聚合操作在数据量大时特别考验平台的性能。因为要按站点和小时两个维度做去重计数Spark会触发shuffle如果数据倾斜严重个别热点站点的数据量可能是普通站点的几十倍导致某个Task长时间跑不完。我在实际执行时通过加盐salt的方式缓解了热点问题先按station_id 随机数进行预聚合再按station_id聚合整体提速了将近3倍。这个细节在教科书上很少讲但在真实项目中几乎是必须掌握的技能。3.2 特征工程是预测效果的分水岭时间特征、周期特征与外部特征模型准确率的高低七成取决于特征工程三成取决于模型选择。我在这个项目里主要构建了三类特征。第一类是时间特征。包括小时0到23、星期几0到6、是否工作日、是否节假日。这些特征看起来简单但实际作用非常大。地铁客流具有极强的时间规律性早高峰集中在7点到9点晚高峰集中在17点到19点工作日的客流曲线与周末截然不同。第五个特征是这是本周的哪一天第几周、是否月初月末也会影响通勤和消费客流。第二类是历史客流特征。包括过去1小时、2小时、3小时的同时段客流以及过去7天同一星期几、同一时段的平均客流。这些滞后特征能让模型捕捉到近期趋势和周期性规律。我用Spark的Window函数来实现下面是一个典型的滑动窗口特征代码import org.apache.spark.sql.expressions.Window val historyWin Window.partitionBy(station_id, hour_slot).orderBy(dt).rowsBetween(-7, -1) sparksql.withColumn(avg_week_same_hour, avg(passenger_cnt).over(historyWin)) .withColumn(last_week_same_hour, lag(passenger_cnt, 7).over(historyWin))这一部分的核心思路是让模型看到历史但因为时间序列的特殊性不能用未来数据构造特征否则会产生数据泄漏。我在第一次迭代时犯过这个错误把所有时间段的数据混在一起求均值导致模型在验证集上表现极好上线后效果崩盘。后来才明白时间序列问题必须严格按时间切分特征只能使用预测时刻之前的数据。第三类是外部环境特征。包括天气状况晴/雨/雪、温度、湿度以及是否特殊活动日。天气对客流的影响非常直接雨天很多人会放弃步行或者骑行转而选择地铁客流量会有明显上升而大型活动演唱会、体育赛事结束后体育场附近站点的客流会瞬间达到峰值。这些特征需要另外的数据源我通过城市天气API获取历史天气数据按天合并到特征表里。3.3 特征宽表的产出模型直接能吃的标准数据格式完成所有特征构建后最后一件事是生成一张特征宽表每一行代表某个站点在某个历史日期某个小时的特征向量 该小时的真实客流量。CREATE TABLE dws_passenger_feature ( station_id INT, dt STRING, hour_slot INT, is_workday INT, is_holiday INT, weather_type STRING, temperature DOUBLE, precipitation DOUBLE, lag_1h_cnt INT, lag_2h_cnt INT, lag_3h_cnt INT, avg_week_same_hour INT, last_week_same_hour INT, label_cnt INT ) PARTITIONED BY (dt STRING) STORED AS PARQUET;这张表做出来后机器学习模型和深度学习模型都可以直接消费不需要再关心原始数据在哪、怎么清洗。这也是整个数据项目中承上启下最关键的一步。4. 预测模型对比机器学习与深度学习各自的边界与选型逻辑特征表准备好之后进入了整个项目最显性的环节——建模。我分别用传统机器学习和深度学习各建了一套模型做了细致的对比。4.1 任务定义与评估指标为什么说这是一个回归问题地铁客流量预测在数学上被定义为一个回归问题给定历史特征X预测未来某个站点某个小时的客流量Y。Y是一个连续数值不是分类标签。评估指标我用了三个MAE平均绝对误差、RMSE均方根误差和MAPE平均绝对百分比误差。其中MAE的直观含义是平均每个站预测错多少人MAPE则是平均预测偏差百分之多少这两个指标最容易被业务方理解。实际效果来看工作日早高峰时段的峰值预测MAE大约在50到80人左右对于单小时上千人客流的站点来说误差率在5%到8%之间这个精度已经可以接受。但高峰时段的MAPE会明显高于平峰时段因为高峰时段客流基数大、波动也大。4.2 机器学习基线随机森林和XGBoost为什么能跑出还不错的成绩先看机器学习方案。我用了随机森林和XGBoost两个模型。很多人直觉以为时序预测应该用专门的时序模型但实际上树模型处理这类结构化特征非常有优势。原因是客流量预测的特征大多是类别特征和数值特征的混合树模型天然可以处理类别特征也天然能捕捉到特征之间的非线性交互关系比如是工作日 早高峰 下雨这三个特征组合起来对客流的拉动作用远不是简单的线性叠加。我在XGBoost上做了一些调参核心参数如下import xgboost as xgb model xgb.XGBRegressor( n_estimators500, max_depth6, learning_rate0.05, subsample0.8, colsample_bytree0.8, reg_alpha0.1, reg_lambda1.0, random_state42 )训练数据使用了按时间排序后的前80%作为训练集后20%作为验证集。这里必须强调时间序列数据不能像传统机器学习那样随机打乱划分否则会引入未来信息导致验证集效果虚高。我第一次就是没注意这一点验证MAE只有30人上线后实际MAE达到80人教训非常深刻。4.3 深度学习方案LSTM如何捕捉客流时序依赖深度学习方案我选择了LSTM长短期记忆网络它在处理时间序列数据时具有天然优势因为它内部有门控机制可以选择性记忆长期信息和遗忘不重要的信息。地铁客流本质上是一个强周期性的时间序列工作日和周末的曲线规律明显不同LSTM理论上能捕捉到这种长距离依赖。我把特征表按时间排序构造滑动窗口序列用前24小时的客流序列来预测下一个小时的客流。def create_sequences(data, seq_len24): sequences [] labels [] for i in range(len(data) - seq_len): sequences.append(data[i:i seq_len]) labels.append(data[i seq_len]) return np.array(sequences), np.array(labels)模型结构是两层LSTM加一层全连接回归层。训练时用Adam优化器初始学习率0.001损失函数是MSE。我在实际训练中发现LSTM对特征归一化非常敏感所有数值特征必须缩放到0到1的区间否则训练极不稳定。另外就是训练速度明显慢于XGBoost在同一批数据上XGBoost几分钟就能训完LSTM要训练几十个epoch在GPU上都需要十几分钟。4.4 两种模型的实测对比结果与结论我用同一个特征表分别训练了XGBoost和LSTM在验证集上的效果对比如下模型MAERMSEMAPE训练耗时XGBoost51人78人6.3%5分钟LSTM47人72人5.8%18分钟LSTM在各项指标上略优于XGBoostMAE低了大约4人MAPE低了0.5个百分点但训练时间几乎是后者的4倍。考虑到误差差距并不算大而工程复杂度明显更高实际生产环境我最终采用XGBoost作为主模型LSTM作为参考模型在高峰时段辅助修正。这种以机器学习为主、深度学习为辅的混合方案在业务落地时既保证了效果又控制了维护成本。5. 从预测到可视化整套地铁数据可视化分析系统的组成模块模型训练好之后必须把预测结果以可读的方式呈现给业务人员看。系统需要展示的不仅是明天几点客流多少这一行数字而是完整的数据分析视角历史客流走势、各站点拥挤度对比、未来一段时间预测曲线、以及异常预警。5.1 可视化大屏的模块设计哪些指标值得放上大屏一个优秀的地铁数据可视化系统大屏信息层级要非常清晰。我最终的页面分为四个核心区域顶部是全局概览全网当日总客流量、线路总客运量、同比环比变化率中间是客流热力地图按站点地理坐标渲染热力图层红色代表拥挤、绿色代表畅通左下是单站点客流曲线选择某个站点后展示过去7天实际客流和未来24小时预测客流的双曲线对比右下是异常预警列表当天预测客流量超过历史均值30%的站点自动标记为预警状态这种布局经过实际用户调研确认调度员最关心的信息一目了然不需要进行任何钻取操作就能掌握全局态势。5.2 数据流转链路从模型输出到前端图表预测模型每天凌晨跑一次离线预测产出第二全天每个站点每个小时的预测客流结果写回Hive表。然后通过预计算同步到MySQL供后端API实时查询。这里需要注意不要为了渲染一张大屏就去直接查HiveHive的查询延迟在秒级甚至分钟级完全不适合交互式查询。MySQL承担在线查询职责Hive承担离线计算职责两者各司其职。后端采用Spring Boot实现提供几个RESTful接口例如查询全网概况、查询站点未来24小时预测曲线、查询某个站点历史同期客流等。前端使用ECharts绘制折线图、热力图和地图组件。ECharts可能是目前对大屏适配最友好的开源图表库它自带地图注册机制把城市地理坐标JSON注册进去就能画出站点热力层。5.3 一个关键设计历史曲线与预测曲线的对比呈现大屏图表中我认为最有业务价值的是未来24小时预测曲线与过去7天同时间段的实际曲线的叠合对比。调度员看到这两条线之间的差距就能快速判断明天的客流走势是否有异常。如果预测线明显高于历史线说明明天可能有大客流事件需要提前安排备用列车或增派站务人员。这个功能的实现只是把历史实际值和模型预测值分别查出来用两条线画在同一个坐标轴上。代码逻辑不复杂但业务价值非常高。数据显示调度员最常用的功能就是这块。这也验证了一个道理数据系统的价值不在于用了多厉害的模型而在于是否把数据的洞察真正嵌入到了业务决策流程中。6. 项目真正跑通之前的那些坑从集群环境到模型上线的完整排错经验每个大数据项目的完整落地都伴随着一堆意想不到的问题。我把自己在这个项目里遇到的高频问题整理出来希望你能避开同样的坑。6.1 Hive表数据文件过小导致的Spark性能问题项目初期我把采集到的原始数据按天生成CSV文件直接上传到HDFS每个文件几十MB。随着天数增多HDFS上积累了数百个小文件。Spark读取时每个文件都会启动一个任务任务调度开销远大于计算本身导致整个ETL过程慢得出奇。解决方法是定期对小文件做合并。在Hive里动态分区插入时通过设置以下参数控制每个分区输出的文件大小SET hive.exec.dynamic.partition.modenonstrict; SET hive.merge.mapfilestrue; SET hive.merge.mapredfilestrue; SET hive.merge.size.per.task268435456;这样每个HDFS目录下只会保留少量的较大文件Spark读取性能恢复正常。在Hadoop体系里小文件问题是新手最容易忽视但影响最大的坑之一。6.2 特征工程中的数据泄漏时间序列预测最隐蔽的错误前面提过一次这里再展开细说。数据泄漏不是指数据被黑客窃取了而是指在建特征时用到了预测目标时刻之后才能获取的信息。比如我用全天的天气数据来预测当天早上8点的客流虽然训练集上效果飙升但在真实场景中8点的时候你不知道全天天气如何模型就废了。正确做法是对所有特征标记信息可用时刻确保每个特征在预测时刻都是已知的。天气特征可以用预测时段之前的天气预报数据不能用实况数据。这个教训让我在后续所有时序项目中都养成一个习惯每个特征写入后都标注其最新可用时间并且写一个断言函数在训练前自动检查防止某个特征偷看未来。6.3 节假日效应模型在法定节假日完全失效的问题第一版模型在普通工作日和周末表现都还可以但在清明节、国庆节这类法定节假日表现极差预测误差飙升到30%以上。原因是这类节假日的客流模式既不同于工作日也不同于周末它呈现的是类周末出行高峰的混合形态训练集中这类样本太少模型根本学不到规律。解决思路是把节假日作为强特征传入模型并单独训练一个节假日修正器先用常规模型做基线预测然后根据节假日类型、放假第几天等特征用线性回归学习一个修正偏移量。经过这层修正后节假日预测误差从30%降到了12%左右虽然仍然偏高但至少达到了可用的范围。6.4 环境搭建阶段的版本兼容性选择最后给做课程设计或毕业设计的同学一个实用建议搭建Hadoop、Spark、Hive环境时务必注意版本兼容性。Hadoop 2.x和Hadoop 3.x对Spark的编译版本要求不同Hive和Spark之间的元数据通信也有严格匹配要求。我的建议是选择CDH或HDP的发行版全家桶方案这些发行版已经对版本组合做了完整测试能省掉大量环境排错时间。如果你只是单机环境跑通流程直接用伪分布式模式也可以二三十GB内存的开发机完全带得动一个迷你集群。6.5 系统上线后还需要什么定时调度与效果监控模型训练和预测不能只手动跑一次上线之后必须做自动化调度。我使用Azkaban设置了两个定时任务每日凌晨2点执行核心数据的ETL任务凌晨4点执行特征构建和模型预测任务早上6点之前把预测结果同步到MySQL并刷新大屏缓存。这样调度员早上到岗时看到的已经是当天最新的预测数据了。同时还保留了模型效果监控表每天自动对比前一天预测值和实际值计算MAE和MAPE。如果连续三天误差超过阈值系统自动发告警邮件提醒维护人员检查数据源或重新训练模型。这一步非常必要因为客流模式会随着季节、突发事件、线路变化而缓慢漂移模型定期重训是保证长期稳定效果的底线。做这个项目最大的一个感悟是所谓大数据系统真正难的不是单个技术组件而是让所有组件在一条完整的数据链路上无缝协作。从HDFS存储到Hive数仓建模到Spark分布式计算再到机器学习模型训练和可视化呈现每一个环节都像流水线上的一环任何一环出了问题整个系统的输出都会失真。另一个深刻的体会是模型永远只是项目的一小部分。数据质量、特征设计、效果监控这些苦活才是决定项目能否真正为业务创造价值的关键。希望这篇复盘能帮你少踩一些坑把同样的技术栈玩出自己的成品。

相关新闻

最新新闻

Makefile入门到实践:掌握依赖管理与增量构建

Makefile入门到实践:掌握依赖管理与增量构建

1. 从手动编译到自动化构建:为什么要用 make/Makefile如果你在 Linux 下写过稍微大一点的 C/C 项目,大概率经历过这样的场面:改了其中一个.c文件,然后重新敲一遍 gcc 命令把整个项目重新编译一遍。小项目还好,文件一多…

2026/9/9 20:07:17
深入HashMap底层:哈希冲突、扩容与Java输出顺序解析

深入HashMap底层:哈希冲突、扩容与Java输出顺序解析

很多朋友一听到哈希表,第一反应就是面试题里的“哈希函数、冲突、扩容”三件套,但到了真正写代码的时候,反而连一个最典型的问题都容易卡壳:Java 哈希表输出顺序,为什么偏偏和插入顺序不一样。我最早学 HashMap 也是这…

2026/9/9 20:07:17
MATLAB/Simulink双馈风机接入三机九节点系统仿真模型详解

MATLAB/Simulink双馈风机接入三机九节点系统仿真模型详解

在风电并网仿真这个圈子里,能跑的东西很多,但“能稳定跑、参数全亮、随改随用”的模型反而稀有。我有段时间给课题组做双馈风机接入多机系统的算例,经常碰到的情况是下载一个模型,打开后满屏报错,或者求解器跑几分钟就…

2026/9/9 20:07:17
Delphi 10.1 Berlin 下 Raize Components 组件库安装部署与使用指南

Delphi 10.1 Berlin 下 Raize Components 组件库安装部署与使用指南

简介:这是一份针对 Delphi Berlin(10.1)环境修正的 Raize Components 6.1.1.12 控件包,专为遇到 PAS 文件错误导致编译失败的用户准备。压缩包内置安装说明,已预先调整配置文件,省去手动排查与改动的麻烦&a…

2026/9/9 20:07:17
变步长LMS自适应滤波算法:原理、Matlab实现与工程调参实战

变步长LMS自适应滤波算法:原理、Matlab实现与工程调参实战

做信号处理的人,应该都绕不过自适应滤波;而自适应滤波里,LMS(最小均方)算法又是最常被拿出来用的那一档。但真正在工程里跑过LMS的同学一定深有体会:固定步长的版本很容易让人头疼。步长调大了,…

2026/9/9 20:07:17
二叉搜索树第K小元素:从递归到Morris遍历的三种解法

二叉搜索树第K小元素:从递归到Morris遍历的三种解法

刷过 LeetCode Hot 100 的朋友应该都能感受到,二叉搜索树(BST)这一块几乎是必考内容。230 这道题,题面看起来简单——给定一棵二叉搜索树,找出其中第 K 小的元素,难度标着 Medium,但里面藏着的门…

2026/9/9 20:02:17