news 2026/9/27 10:58:47

经典树形结构:闭包表

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
经典树形结构:闭包表

一、树形结构存储,难在哪?

树形结构由节点和边组成,每个节点可以有零个或多个子节点,但只有一个父节点(根节点除外)。这种结构在现实中随处可见:公司的组织架构、电商的商品类目、论坛的帖子回复……但在关系型数据库中存储和查询它们,却并不直观。

常见的诉求无非就几类:查某个节点的所有子节点、查所有祖先节点、查两节点之间的距离、增删改节点。看似简单,但不同的存储方案在这几项操作上的表现天差地别。

二、四种常见方案速览

在正式介绍闭包表之前,我们先快速了解另外三种主流方案,这样才能理解闭包表到底“优”在哪里。

1. 邻接表

最直观的方案——每个节点记录一个parent_id指向父节点。

  • 优点:结构简单,插入方便。

  • 缺点:查询子树或祖先需要递归查询,层级深时性能极差。

2. 路径枚举

在每个节点中存储从根节点到该节点的完整路径,如/1/2/3。

  • 优点:避免了递归查询,查询子树效率高。

  • 缺点:移动节点时需要更新该节点及所有子孙的路径,维护成本极高;路径长度有上限。

3. 嵌套集

为每个节点赋予左右值,通过数值范围来判断祖先后代关系。

  • 优点:查询子树极快。

  • 缺点:插入、删除、移动节点时需要更新大量节点的左右值,模型复杂,维护困难。

4. 闭包表——今天的主角

单独创建一张关系表,存储树中所有节点对之间的祖先-后代关系(包括节点自身)。

三、闭包表的核心原理

闭包表的核心思想是空间换时间——用额外的存储空间,换取查询效率的大幅提升。

它通常需要两张表:

节点表(存储节点本身的信息):

CREATE TABLE nodes ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255) NOT NULL );

闭包关系表(存储所有祖先-后代关系):

CREATE TABLE node_paths ( ancestor_id INT, -- 祖先节点ID descendant_id INT, -- 后代节点ID depth INT, -- 两者之间的距离(层数差) PRIMARY KEY (ancestor_id, descendant_id), FOREIGN KEY (ancestor_id) REFERENCES nodes(id), FOREIGN KEY (descendant_id) REFERENCES nodes(id) );

关键点:每个节点不仅要记录与所有祖先的关系,还要记录与自身的关系(即ancestor_id = descendant_id,depth = 0)。

举个例子

假设有这样一棵树:

1 ├── 2 │ └── 4 └── 3

闭包表中存储的数据是这样的:

ancestor_iddescendant_iddepth
110
121
131
142
220
241
330
440

有了这张表,查询就变得异常简单:

  • 查询节点1的所有后代:SELECT * FROM node_paths WHERE ancestor_id = 1

  • 查询节点4的所有祖先:SELECT * FROM node_paths WHERE descendant_id = 4

  • 查询节点2的直接子节点:SELECT * FROM node_paths WHERE ancestor_id = 2 AND depth = 1

四、闭包表的优缺点

优点

  1. 查询效率极高:无论树有多深,查询任意节点的所有祖先或所有后代都只需要一次简单的索引查询,无需递归。

  2. 支持复杂查询:可以轻松查询两节点之间的距离、某个节点的所有子孙等。

  3. 节点移动方便:移动一个子树时,只需要删除该子树相关的旧路径,再插入新路径即可,操作相对可控。

缺点

  1. 存储空间较大:闭包表存储了所有节点对的关系,数据量约为 O(n²) 级别。树越大,关系表膨胀越明显。

  2. 插入成本较高:插入一个新节点时,需要为它和所有祖先节点各插入一条关系记录。

五、什么时候该用闭包表?

综合来看,闭包表最适合以下场景:

  • 树形结构层级较深(比如超过5层),邻接表的递归查询难以承受。

  • 查询操作远多于写入操作,愿意用存储空间换取查询性能。

  • 需要频繁查询祖先/后代关系,比如权限系统中的部门归属查询、电商系统中的类目路径查询。

如果树结构非常浅、数据量很小,或者写入极其频繁,邻接表可能是更轻量的选择。没有银弹,只有最适合的方案。

六、总结

闭包表通过“空间换时间”的思路,用一张专门的关系表存储所有节点对的祖先-后代关系,将复杂的树形查询转化为简单的索引查询。虽然插入和存储成本有所增加,但在查询性能和维护便利性上优势明显。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/27 10:57:42

前言:写给每一位想学会“让芯片干活“的读者

版本:1.0(面向 ESP-IDF v6.0.1 / ESP32-S3)为什么写这本书 这本书想做的只有一件事:让一个完全零基础的人,只看这一本书, 就建立起使用 ESP-IDF(Espressif IoT Development Framework&#xff0…

作者头像 李华
网站建设 2026/9/27 10:51:57

射频频率计选型与精度验证实战指南

1. 射频频率计不是万能表:先搞清它到底能测什么、不能测什么射频频率计,这个词在电子工程师的日常对话里经常出现,但很多人一上手就栽跟头——买回来接上信号源,屏幕乱跳,读数飘忽不定,甚至直接报错。我第一…

作者头像 李华
网站建设 2026/9/27 10:50:49

MIT 6.S081 traps 实验篇(lab4):RISC-V assembly (easy)

RISC-V assembly (easy) 实验目标 lab4 的第一关是纯阅读 问答,不写代码,目的是通过一份真实的反汇编(call.asm)把 RISC-V 的基础机制摸透: 弄清楚参数寄存器、返回地址、栈帧在汇编层面长什么样。理解函数如何被调…

作者头像 李华
网站建设 2026/9/27 10:49:04

STM32 SBUS协议解析:DMA+IDLE中断+状态机三重实时保障

1. 项目概述:为什么SBUS解析不能只靠普通串口中断?SBUS是Futaba开发的航模遥控协议,现在几乎成了多旋翼飞控、云台控制器、机器人舵机控制的事实标准。它用单线反相串口(TTL电平)传输16路通道1路数字开关信号&#xff…

作者头像 李华
网站建设 2026/9/27 10:47:12

Cortex-M FPU中断嵌套HardFault排查:惰性保存与优先级配置

1. 从一次HardFault定位说起:FPU上下文与中断嵌套的隐秘冲突那个HardFault出现在凌晨两点。设备跑的是Cortex-M4内核,带FPU,FreeRTOS上跑着几个任务,串口每隔几秒打印一次姿态数据。现象很诡异:平时跑几个小时都没事&a…

作者头像 李华