如何将嵌套集有效地转换为对象层次结构

时间:2012-06-01 14:47:10

标签: algorithm nested-sets

假设我有一个表示嵌套集层次结构的节点列表(示例在伪c#中)。

class Node
{
    public decimal left;
    public decimal right;
    public decimal id;
    public void AddChild(Node child) {...}
    ...
}

List<Node> nodes = GetFlatNodesWithoutChildrenSetFromDatabase();

字段leftrightid已填充,因为这些值存储在某个数据库中。

将此平面列表转换为层次结构的有效方法是什么,这意味着为每个父节点填充适当的子节点?

一种方法是找到每个节点的所有祖先,对它们进行排序以找到父节点并将子节点添加到该节点。

foreach (var n in nodes)
{
    var parent = nodes.Where(i => i.left < n.left && i.right > n.right).OrderBy(i => i.right - n.right).FirstOrDefault();
    if (parent != null)
        parent.AddChild(n);
}

但这效率很低。

是否有更好(意味着更快)的方法?

修改

可能的解决方案(as suggested by Chris):

var stack = new Stack<Node>(nodes.Take(1));
foreach (var n in nodes.Skip(1))
{
    while (stack.Peek().right < n.left)
        stack.Pop();

    stack.Peek().addChild(n);
    stack.Push(n);
}

节点必须按left排序。

3 个答案:

答案 0 :(得分:3)

我可能考虑探索的方法是左边排序然后你可以迭代一次。

当你向左移动并将其粘贴在堆栈上时,你“打开”一个节点。

当您到达要处理的新节点时,通过确定其右侧是否小于新节点,检查堆栈顶部的节点是否应该关闭。如果它是你从堆栈中删除它(关闭它),你已经处理了它的所有孩子。然后,您可以检查堆栈的新顶部,直到找到仍处于打开状态的堆栈。然后,将当前节点作为子节点添加到堆栈顶部的节点,然后打开该节点(因此它将位于堆栈顶部)。

您链接的维基百科页面上的图表(http://en.wikipedia.org/wiki/Nested_set_model)是我的灵感来源。

Nested Set Diagram

我的算法基本上在中间向下移动,每当你输入其中一个集合时,我称之为打开并且正在关闭集合。很明显,你打开但未关闭的最新套装将位于堆叠的顶部,因此放置孩子的位置。

我认为由于排序,这应该是O(nlogn)的复杂性。其余部分只是O(n)。

答案 1 :(得分:0)

我知道这个问题很老(我没有找到关于这个主题的任何其他问题/信息)而且我不知道“伪C#”,但是为了防止你们中的一些人使用递归算法嵌套集列表=&gt;树算法,这是我来到的(在scala中):

def retrieveUserFolderTree(user: User): Future[List[Folder]] = {
  // Get a list of user's folders orderred by left asc
  val dbFoldersPromise = folderDAO.findUserFolders(user)

  dbFoldersPromise.map {
    case rootFolder :: tail => generateChildren(0, rootFolder.right, tail)
    case Nil => Nil
  }
}

private def generateChildren(currentLeft: Int, currentRight: Int, dbFolders: Seq[DBFolder]): List[Folder] = {
  dbFolders match {
    case dbFolder :: tail if dbFolder.left > currentRight => Nil
    case dbFolder :: tail if dbFolder.left > currentLeft => Folder(dbFolder.id, dbFolder.name, generateChildren(dbFolder.left, dbFolder.right, tail)) :: generateChildren(dbFolder.right, currentRight, tail)
    case dbFolder :: tail => generateChildren(currentLeft, currentRight, tail)
    case Nil => Nil
  }
}

希望这会对某人有所帮助。

答案 2 :(得分:0)

也许我在某个地方错过了一个步骤但是当我使用上面的逻辑工作时,我最终得到了堆栈上的一些复制元素。它们按预期在树中,但另外它们也位于根节点上方的堆栈顶部。我不得不在末尾添加一个小循环来清理堆栈。

        var stack = new Stack<DvrNode>(nodes.Take(1));
        foreach (var n in nodes.Skip(1))
        {
            while (stack.Peek().Right < n.Left)
                stack.Pop();

            ((List<DvrNode>)stack.Peek().Children).Add(n);
            stack.Push(n);
        }

        while (stack.Peek().Left != 1)
            stack.Pop();