8.4 KiB
先序遍历原理:从树结构到实际应用的深度解析
先序遍历(Pre-order Traversal)是树结构中最基础、最常用的遍历方式之一,其核心思想是“先访问根节点,再递归遍历左子树,最后递归遍历右子树”。在部门树形结构等场景中,先序遍历的衍生设计(如左值右值标记法)能极大提升数据操作效率。以下从基础概念到实际应用展开详细解说。
一、先序遍历的基础定义与核心规则
1. 树结构的基本概念
在讲解先序遍历前,需明确树的核心术语:
- 节点:树中的每个元素(如部门树形结构中的“部门”)。
- 根节点:树的顶层节点(无父节点,如公司总部)。
- 子节点:被父节点直接包含的节点(如“技术部”是“研发中心”的子节点)。
- 叶子节点:无任何子节点的节点(如“前端开发组”)。
- 路径:从根节点到某一节点的所有节点组成的序列。
2. 先序遍历的核心逻辑
先序遍历的遍历顺序可概括为 “根 → 左 → 右”,即:
- 首先访问当前节点(记录或处理节点信息);
- 递归地对当前节点的左子树执行先序遍历;
- 递归地对当前节点的右子树执行先序遍历。
示例:二叉树的先序遍历
对如下二叉树(A为根节点,B、C为子节点):
A
/ \
B C
/ \
D E
先序遍历顺序为:
A → B → D → E → C
- 访问根节点A → 遍历左子树B → 访问B → 遍历B的左子树D → 访问D(无左右子树)→ 遍历B的右子树E → 访问E → 左子树遍历完成 → 遍历根节点A的右子树C → 访问C。
二、多叉树的先序遍历(部门树场景适配)
部门树等实际场景中,树通常是多叉树(一个父节点可包含多个子节点),先序遍历规则调整为:
“根 → 子树1 → 子树2 → ... → 子树n”,即访问根节点后,按顺序递归遍历每个子树。
1. 多叉树先序遍历示例
以某公司部门树为例(根节点为“总公司”,下设“技术部”“市场部”,技术部下设“前端组”“后端组”):
总公司(根节点)
/ \
技术部 市场部
/ \
前端组 后端组
先序遍历顺序为:
总公司 → 技术部 → 前端组 → 后端组 → 市场部
- 访问总公司 → 遍历第一个子树“技术部” → 访问技术部 → 遍历技术部第一个子树“前端组” → 访问前端组(无子节点)→ 遍历技术部第二个子树“后端组” → 访问后端组 → 技术部子树遍历完成 → 遍历总公司第二个子树“市场部” → 访问市场部。
三、先序遍历在部门树存储中的核心应用:左值(lft)与右值(rgt)标记
在部门树形结构存储优化中,先序遍历的核心价值在于通过**“进入标记”和“离开标记”** 记录节点的层级关系,即左值(lft)和右值(rgt)的设计。
1. 标记规则
遍历过程中,每个节点会被标记两个值:
- 左值(lft):首次访问节点(进入节点)时记录的序号(从1开始递增);
- 右值(rgt):遍历完该节点的所有子节点后(离开节点)记录的序号。
2. 标记示例(基于上述部门树)
遍历步骤 | 操作 | 节点 | lft值 | rgt值 |
---|---|---|---|---|
1 | 进入根节点 | 总公司 | 1 | - |
2 | 进入子节点 | 技术部 | 2 | - |
3 | 进入子节点 | 前端组 | 3 | - |
4 | 离开前端组(无子节点) | 前端组 | - | 4 |
5 | 进入子节点 | 后端组 | 5 | - |
6 | 离开后端组(无子节点) | 后端组 | - | 6 |
7 | 离开技术部(所有子节点遍历完成) | 技术部 | - | 7 |
8 | 进入子节点 | 市场部 | 8 | - |
9 | 离开市场部(无子节点) | 市场部 | - | 9 |
10 | 离开总公司(所有子节点遍历完成) | 总公司 | - | 10 |
最终标记结果:
节点 | lft | rgt |
---|---|---|
总公司 | 1 | 10 |
技术部 | 2 | 7 |
前端组 | 3 | 4 |
后端组 | 5 | 6 |
市场部 | 8 | 9 |
3. 核心规律(标记的价值所在)
通过先序遍历标记的lft和rgt,形成以下关键规律,支撑高效查询:
- 父子关系:父节点的lft < 所有子节点的lft,且父节点的rgt > 所有子节点的rgt(如总公司lft=1 < 技术部lft=2,rgt=10 > 技术部rgt=7)。
- 子孙范围:一个节点的所有子孙节点,其lft和rgt均在该节点的lft和rgt之间(如技术部的子孙节点前端组、后端组,lft均在2-7之间)。
- 叶子节点特征:叶子节点无子孙,因此rgt = lft + 1(如前端组lft=3,rgt=4=3+1)。
- 子孙数量计算:某节点的子孙总数 = (rgt - lft - 1) / 2(如技术部子孙数=(7-2-1)/2=2,即前端组和后端组)。
四、先序遍历的优缺点与适用场景
1. 优势
- 查询效率极高:基于lft和rgt的规律,无需递归即可快速查询子孙节点、计算数量、判断叶子节点等,时间复杂度低。
- 关系表达直观:通过数值范围直接体现层级关系,避免传统parent_id的链式依赖。
2. 局限性
- 维护成本高:新增或删除节点时,需批量更新后续节点的lft和rgt(如插入一个节点需给所有lft大于插入位置的节点+2),数据量大时操作耗时。
- 初始化复杂:现有树形结构迁移时需全量遍历计算lft和rgt,层级过深或数据量大时成本高。
3. 适用场景
最适合查询频繁、结构稳定、数据量中等的树形场景,如:
- 企业组织架构(部门层级变动少,查询需求多);
- 权限菜单树(菜单结构稳定,需频繁查询子菜单);
- 分类目录树(如电商商品分类,查询远多于增删)。
五、总结
先序遍历作为树结构的基础遍历方式,其核心是“先根后子”的访问顺序。在部门树等场景中,通过衍生的lft和rgt标记法,将树形关系转化为数值范围关系,实现了查询效率的质的提升。理解先序遍历的原理,不仅能掌握这一优化方案的核心逻辑,更能深入理解树形数据结构在计算机存储与操作中的设计思想。实际应用中需结合业务的查询与写入频率、数据量大小,权衡其优势与维护成本,避免盲目套用。
六、附
查出所有子孙部门
SET @lft := 9;
SET @rgt := 18;
SELECT * FROM department WHERE lft BETWEEN @lft AND @rgt ORDER BY lft ASC;
/*例子中用BETWEEN将被查部门本身也查了出来。实际中可以用大于小于*/
查询子孙部门总数
总数 = (右值 - 左值 - 1) / 2
新增部门
SET @lft := 7;/*新部门的左值*/
SET @rgt := 8;/*新部门的左值*/
SET @level := 5;/*新部门的层级*/
begin;
/*将插入的后续边缘的节点左右数+2*/
UPDATE department SET lft=lft+2 WHERE lft > @lft;
UPDATE department SET rgt=rgt+2 WHERE rgt >= @lft;
/*插入数据*/
INSERT INTO department(name,lft,rgt,level) VALUES('新部门',@lft,@rgt,level);
/*新增影响行数为0时,必须回滚*/
commit;
/*rollback;*/
删除
SET @lft := 7;/*要删除的节点左值*/
SET @rgt := 8;/*要删除的节点右值*/
begin;
UPDATE department SET lft=lft-2 WHERE lft > @lft;
UPDATE department SET rgt=rgt-2 WHERE rgt > @lft;
/*删除节点*/
DELETE FROM department WHERE lft=@lft AND rgt=@rgt;
/*删除影响行数为0时,必须回滚*/
commit;
直接子节点
SET @level := 2;/*总经理的level*/
SET @lft := 2;/*总经理的左值*/
SET @rgt := 19;/*总经理的右值*/
SELECT * FROM department WHERE lft > @lft AND rgt < @rgt AND level = @level+1;
祖宗链路
SET @lft := 3;/*产品部左值*/
SET @rgt := 8;/*产品部右值*/
SELECT * FROM department WHERE lft < @lft AND rgt > @rgt ORDER BY lft ASC;
计算层级
-- 统计祖链节点数量,再加1即为当前节点层级
SELECT COUNT(*) + 1 AS level
FROM department
WHERE lft < 3 AND rgt > 4;
直接存储 层级