二维表格转树形结构算法

原始需求是前端同学在 VUE 开发中使用 ElementUI 的树形组件时,组件需要一次性将整个树形组件的数据全部加载,这就要求后端将数据一次性返回并且必须是树形数据结构。我是理解不了 ElementUI 组件的逻辑,我自己的树形组件可以按需加载,即点击树中某一个节点时才加载这个节点下的所有数据。反抗是没有用的,老实实现需求就是了。

查询数据表的 SQL 语句如下:

这个表通过字段parent_project_id实现了树形目录中的上下级关系,从而可以实现无限级的树形数据结构。顶层目录的parent_project_id值为0

要生成的树形目录数据结构应该是这样的(实际上远比这个要复杂得多):

使用children属性来保存子节点的所有信息,没有子节点时那么children则为空数组。

开始想如何实现,第一种方法是广度优先搜索,一层一层遍历节点和子节点,直到最后一层的所有节点都没有子节点;第二种方法是深度优先搜索,查找每个节点的第一个子节点是否有子节点是否有数据,如果有则继续向下查找,如果没有,则跳到下一个同胞节点向下查找,直到所有节点都没有子节点为止。记得上人工智能课时老师还留了这道题当作业,好吧,这两个方法都不可取,因为每一次查找都要访问一次数据库,即使不访问数据库,在内存中遍历也是一件不小的工程。我当时的作业每一种算法都有上百行代码,这显然作为一个有强迫症的程序员是不能容忍的。

开始思考第三种算法,一次性从数据库获取数据,且要求在内存中的查找数据的次数尽可能少,即时间复杂度尽可能低。最后尝试使用 引用类型的特性 实现,下面是的代码(Scala):

以上代码的解释如下:

  1. 函数参数primaryColumn表示主键的名字,本例中为project_idparentColumn表示父级列的列名,本例中为parent_project_idstartPoint为查询起始点,本例中为0,用这个值来确认是否为顶层项;newColumn为生成新列的名称,用于保存所有子节点数据,本例中为children
  2. 这是类 DataTable 的其中的一个方法,DataTable 可以理解从数据库获取的一个二维表格,代码中的this即表示当前表格,this.rows表示当前表格的所有行。
  3. 使用relations保存每一项的实体系,是一个 HashMap 结构。Key 是节点主键project_id的值,Value 则是实体,包含节点的所有属性。
  4. 使用result保存最终要返回的结果,是一个对象数组。
  5. 需要两次遍历,第一次遍历,可能无法确定上下级关系,需要第一次遍历。第一次遍历时无法确认上下级关系的实体保存在stash中。
  6. 第一次遍历,初步确认下一级关系,判断是顶层还是子项,map变量即每一个实体。如果是顶层那么就添加到result中,如果是子项则在relations中找到父级实体并添加为这个实体的子项。由于引用类型的特性,resultrelations中引用的实体是同一个,如果更新了relations中的实体,那么result中的保存的值也跟着更新了。
  7. 第二次遍历是因为数据顺序的问题,有可能主键值小的实体是主键值大的实体的子项。最好的办法是跟 SQL 语句配合,先在数据库中进行一次排序,然后再在程序中加工。例如 SQL 语句可以修改为 SELECT id AS project_id, parent_project_id, project_name FROM qross_projects ORDER BY parent_project_id ASC, id ASC;
  8. 最后返回值result则包含了所有顶层的实体,顶层实体中包含了自己的所有子项。由于引用类型的特性,虽然三个集合中项都指向相同的实体,但清空relationsstash并不影响reulst的结果。

本例是 PQLSharp 表达式 TO TREE 的底层实现。


参考链接


微信公众号
码农老吴  |  星源工作室  |  开发月志  |  问题反馈
联系我们:wu@qross.io     手机/微信:18618171102
京 ICP 备 20027445 号
$(h1)!