有效删除树数据结构

时间:2012-02-13 18:07:07

标签: algorithm data-structures tree

我有一个树数据结构:

  • 树的根没有父母,没有以前的兄弟姐妹(它可以 有下一个兄弟姐妹)。
  • 每个节点都有一个唯一的父节点。
  • 每个节点都有一个指向它的下一个和前一个兄弟姐妹,孩子和父母的指针。

此树数据结构正在填充数百万个节点。当我删除一棵树时 大量节点,抛出堆栈溢出异常。当节点数量相对较小或我在发布模式下构建时,数据结构很有效。

这是节点的析构函数:

    Entity::~Entity(void)
    {
        Entity* child = NULL;

        if (firstChild != NULL)
            child = firstChild->getNextSibling();

        while(child != NULL)
        {
            delete child->getPreviousSibling();
            child->setPreviousSibling(NULL);

            child = child->getNextSibling();
        }

        if (lastChild != NULL)
            delete lastChild;

        if (isRoot())
        {
            if (nextSibling != NULL)
            {
                nextSibling->setPreviousSibling(NULL);
                delete nextSibling;
            }
        }
    }

可以实现非递归算法来遍历树并删除它的所有节点。

你能建议一个有效的后序遍历算法来删除非二叉树吗?

6 个答案:

答案 0 :(得分:3)

编辑:错过了关于父节点的后向指针。这意味着我们不需要维护路径历史记录来回溯,我们可以取消堆栈。

node = root;
while (node)
{
    if (node->firstChild)
    {
        next = node->firstChild;
        node->firstChild = null;
    }
    else 
    {
        next = node->nextSibling ? node->nextSibling : node->parent;
        delete node;
    }
    node = next;
}

原始答案:

你可以递归地执行任何操作,可以使用循环和堆栈。虽然这不需要任何更少的内存,但优点是您可以将该内存放在堆上并避免溢出。

s.push(root);
while (!s.empty())
{
     node = s.pop();
     if (node->nextSibling) s.push(node->nextSibling);
     if (node->firstChild) s.push(node->firstChild);
     delete node;
}

答案 1 :(得分:1)

我会尝试更简单的事情,并让递归析构函数尽职尽责:

Entity::~Entity(void)
{
    Entity* child = firstChild;
    while (child) {
      Entity *succ = child->getNextSibling();
      delete child;
      child = succ;
    }
}

答案 2 :(得分:1)

以下是非递归解决方案的草图:

  • 使用根指针初始化指向节点的指针;
  • 重复
    • 如果当前节点有儿子,请转移到那个儿子;
    • 否则,删除当前节点并返回其父节点(如果有);
  • 直到当前节点没有父节点。

答案 3 :(得分:1)

或者,您可以简单地增加堆栈大小。

在Windows和Visual C ++上,默认值为1 MB - 只需将其增加到10 MB甚至100 MB - 这个内存实际上不会提交,直到(除非)您确实需要它,你只需预先预留(参见/STACK选项)。您甚至可以选择性地为Debug配置执行此操作,以考虑那里的“更胖”堆栈。

答案 4 :(得分:0)

您可以尝试创建静态函数来执行删除操作。我在以前的应用程序中发现,要求大型对象树执行任务可能比使用独立函数在树上执行操作要慢得多。静态函数本质上是全局的,因此递归调用确切地知道去哪里。使用递归“方法”需要通过每个对象查找函数,这可能会增加额外的开销,特别是对于像这样的缓存不友好的操作。如果你违反接口以避免对每个对象进行getNextSibling()调用并使程序完全保持在一个全局递归函数中,那么你可能会获得更好的性能。

这不是递归本身,而是当你运行这棵树时,你的指令管道在数据访问上停滞不前。

答案 5 :(得分:0)

考虑一下你是否真的需要打扰树和处理每个节点。

我经常发现为特定任务设置专用内存池很方便,然后在完成后一次性释放整个池。