Preface
偶然在QQ上的一个交流群中看到了一位群友的棘手需求。互联网开发中,数据的落盘存储通常在MySQL中。MySQL是一种关系型数据库,以“行”为基本的存储单元,然后通过外键等建立数据实体模型之间的联系。
但有些数据的存储,在MySQL上并没有那么友好。比如一个存在等级与隶属关系的部门表。每个部门会有一个所直接隶属的上级部门,一个部门又可能会有多个直属的下级部门。在MySQL中存储时,每一个部门都要有一个parent列,来指明自己的直属上级部门。这样一来,就会得到一张实际为树形关系的部门结构表。
实体数据之间的关系,是一张树形结构图
话题回到群友的需求上来,群友的需求也很简单,通过数据库查询与业务层的处理,将所有部门的完整隶属关系整合成一个列表,如下所示:
0: 北京分公司 -> 人事部 -> 档案组
1: 北京分公司 -> 人事部 -> 绩效组
2: 北京分公司 -> 科技部 -> 前台组
...
处理思路
看到了这样的需求后,第一个闪念在脑海中的想法是——拓扑排序。我们知道树结构是图结构的一种特例,树结构是一种无环图,因此只要数据库记录层面不出问题,将查询得到的数据按照树结构组织起来,那么该结构的拓扑序列是一定存在的。
在实际拓扑排序中,只要将每条数据的parent域作为一条指向其他结点的边来看待即可。从入度为零的结点出发,按照parent指针进行深度优先遍历,将路径上的结点收集起来,即可完成上述需求。
如果想要了解拓扑排序的过程或者算法原理,可以查看数据结构书籍中关于图章节的讲述,也可查看下面链接中的解释。
拓扑排序百度百科:https://baike.baidu.com/item/%E6%8B%93%E6%89%91%E6%8E%92%E5%BA%8F
拓扑排序维基百科:https://zh.wikipedia.org/wiki/%E6%8B%93%E6%92%B2%E6%8E%92%E5%BA%8F
设计与实现
算法性能预估
在拓扑排序之前,我们需要考虑一下算法的性能问题。首先拓扑排序的时间复杂度在 O ( N 2 ) O(N^2) O(N2),因此理论上该算法的性能并不好。但是时间复杂度只是一个理论指标,实际我们还需要考虑问题的规模。通常部门的架构表的数据量不会很大,对于某些场景下,该表的数据量也就在几千到几万条之间。
同时,部门架构表经常面对的是读多写少的场景,因此一定程度上,我们可以将一次业务处理的数据进行缓存。而且在业务处理中,面对如此小量的数据,通常我们可以从磁盘中一次性将其读入到内存,然后供业务层处理,这样可以减少磁盘IO的次数。
算法实现
准备承载数据库查询结果的POJO类
@Data
@NoArgsConstructor
@AllArgsConstructor
static class Node {
private Integer id; // 部门ID
private String name; // 部门名称
private Integer parent; // 上级部门的ID
private Integer inDegree = 0; // 该部门的入度(该部门子部门的数量)
}
模拟数据库查询得到整张部门表数据
final List<Node> dbQuerySet = Arrays.asList(
new Node(1, "北京分公司", null, 0),
new Node(2, "人事部", 1, 0),
new Node(3, "科技部", 1, 0),
new Node