【问题标题】:Generating Hierarchy using Maps and path variable使用地图和路径变量生成层次结构
【发布时间】:2016-05-10 14:01:10
【问题描述】:

我有一个家庭类,可以从 postgres 数据库中提取数据。这个类看起来像这样:

@Entity
public class Family(){
@Id
private long id;
private String firstName;
private String lastName;
private Long parentId;
private4 String familyPath;
private List<Family> children;

//getters and setters

在数据库中,我将它们彼此的关系存储为一个以句点分隔的字符串。例如,如果 Bob 是 Sue 的孩子,那么树列将如下所示:“bob.sue”。此路径作为家庭对象的一部分存储在 familyPath 变量中。

澄清 familyPath 是基于数据库中每一行的唯一 ID 的路径。所以路径可能看起来像“1.2.3”,其中最后一个数字是当前行。 “1.2.4”是另一条潜在路径。所以 ID 为 3 和 4 的行是 2 的子代,依此类推。

在我的代码中,我在数据库中查询数据中的所有家庭成员,因此我在数据库中有一组家庭的每个成员。我的目标是使用这个初始的平面集生成一组所有家庭成员作为层次结构。所以,最后如果我在 Bob 上调用 getChildren,我会得到一个包含 Sue 和任何其他孩子的列表。

我的解决方案:

首先,我遍历我的家庭列表,并找到我称之为根成员的东西——那些位于家庭路径顶层的成员——并将它们删除到一个单独的列表中。所以现在我有一份*家庭成员的名单,还有一份其他人的名单。

然后,对于*列表中的每个成员,我调用以下递归方法:

private Family familyTree(Family root, List<Family> members) {
    List<Family> children = new ArrayList<>();


    for (Family f : members) {
        if (isChildOf(f, root)) {
            children.add(familyTree(f, resources));
        }
    }
    root.setChildren(children);
    return root;
}


private boolean isChildOf(Family a, Family b) {
    String pCPath = a.getFamilyPath();
    String pPPath = b.getFamilyPath();

    return pCPath.indexOf('.') >= 0
            && pCPath.substring(0, pCPath.lastIndexOf('.')).equals(pPPath);
}

并将输出保存到列表中。这会产生所需的结果。

我的问题 但是,我觉得这种递归方法非常昂贵(n^2)。我在想可能有一种更有效的方法来使用集合、映射和 Family 对象的 familyPath 变量来生成这个层次结构,但是我一直陷入多个迭代循环中。有人有想法吗?

【问题讨论】:

  • 请添加isChildOf的代码
  • 添加到递归函数的代码块
  • 我还在数据库中添加了一个 parentId 列,并为模型添加了一个相应的变量。所以现在每个家庭对象都有其父对象的 id

标签: java database recursion hashmap hierarchy


【解决方案1】:

选项 1 - 单次通过

private Family familyTree(Family root, List<Family> members) {
    Map<Long, List<Family>> parentMap = new HashMap<>();

    // Assuming root is not contained in members
    root.children  = new ArrayList<>();
    parentMap.put(root.id, root.children);

    // Assign each member to a child list
    for (Family member : members) {

        // Put the family member in the right child list
        Long parentId = member.getParentId();
        List<Family> parentChildren = parentMap.get(parentId);
        if (parentChildren == null) {
            parentChildren = new ArrayList<>();
            parentMap.put(parentId, parentChildren);
        }
        parentChildren.add(member);

        // Get or create the child list of the family member
        List<Family> ownChildren = parentMap.get(member.id);
        if (ownChildren == null) {
            ownChildren = new ArrayList<>();
            parentMap.put(member.id, ownChildren);
        }
        member.children = ownChildren;
    }
    return root;
}

private Long getParentId() {
    // left as an exercise...
}

选项 1.b - 单次遍历所有成员,包括根

private List<Family> familyTree(List<Family> members) {
    List<Family> roots = new ArrayList<>();
    Map<Long, List<Family>> parentMap = new HashMap<>();

    // Assign each member to a child list
    for (Family member : members) {

        // Put the family member in the right child list
        Long parentId = member.getParentId();
        if (parentId == null) {
            // a root member
            roots.add(member);
        } else {
            // a non-root member
            List<Family> parentChildren = parentMap.get(parentId);
            if (parentChildren == null) {
                parentChildren = new ArrayList<>();
                parentMap.put(parentId, parentChildren);
            }
            parentChildren.add(member);
        }

        // Get or create the child list of the family member
        List<Family> ownChildren = parentMap.get(member.id);
        if (ownChildren == null) {
            ownChildren = new ArrayList<>();
            parentMap.put(member.id, ownChildren);
        }
        member.children = ownChildren;
    }
    return roots;
}

选项 2 - 添加对父级的引用

你的Family 类应该有一个private Family parent 属性。然后,您将能够对每个家庭“级别”进行一次查询。那就是:

  1. 获取 Sue 的所有孩子
  2. 从 (1) 中获取 people 的所有子级并将它们分配给正确的父级

选项 3 - 层次结构的嵌套集模型

可以修改数据库架构以在单个查询中检索整个子树。诀窍是给每个树节点一个“左”和一个“右”值。这些值为节点子节点的“左”和“右”值建立了一个范围。

然后可以像这样选择一棵完整的树:

SELECT child.id, ...
FROM family AS child, family AS parent
WHERE child.lft BETWEEN parent.lft AND parent.rgt
    AND parent.id = 111
ORDER BY child.lft;

还有很多其他的分层操作可以很容易地用这样的模式来完成。有关详细信息,请参阅 this postJoe Celko 为 Smarties 编写的 SQL 中的树和层次结构

最后,您的模型只考虑每个家庭成员的单亲,这看起来很奇怪。

【讨论】:

  • 我在上面添加了说明:familyPath 是按 ID 的,它对于数据库中的每个条目都是唯一的。所以familyPath可能看起来像“1.2.3.4”
  • 谢谢!您的解决方案正是我正在构建的目标
  • 我们仍在研究 n^2 时间复杂度,对吧?因为我们正在循环遍历每个*家族成员,并且我们正在遍历家族树方法中的每个非*家族成员。所以如果我们有一个非常大的列表,O(n^2)
  • 你的第一个实现比 n^2 差很多。每个孩子都遍历整个列表及其孩子等等。我尊重您在问题中提供的方法签名。请参阅我的更新 (1.5) 以了解单次传递方法。