代码之家  ›  专栏  ›  技术社区  ›  Tomalak

将平面表解析为树的最有效/最优雅的方法是什么?

  •  491
  • Tomalak  · 技术社区  · 17 年前

    假设您有一个存储有序树层次结构的平面表:

    Id   Name         ParentId   Order
     1   'Node 1'            0      10
     2   'Node 1.1'          1      10
     3   'Node 2'            0      20
     4   'Node 1.1.1'        2      10
     5   'Node 2.1'          3      10
     6   'Node 1.2'          1      20
    

    这是一张图表,我们在这里 [id] Name . 根节点0是虚构的。

                           [0] ROOT
                              /    \ 
                  [1] Node 1          [3] Node 2
                  /       \                   \
        [2] Node 1.1     [6] Node 1.2      [5] Node 2.1
              /          
     [4] Node 1.1.1
    

    您将使用哪种最简单的方法将其输出到HTML(或文本),作为一个顺序正确、缩进正确的树?

    进一步假设您只有基本的数据结构(数组和哈希映射),没有父/子引用的奇特对象,没有ORM,没有框架,只有您的双手。该表表示为一个结果集,可以随机访问。

    伪代码或简单的英语是可以的,这纯粹是一个概念问题。

    额外的问题:有没有更好的方法在RDBMS中存储这样的树结构?


    编辑和添加

    回答一位评论者的问题( Mark Bessey

    我提到的“结果集”可以被描绘成一组hashmaps(用这个术语来说)。因为我的例子已经在那里了。有些答案会走多远,先构建它,但这没关系。

    这棵树可以任意深。每个节点可以有N个子节点。不过,我心里并没有一棵“百万条目的”树。

    我已经发布了我自己的解决方案,所以你们可以把它撕成碎片。

    14 回复  |  直到 8 年前
        1
  •  481
  •   Bill Karwin    6 年前

    既然 MySQL 8.0 supports recursive queries all popular SQL databases support recursive queries 用标准语法。

    WITH RECURSIVE MyTree AS (
        SELECT * FROM MyTable WHERE ParentId IS NULL
        UNION ALL
        SELECT m.* FROM MyTABLE AS m JOIN MyTree AS t ON m.ParentId = t.Id
    )
    SELECT * FROM MyTree;
    

    我在演示文稿中测试了MySQL 8.0中的递归查询 Recursive Query Throwdown

    以下是我2008年的原始答案:


    在关系数据库中存储树结构数据有几种方法。您在示例中展示的内容使用两种方法:

    • 邻接表 (父项列)和
    • 路径枚举 (姓名栏中的虚线数字)。

    另一种解决方案称为 嵌套集 ,并且它也可以存储在同一个表中。阅读“ Trees and Hierarchies in SQL for Smarties Joe Celko提供了关于这些设计的更多信息。

    我通常喜欢一种叫做 闭合表 (也称为“邻接关系”),用于存储树结构数据。它需要另一个表,但查询树非常容易。

    我在演讲中提到了闭包表 Models for Hierarchical Data with SQL and PHP 在我的书里 SQL Antipatterns: Avoiding the Pitfalls of Database Programming .

    CREATE TABLE ClosureTable (
      ancestor_id   INT NOT NULL REFERENCES FlatTable(id),
      descendant_id INT NOT NULL REFERENCES FlatTable(id),
      PRIMARY KEY (ancestor_id, descendant_id)
    );
    

    将所有路径存储在闭包表中,其中存在从一个节点到另一个节点的直接祖先。为每个节点包含一行以引用自身。例如,使用问题中显示的数据集:

    INSERT INTO ClosureTable (ancestor_id, descendant_id) VALUES
      (1,1), (1,2), (1,4), (1,6),
      (2,2), (2,4),
      (3,3), (3,5),
      (4,4),
      (5,5),
      (6,6);
    

    现在,您可以从节点1开始创建一棵树,如下所示:

    SELECT f.* 
    FROM FlatTable f 
      JOIN ClosureTable a ON (f.id = a.descendant_id)
    WHERE a.ancestor_id = 1;
    

    输出(在MySQL客户端中)如下所示:

    +----+
    | id |
    +----+
    |  1 | 
    |  2 | 
    |  4 | 
    |  6 | 
    +----+
    

    换句话说,节点3和5被排除在外,因为它们是独立层次结构的一部分,而不是从节点1开始下降。


    回复:e-satis对直系子女(或直系父母)的评论。您可以添加一个“ path_length “列到 ClosureTable 使专门查询直系子对象或父对象(或任何其他距离)更容易。

    INSERT INTO ClosureTable (ancestor_id, descendant_id, path_length) VALUES
      (1,1,0), (1,2,1), (1,4,2), (1,6,1),
      (2,2,0), (2,4,1),
      (3,3,0), (3,5,1),
      (4,4,0),
      (5,5,0),
      (6,6,0);
    

    然后,您可以在搜索中添加一个术语,以查询给定节点的直接子节点。他们的后代 路径长度 是1。

    SELECT f.* 
    FROM FlatTable f 
      JOIN ClosureTable a ON (f.id = a.descendant_id)
    WHERE a.ancestor_id = 1
      AND path_length = 1;
    
    +----+
    | id |
    +----+
    |  2 | 
    |  6 | 
    +----+
    

    @ashraf的回复:“对整棵树[按名称]排序怎么样?”

    下面是一个示例查询,用于返回作为节点1的后代的所有节点,并将它们连接到包含其他节点属性(例如 name ,并按名称排序。

    SELECT f.name
    FROM FlatTable f 
    JOIN ClosureTable a ON (f.id = a.descendant_id)
    WHERE a.ancestor_id = 1
    ORDER BY f.name;
    

    @Nate回复:

    SELECT f.name, GROUP_CONCAT(b.ancestor_id order by b.path_length desc) AS breadcrumbs
    FROM FlatTable f 
    JOIN ClosureTable a ON (f.id = a.descendant_id) 
    JOIN ClosureTable b ON (b.descendant_id = a.descendant_id) 
    WHERE a.ancestor_id = 1 
    GROUP BY a.descendant_id 
    ORDER BY f.name
    
    +------------+-------------+
    | name       | breadcrumbs |
    +------------+-------------+
    | Node 1     | 1           |
    | Node 1.1   | 1,2         |
    | Node 1.1.1 | 1,2,4       |
    | Node 1.2   | 1,6         |
    +------------+-------------+
    

    编辑建议上面最后一个查询中的ORDER BY应为 ORDER BY b.path_length, f.name ,大概是为了确保顺序与层次结构匹配。但这不起作用,因为它会将“节点1.1.1”排在“节点1.2”之后。

    如果您希望排序以合理的方式匹配层次结构,这是可能的,但不仅仅是通过按路径长度排序。例如,请参见我的答案 MySQL Closure Table hierarchical database - How to pull information out in the correct order .

        2
  •  61
  •   Jonny Buchanan    14 年前

    如果使用嵌套集(有时称为修改的预排序树遍历),则可以通过单个查询按树顺序提取整个树结构或其中的任何子树,但插入的代价更高,因为您需要管理通过树结构描述顺序路径的列。

    对于 django-mptt ,我使用了这样的结构:

    id  parent_id  tree_id  level  lft  rght
    --  ---------  -------  -----  ---  ----
     1       null        1      0    1    14
     2          1        1      1    2     7
     3          2        1      2    3     4
     4          2        1      2    5     6
     5          1        1      1    8    13
     6          5        1      2    9    10
     7          5        1      2    11   12
    

    它描述了一棵看起来像这样的树(带有 id

     1
     +-- 2
     |   +-- 3
     |   +-- 4
     |
     +-- 5
         +-- 6
         +-- 7
    

    或者,作为一个嵌套的集合图,它使 lft rght

     __________________________________________________________________________
    |  Root 1                                                                  |
    |   ________________________________    ________________________________   |
    |  |  Child 1.1                     |  |  Child 1.2                     |  |
    |  |   ___________    ___________   |  |   ___________    ___________   |  |
    |  |  |  C 1.1.1  |  |  C 1.1.2  |  |  |  |  C 1.2.1  |  |  C 1.2.2  |  |  |
    1  2  3___________4  5___________6  7  8  9___________10 11__________12 13 14
    |  |________________________________|  |________________________________|  |
    |__________________________________________________________________________|
    

    如您所见,要获得给定节点的整个子树,只需选择所有具有 lft rght 其之间的值 lft rght

    这个 level 为了方便起见,列是一种非规范化,而 tree_id 列允许您重新启动 lft rght lft development notes 当时我正试图对每个操作所需的查询进行思考。

    tree_item_iterator 效用函数,对于每个节点,它应该为您提供足够的信息,以生成您想要的任何类型的显示。

    有关MPTT的更多信息:

        3
  •  24
  •   Michał Kołodziejski    12 年前

    这是一个相当古老的问题,但由于它有许多观点,我认为值得提出一个替代方案,而且在我看来是非常优雅的解决方案。

    为了读取树结构,您可以使用 递归公共表表达式 (CTEs)。它提供了一种一次获取整个树结构的可能性,具有关于节点级别、其父节点以及父节点子节点中的顺序的信息。

    1. 创建一个结构

      CREATE TABLE tree (
          id int  NOT NULL,
          name varchar(32)  NOT NULL,
          parent_id int  NULL,
          node_order int  NOT NULL,
          CONSTRAINT tree_pk PRIMARY KEY (id),
          CONSTRAINT tree_tree_fk FOREIGN KEY (parent_id) 
            REFERENCES tree (id) NOT DEFERRABLE
      );
      
      
      insert into tree values
        (0, 'ROOT', NULL, 0),
        (1, 'Node 1', 0, 10),
        (2, 'Node 1.1', 1, 10),
        (3, 'Node 2', 0, 20),
        (4, 'Node 1.1.1', 2, 10),
        (5, 'Node 2.1', 3, 10),
        (6, 'Node 1.2', 1, 20);
      
    2. WITH RECURSIVE 
      tree_search (id, name, level, parent_id, node_order) AS (
        SELECT 
          id, 
          name,
          0,
          parent_id, 
          1 
        FROM tree
        WHERE parent_id is NULL
      
        UNION ALL 
        SELECT 
          t.id, 
          t.name,
          ts.level + 1, 
          ts.id, 
          t.node_order 
        FROM tree t, tree_search ts 
        WHERE t.parent_id = ts.id 
      ) 
      SELECT * FROM tree_search 
      WHERE level > 0 
      ORDER BY level, parent_id, node_order;
      

      结果如下:

       id |    name    | level | parent_id | node_order 
      ----+------------+-------+-----------+------------
        1 | Node 1     |     1 |         0 |         10
        3 | Node 2     |     1 |         0 |         20
        2 | Node 1.1   |     2 |         1 |         10
        6 | Node 1.2   |     2 |         1 |         20
        5 | Node 2.1   |     2 |         3 |         10
        4 | Node 1.1.1 |     3 |         2 |         10
      (6 rows)
      

      树节点按深度级别排序。在最终输出中,我们将在后面的行中显示它们。

      对于每个级别,它们按父级中的父级id和节点顺序排序。这告诉我们如何在输出链接节点中按此顺序将它们呈现给父节点。

      有了这样一个结构,用HTML制作一个非常好的表示就不难了。

      PostgreSQL、IBM DB2、MS SQL Server和Oracle .

      如果您想阅读有关递归SQL查询的更多信息,可以查看您最喜欢的DBMS文档,或者阅读我的两篇文章,其中涉及此主题:

        4
  •  18
  •   Eric Weilnau    14 年前

    SELECT LPAD(' ', (LEVEL - 1) * 4) || "Name" AS "Name"
    FROM (SELECT * FROM TMP_NODE ORDER BY "Order")
    CONNECT BY PRIOR "Id" = "ParentId"
    START WITH "Id" IN (SELECT "Id" FROM TMP_NODE WHERE "ParentId" = 0)
    

    从SQLServer2005开始,您可以使用递归公共表表达式(CTE)。

    WITH [NodeList] (
      [Id]
      , [ParentId]
      , [Level]
      , [Order]
    ) AS (
      SELECT [Node].[Id]
        , [Node].[ParentId]
        , 0 AS [Level]
        , CONVERT([varchar](MAX), [Node].[Order]) AS [Order]
      FROM [Node]
      WHERE [Node].[ParentId] = 0
      UNION ALL
      SELECT [Node].[Id]
        , [Node].[ParentId]
        , [NodeList].[Level] + 1 AS [Level]
        , [NodeList].[Order] + '|'
          + CONVERT([varchar](MAX), [Node].[Order]) AS [Order]
      FROM [Node]
        INNER JOIN [NodeList] ON [NodeList].[Id] = [Node].[ParentId]
    ) SELECT REPLICATE(' ', [NodeList].[Level] * 4) + [Node].[Name] AS [Name]
    FROM [Node]
      INNER JOIN [NodeList] ON [NodeList].[Id] = [Node].[Id]
    ORDER BY [NodeList].[Order]
    

    Name
    'Node 1'
    '    Node 1.1'
    '        Node 1.1.1'
    '    Node 1.2'
    'Node 2'
    '    Node 2.1'
    
        5
  •  10
  •   Leniel Maccaferri    13 年前

    比尔的回答真是太好了,这个回答又增加了一些东西,这让我希望我的回答能得到支持。

    无论如何,我想支持树结构和Order属性。我在每个名为 leftSibling 这也是同样的道理 Order 是指在原始问题中执行(保持从左到右的顺序)。

    mysql> desc nodes ;
    +-------------+--------------+------+-----+---------+----------------+
    | Field       | Type         | Null | Key | Default | Extra          |
    +-------------+--------------+------+-----+---------+----------------+
    | id          | int(11)      | NO   | PRI | NULL    | auto_increment |
    | name        | varchar(255) | YES  |     | NULL    |                |
    | leftSibling | int(11)      | NO   |     | 0       |                |
    +-------------+--------------+------+-----+---------+----------------+
    3 rows in set (0.00 sec)
    
    mysql> desc adjacencies;
    +------------+---------+------+-----+---------+----------------+
    | Field      | Type    | Null | Key | Default | Extra          |
    +------------+---------+------+-----+---------+----------------+
    | relationId | int(11) | NO   | PRI | NULL    | auto_increment |
    | parent     | int(11) | NO   |     | NULL    |                |
    | child      | int(11) | NO   |     | NULL    |                |
    | pathLen    | int(11) | NO   |     | NULL    |                |
    +------------+---------+------+-----+---------+----------------+
    4 rows in set (0.00 sec)
    

    More detail and SQL code on my blog .

    谢谢比尔,你的回答有助于入门!

        6
  •  7
  •   Oli    17 年前

    如果有选择,我会使用对象。我会为每个记录创建一个对象,其中每个对象都有一个 children 收集并将它们全部存储在以Id为键的assoc数组(/hashtable)中。并对集合进行一次闪电扫描,将子项添加到相关的子项字段中。 易于理解的

    但是,由于限制使用一些好的OOP并不是一件有趣的事情,我可能会基于以下内容进行迭代:

    function PrintLine(int pID, int level)
        foreach record where ParentID == pID
            print level*tabs + record-data
            PrintLine(record.ID, level + 1)
    
    PrintLine(0, 0)
    

    肮脏的 如果你有选择的话,那就走面向对象的路线。

        7
  •  6
  •   Konchog    4 年前

    有一些非常好的解决方案利用了sql索引的内部btree表示。这是基于1998年前后所做的一些伟大的研究。

    下面是一个示例表(在mysql中)。

    CREATE TABLE `node` (
      `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
      `name` varchar(255) NOT NULL,
      `tw` int(10) unsigned NOT NULL,
      `pa` int(10) unsigned DEFAULT NULL,
      `sz` int(10) unsigned DEFAULT NULL,
      `nc` int(11) GENERATED ALWAYS AS (tw+sz) STORED,
      PRIMARY KEY (`id`),
      KEY `node_tw_index` (`tw`),
      KEY `node_pa_index` (`pa`),
      KEY `node_nc_index` (`nc`),
      CONSTRAINT `node_pa_fk` FOREIGN KEY (`pa`) REFERENCES `node` (`tw`) ON DELETE CASCADE
    )
    

    树表示法所需的字段只有:

    • pa:对父节点root的引用(使用tw)为null。
    • nc:用作语法糖。它是tw+sz,表示节点的“下一个子节点”的tw。

    以下是示例24节点填充,按tw排序:

    +-----+---------+----+------+------+------+
    | id  | name    | tw | pa   | sz   | nc   |
    +-----+---------+----+------+------+------+
    |   1 | Root    |  1 | NULL |   24 |   25 |
    |   2 | A       |  2 |    1 |   14 |   16 |
    |   3 | AA      |  3 |    2 |    1 |    4 |
    |   4 | AB      |  4 |    2 |    7 |   11 |
    |   5 | ABA     |  5 |    4 |    1 |    6 |
    |   6 | ABB     |  6 |    4 |    3 |    9 |
    |   7 | ABBA    |  7 |    6 |    1 |    8 |
    |   8 | ABBB    |  8 |    6 |    1 |    9 |
    |   9 | ABC     |  9 |    4 |    2 |   11 |
    |  10 | ABCD    | 10 |    9 |    1 |   11 |
    |  11 | AC      | 11 |    2 |    4 |   15 |
    |  12 | ACA     | 12 |   11 |    2 |   14 |
    |  13 | ACAA    | 13 |   12 |    1 |   14 |
    |  14 | ACB     | 14 |   11 |    1 |   15 |
    |  15 | AD      | 15 |    2 |    1 |   16 |
    |  16 | B       | 16 |    1 |    1 |   17 |
    |  17 | C       | 17 |    1 |    6 |   23 |
    | 359 | C0      | 18 |   17 |    5 |   23 |
    | 360 | C1      | 19 |   18 |    4 |   23 |
    | 361 | C2(res) | 20 |   19 |    3 |   23 |
    | 362 | C3      | 21 |   20 |    2 |   23 |
    | 363 | C4      | 22 |   21 |    1 |   23 |
    |  18 | D       | 23 |    1 |    1 |   24 |
    |  19 | E       | 24 |    1 |    1 |   25 |
    +-----+---------+----+------+------+------+
    

    每个树结果都可以非递归地完成。 例如,获取tw='22'处节点的祖先列表

    祖先

    select anc.* from node me,node anc 
    where me.tw=22 and anc.nc >= me.tw and anc.tw <= me.tw 
    order by anc.tw;
    +-----+---------+----+------+------+------+
    | id  | name    | tw | pa   | sz   | nc   |
    +-----+---------+----+------+------+------+
    |   1 | Root    |  1 | NULL |   24 |   25 |
    |  17 | C       | 17 |    1 |    6 |   23 |
    | 359 | C0      | 18 |   17 |    5 |   23 |
    | 360 | C1      | 19 |   18 |    4 |   23 |
    | 361 | C2(res) | 20 |   19 |    3 |   23 |
    | 362 | C3      | 21 |   20 |    2 |   23 |
    | 363 | C4      | 22 |   21 |    1 |   23 |
    +-----+---------+----+------+------+------+
    

    兄弟姐妹和孩子都很琐碎——只需使用tw对pa字段进行排序即可。

    例如,以tw=17为根的节点集(分支)。

    select des.* from node me,node des 
    where me.tw=17 and des.tw < me.nc and des.tw >= me.tw 
    order by des.tw;
    +-----+---------+----+------+------+------+
    | id  | name    | tw | pa   | sz   | nc   |
    +-----+---------+----+------+------+------+
    |  17 | C       | 17 |    1 |    6 |   23 |
    | 359 | C0      | 18 |   17 |    5 |   23 |
    | 360 | C1      | 19 |   18 |    4 |   23 |
    | 361 | C2(res) | 20 |   19 |    3 |   23 |
    | 362 | C3      | 21 |   20 |    2 |   23 |
    | 363 | C4      | 22 |   21 |    1 |   23 |
    +-----+---------+----+------+------+------+
    

    当读取的数量远大于插入或更新的数量时,此方法非常有用。

    插入/删除成本很高,因为插入点之后的所有节点上以及所有祖先节点上都需要分别更新tw索引和sz(分支大小)值。

    分支移动涉及将分支的tw值移出范围,因此在移动分支时还需要禁用外键约束。移动分支基本上需要四个查询:

    • 将分支移出范围。
    • 弥合它留下的空隙。(剩余的树现在已标准化)。
    • 打开将要打开的缺口。
    • 将分支移动到它的新位置。

    调整树查询

    打开/关闭树中的间隙是create/update/delete方法使用的一个重要子函数,因此我将其包括在这里。

    我们首先使用一个(稍加修改的)祖先函数来更新sz值。

    update node me, node anc set anc.sz = anc.sz - me.sz from 
    node me, node anc where me.tw=18 
    and ((anc.nc >= me.tw and anc.tw < me.pa) or (anc.tw=me.pa));
    

    然后,我们需要调整tw,以使其tw高于要移除的分支。

    update node me, node anc set anc.tw = anc.tw - me.sz from 
    node me, node anc where me.tw=18 and anc.tw >= me.tw;
    

    然后,我们需要调整那些pa的tw高于要移除的分支的父级。

    update node me, node anc set anc.pa = anc.pa - me.sz from 
    node me, node anc where me.tw=18 and anc.pa >= me.tw;
    
        8
  •  5
  •   matt b    17 年前

    这篇文章写得很快,既不美观,也不高效(加上它有很多自动信箱,可以在 int Integer 真烦人,但它是有效的。

    这可能违反了规则,因为我正在创建自己的对象,但嘿,我这样做是为了从实际工作中转移注意力:)

    这还假设在开始构建节点之前,resultSet/table已完全读入某种结构中,如果有数十万行,这不是最佳解决方案。

    public class Node {
    
        private Node parent = null;
    
        private List<Node> children;
    
        private String name;
    
        private int id = -1;
    
        public Node(Node parent, int id, String name) {
            this.parent = parent;
            this.children = new ArrayList<Node>();
            this.name = name;
            this.id = id;
        }
    
        public int getId() {
            return this.id;
        }
    
        public String getName() {
            return this.name;
        }
    
        public void addChild(Node child) {
            children.add(child);
        }
    
        public List<Node> getChildren() {
            return children;
        }
    
        public boolean isRoot() {
            return (this.parent == null);
        }
    
        @Override
        public String toString() {
            return "id=" + id + ", name=" + name + ", parent=" + parent;
        }
    }
    
    public class NodeBuilder {
    
        public static Node build(List<Map<String, String>> input) {
    
            // maps id of a node to it's Node object
            Map<Integer, Node> nodeMap = new HashMap<Integer, Node>();
    
            // maps id of a node to the id of it's parent
            Map<Integer, Integer> childParentMap = new HashMap<Integer, Integer>();
    
            // create special 'root' Node with id=0
            Node root = new Node(null, 0, "root");
            nodeMap.put(root.getId(), root);
    
            // iterate thru the input
            for (Map<String, String> map : input) {
    
                // expect each Map to have keys for "id", "name", "parent" ... a
                // real implementation would read from a SQL object or resultset
                int id = Integer.parseInt(map.get("id"));
                String name = map.get("name");
                int parent = Integer.parseInt(map.get("parent"));
    
                Node node = new Node(null, id, name);
                nodeMap.put(id, node);
    
                childParentMap.put(id, parent);
            }
    
            // now that each Node is created, setup the child-parent relationships
            for (Map.Entry<Integer, Integer> entry : childParentMap.entrySet()) {
                int nodeId = entry.getKey();
                int parentId = entry.getValue();
    
                Node child = nodeMap.get(nodeId);
                Node parent = nodeMap.get(parentId);
                parent.addChild(child);
            }
    
            return root;
        }
    }
    
    public class NodePrinter {
    
        static void printRootNode(Node root) {
            printNodes(root, 0);
        }
    
        static void printNodes(Node node, int indentLevel) {
    
            printNode(node, indentLevel);
            // recurse
            for (Node child : node.getChildren()) {
                printNodes(child, indentLevel + 1);
            }
        }
    
        static void printNode(Node node, int indentLevel) {
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < indentLevel; i++) {
                sb.append("\t");
            }
            sb.append(node);
    
            System.out.println(sb.toString());
        }
    
        public static void main(String[] args) {
    
            // setup dummy data
            List<Map<String, String>> resultSet = new ArrayList<Map<String, String>>();
            resultSet.add(newMap("1", "Node 1", "0"));
            resultSet.add(newMap("2", "Node 1.1", "1"));
            resultSet.add(newMap("3", "Node 2", "0"));
            resultSet.add(newMap("4", "Node 1.1.1", "2"));
            resultSet.add(newMap("5", "Node 2.1", "3"));
            resultSet.add(newMap("6", "Node 1.2", "1"));
    
            Node root = NodeBuilder.build(resultSet);
            printRootNode(root);
    
        }
    
        //convenience method for creating our dummy data
        private static Map<String, String> newMap(String id, String name, String parentId) {
            Map<String, String> row = new HashMap<String, String>();
            row.put("id", id);
            row.put("name", name);
            row.put("parent", parentId);
            return row;
        }
    }
    
        9
  •  4
  •   wcm    17 年前

    假设您知道根元素为零,下面是要输出到文本的伪代码:

    function PrintLevel (int curr, int level)
        //print the indents
        for (i=1; i<=level; i++)
            print a tab
        print curr \n;
        for each child in the table with a parent of curr
            PrintLevel (child, level+1)
    
    
    for each elementID where the parentid is zero
        PrintLevel(elementID, 0)
    
        10
  •  3
  •   Mark Bessey    17 年前

    您可以使用hashmap模拟任何其他数据结构,因此这不是一个可怕的限制。从上到下扫描,为数据库的每一行创建一个hashmap,并为每一列创建一个条目。将这些hashmap中的每一个添加到“主”hashmap,并键入id。如果任何节点具有您尚未看到的“父节点”,请在主hashmap中为其创建一个占位符条目,并在看到实际节点时进行填充。

    至于是否有“更好”的方法在数据库中存储树,这取决于您将如何使用数据。我见过一些已知最大深度的系统,它们在层次结构中的每一层都使用不同的表。如果树中的级别毕竟不完全相同(顶级类别与树叶不同),那么这就很有意义了。

        11
  •  1
  •   tchen    17 年前

    编辑:我会先将整个表读入一个数组,这样它就不会重复查询数据库。当然,如果您的桌子很大,这将不实用。

    在构建结构之后,我必须对其进行深度优先遍历,并打印出HTML。

        12
  •  1
  •   Nick Johnson    17 年前

    delimiter = '.'
    stack = []
    for item in items:
      while stack and not item.startswith(stack[-1]+delimiter):
        print "</div>"
        stack.pop()
      print "<div>"
      print item
      stack.append(item)
    

    这样做的目的是维护一个表示树中当前位置的堆栈。对于表中的每个元素,它会弹出堆栈元素(关闭匹配的div),直到找到当前项的父项。然后,它输出该节点的开始并将其推送到堆栈。

    如果要使用缩进而不是嵌套元素输出树,只需跳过print语句即可打印div,并在每个项之前打印相当于堆栈大小的若干倍的空格。例如,在Python中:

    print "  " * len(stack)
    

    您还可以轻松地使用此方法构造一组嵌套列表或字典。

    idx = {}
    idx[0] = []
    for node in results:
      child_list = []
      idx[node.Id] = child_list
      idx[node.ParentId].append((node, child_list))
    

        13
  •  1
  •   bluish dmajkic    13 年前

    为了扩展Bill的SQL解决方案,您基本上可以使用平面阵列进行相同的扩展。此外,如果您的字符串都具有相同的长度,并且您的最大子级数已知(例如在二叉树中),则可以使用单个字符串(字符数组)来实现。如果你有任意数量的孩子,这会使事情变得有点复杂。。。我必须检查我的旧笔记,看看能做些什么。

    然后,牺牲一点内存,特别是如果你的树是稀疏的和/或未被缓冲的,你可以通过一点索引数学,通过存储你的树,在数组中以宽度第一的方式随机访问所有字符串,就像这样(对于二叉树):

    String[] nodeArray = [L0root, L1child1, L1child2, L2Child1, L2Child2, L2Child3, L2Child4] ...
    

    你知道你的绳子长度,你知道的

    我现在正在工作,所以不能在上面花太多时间,但有兴趣的话,我可以获取一些代码来完成这项工作。

    我们用它来搜索由DNA密码子组成的二叉树,这是一个建立树的过程,然后我们将树展平以搜索文本模式,当找到时,通过索引数学(从上面反转)我们得到节点。。。我们的树很少有空节点,但我们可以在一瞬间烧掉千兆字节的数据。

        14
  •  0
  •   Andrew Barber Eric Lafortune    13 年前

    考虑将nosql工具(如neo4j)用于层次结构。 e、 g像linkedin这样的网络应用程序使用couchbase(另一种nosql解决方案)

    但nosql仅用于数据集市级查询,而不用于存储/维护事务