TreeNode是WinForms控件类,非通用二叉树结构;应自定义含left、right、val的节点类,递归前必判空,中序遍历访问值在左右递归之间,推荐用yield return实现惰性迭代器而非List存储结果。

用 TreeNode 手写递归遍历,别碰 TreeView 控件
C# 里没有内置的「二叉树」类型,TreeNode 是 Windows Forms 控件配套类,不是通用数据结构。真要练算法,得自己定义节点类。否则一上来就绑 TreeView,遍历逻辑和 UI 渲染混在一起,改个中序顺序都得看控件文档。
常见错误现象:NullReferenceException 频发、遍历结果乱序、递归栈溢出(尤其深树没判空)
- 定义节点必须含
left、right、val三个字段,别漏public修饰符 - 递归函数入口一定要先判
if (node == null) return;—— 这是 90% 的空引用来源 - 前/中/后序只差三行代码位置:访问
val放在递归左/左右之间/递归右之后
示例(中序):
void InOrder(TreeNode node) {
if (node == null) return;
InOrder(node.left);
Console.WriteLine(node.val); // ← 这行位置决定遍历类型
InOrder(node.right);
}
IEnumerator<t></t> 实现迭代器,避免手动维护栈
递归写法简洁,但深度超 1000 层大概率爆栈;手写栈模拟又容易错乱。C# 的优势是能用 yield return 写惰性迭代器,调用方按需取值,内存友好且逻辑干净。
使用场景:需要遍历中途中断、配合 LINQ(如 .Skip(100).Take(10))、或嵌入异步流
- 迭代器方法返回
IEnumerable<t></t>,内部用Stack<treenode></treenode>模拟递归调用栈 - 中序迭代需「一路向左压栈」,弹出时才访问,再转向右子树——这个转向逻辑最容易漏掉
- 不要在迭代器里做耗时操作(如 IO),否则阻塞整个枚举过程
关键片段:
public IEnumerable<int> InOrderIterator() {
var stack = new Stack<TreeNode>();
var curr = root;
while (curr != null || stack.Count > 0) {
while (curr != null) {
stack.Push(curr);
curr = curr.left;
}
curr = stack.Pop();
yield return curr.val;
curr = curr.right; // ← 容易忘记这句,导致死循环
}
}
别用 List<t></t> 存遍历结果,除非你确定要全部加载
很多教程一上来就 var result = new List<int>()</int>,然后 .Add() 塞满再返回——这等于放弃 C# 迭代器的流式优势,还多占一倍内存。
性能影响:10 万节点的树,全量存 List 比迭代器慢 3–5 倍(GC 压力 + 内存分配)
- 如果只是校验「是否存在某值」,用
foreach配合break,遍历到就停 - 如果后续要多次遍历,才考虑转成
ToArray()或ToList()—— 但得确认数据量可控 - 调试时想看全部结果?直接
yourTree.InOrderIterator().ToArray()临时用,别写死在业务逻辑里
LeetCode 风格题用 int? 处理空节点,别硬套 null
刷题时输入常是 [1,null,2,3] 这种 JSON 表示,C# 解析后 TreeNode 的 left/right 是 null,但 val 本身不能为 null(int 是值类型)。这时候要么把 val 改成 int?,要么用特殊值(如 int.MinValue)标记空——后者极易和真实数据冲突。
兼容性注意:.NET Core 3.0+ 默认开启可空引用类型,但 int? 是可空值类型,不受此影响,放心用
- 定义节点时写
public int? val { get; set; },构造函数接受int? - 序列化/反序列化(如
System.Text.Json)会自动处理int?与null的映射 - 遍历中访问
val.Value前必须判val.HasValue,否则仍抛异常
真正卡住人的从来不是「怎么写三种遍历」,而是节点定义不一致、空值处理不统一、还有人试图用 TreeView.Nodes 当算法题解——那玩意儿底层是 Win32 API 封装,跟数据结构课没关系。


















