Algorithm 树数据结构的高效删除

Algorithm 树数据结构的高效删除,algorithm,data-structures,tree,Algorithm,Data Structures,Tree,我有一个树状数据结构: 树的根没有父节点,也没有以前的兄弟节点(它可以 有下一个兄弟姐妹) 每个节点都有一个唯一的父节点 每个节点都有一个指向其下一个和上一个兄弟节点、子节点和父节点的指针 此树数据结构正在填充数百万个节点。当我删除一棵树时 大量节点时,会引发堆栈溢出异常。当节点数量相对较少或在发布模式下构建时,数据结构运行良好 这是节点的析构函数: Entity::~Entity(void) { Entity* child = NULL;

我有一个树状数据结构:

  • 树的根没有父节点,也没有以前的兄弟节点(它可以 有下一个兄弟姐妹)
  • 每个节点都有一个唯一的父节点
  • 每个节点都有一个指向其下一个和上一个兄弟节点、子节点和父节点的指针
此树数据结构正在填充数百万个节点。当我删除一棵树时 大量节点时,会引发堆栈溢出异常。当节点数量相对较少或在发布模式下构建时,数据结构运行良好

这是节点的析构函数:

    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;
            }
        }
    }
可以实现一个非递归算法来遍历树并删除它的所有节点


你能推荐一种高效的后序遍历算法来删除非二叉树吗

我会尝试更简单的方法,让递归析构函数完成它们的任务:

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

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


这不是递归本身,而是当您运行此树时,指令管道在数据访问上一直处于停滞状态。

考虑您是否真的需要遍历树并处理每个节点


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

下面是一个非递归解决方案的示意图:

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

编辑:缺少指向父节点的反向指针。这意味着我们不需要维护路径历史来回溯,并且我们可以取消堆栈

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;
}

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


Windows和Visual C++,默认值为1 MB,只需将其增加到10 MB或甚至100 MB,这个内存实际上不会是“强”提交< /强>直到(除非)你真正需要它,你才是“强”>保留它前面(请参阅选项)。您甚至可以仅针对调试配置选择性地执行此操作,以说明其中的“胖”堆栈。

您的算法比我的算法简单得多,但不幸的是,它无法解决我的特定问题。我建议进行此简化,因为我不确定您之前的算法,尤其是最后一部分的正确性(类似于删除lastChild…),堆栈使用率现在非常有限:只有2个指针*max_tree_depth。你已经验证了堆栈溢出吗?谢谢你的回复,我验证了堆栈溢出,现在它支持更多的节点。我希望有人提出一个迭代解决方案。否则我会将你的解决方案标记为已接受。这不是很简单吗er只是为了
删除firstChild;删除nextSibling;
?希望尾部调用优化将避免第二个析构函数调用的内存开销,因此您最终得到与
while
循环等效的东西。另一种方法(使用额外内存)是通过节点存储对数组中或专用链表中所有树节点的引用,并基于该引用而不是树结构进行删除。您应该公开原始代码,否则问题无法理解。您现在公开的代码(未接受我的答案),不要引发任何堆栈溢出,正如您已经测试过的。原始代码和您的代码确实不幸地引发了堆栈溢出。但您的代码更清晰…@chac我还原了我的原始代码经过仔细考虑,无论何时“删除”,都必须进行析构函数查找。这可以通过将它们链接到一个没有实际删除的列表中来避免。我认为这不起作用:当它返回到父级时,在删除子级之后,它仍然有第一个子集,但指向已释放的节点。