图数据结构内存管理

时间:2014-05-04 04:02:24

标签: c++ algorithm memory-management graph

我想为我的项目实现自定义图形数据结构,我对正确的内存管理有疑问。

本质上,数据结构将包含具有两个向量的节点:一个用于进入节点的边缘,另一个用于从节点出来的边缘(没有环形边缘)。图表已连接。该图表还包含一个条目'没有边缘的节点。边是一个指向节点的指针。

我的问题是:为这种类型的数据结构清理内存的最佳方法是什么?如果只有一个入口边缘(此时此结构退化为n-ary树),我理解如何做到这一点,但我不知道在有多个节点有边缘的情况下该怎么做进入一个节点。我不能只是从任意一个入口节点调用delete,因为这可能会导致双重释放'以后的错误。

例如,假设我有这个子图:

C <-- B
^
|
A

如果我要从节点B调用delete,我会删除为C分配的内存,但是A仍然会有一个指向它的指针。因此,如果我想清除A连接的所有节点,我会得到一个双重自由错误。

4 个答案:

答案 0 :(得分:2)

删除组件时,您需要执行搜索以确定哪个节点仍然连接到输入边缘。如果您最终拥有多个连接组,则需要确定其中哪一个包含入口节点并删除所有其他组。

的贪婪(本地)算法不存在,可以通过简单的思想实验来显示:

设A,B是仅通过节点n连接 的子图,该节点应被删除。我们留下了两个未连接的子图。如果我们刚刚删除了进入A或B的入口节点的唯一路由,则无法知道(每个节点没有一大堆状态)。并且,有必要弄清楚这一点,以便适当选择删除可以制作A或B.

即使每个节点都存储到入口节点的每个路由,也意味着每当删除单个节点时都必须清除所有节点中的所有路由。

解决方案草图

让我们谈谈我们需要做的事情的图形表示:

首先,将要删除的节点着色为黑色。然后对我们遇到的每个节点执行以下操作:

对于未着色的节点:

  • 如果我们来自的节点是黑色,请为此节点指定一种新颜色
  • 如果我们来自的节点是彩色的,请为此节点指定相同的颜色
  • 穿越每个外围的边缘

对于彩色节点:

  • 如果我们来自的节点是黑色,只需返回
  • 如果我们来自的节点颜色相同,只需返回
  • 即可
  • 如果我们来自的节点有不同的颜色,请合并两种颜色(例如,记住绿色和蓝色相同,或者将每个绿色节点涂成蓝色)
  • 穿越每个外围的边缘

最后,我们将在删除当前节点后知道哪些连接组件将存在。必须删除不包含入口节点的所有连接组件(加上我们原来要删除的节点)(注意:如果我们的待删除节点是入口节点,这可能会删除每个节点...)

实施

您需要一个如下所示的数据结构:

struct cleanup {
    vector<set<node*>> colors;
    node* to_be_deleted;
    size_t entry_component;
};

列表向量的索引将是您的“颜色”。 “黑色”将由to_be_deleted的使用表示。最后,entry_component将包含具有条目节点的颜色的索引。

现在,可以实现以前的算法。有很多事情需要考虑,实施可能最终会有所不同,具体取决于您已经为其他操作保留了哪种支持结构。

答案 1 :(得分:1)

答案取决于图表的复杂性:

  1. 如果图表是树,则每个父级都可以拥有其子级并在其析构函数中删除它们。

  2. 如果图形是有向非循环图形,那么处理它的一种简单而高效的方法是在节点上进行引用计数。

  3. 如果图表可以是循环的,那你就不走运了。您需要跟踪图中的每个节点,然后进行垃圾回收。根据您的使用情况,您可以通过

    进行收集
    • 完成完整图表或

    • 后清理所有内容
    • 重复标记所有连接的节点并清理所有无法访问的节点。

  4. 如果有可能逃避选项1或2(可能调整问题以确保图表满足约束条件),你应该这样做;选项3意味着代码复杂性和运行时方面的重大开销。

答案 2 :(得分:0)

有两种方法。一种方法是让您的节点知道其他节点有哪些边缘。因此,如果从B中删除C,C将需要从A中删除它的边缘。因此,当您删除/删除A时,它将不会尝试删除C.

std :: shared_ptr或其他类型的引用计数也可能对您有用。

答案 3 :(得分:0)

这是在实现图表时避免内存问题的一种简单方法:不要使用指针来表示边缘。

相反,为每个节点提供一个唯一的ID号(递增的整数计数器就足够了)。保留全局unordered_map<int, shared_ptr<Node> >,以便您可以通过其ID号快速查找任何节点。然后,每个节点可以将其边缘表示为一组整数节点ID。

删除节点后(即将其从节点的全局地图中删除),其他一些节点现在可能会有&#34;悬空边缘&#34;,但这很容易被发现并且处理因为当你在全局地图中查找现在删除的Node的ID时,查找将失败。然后,您可以通过忽略该边缘,或通过删除其边缘的源节点等来优雅地进行响应。

这样做的好处:代码仍然非常简单,无需担心引用周期,内存泄漏或双重释放。

缺点:遍历图形的效率稍差(因为进行地图查找比简单的指针取消引用需要更多的周期)和(取决于你正在做的事情)悬挂的边缘和&#39;悬空边缘& #39;可能需要偶尔清理扫描(但这些很容易做到......只是遍历全局地图,并且对于每个节点,检查其边缘集中的每个边缘并删除那些不存在的ID。全球地图)

更新:如果您不想进行大量的unordered_map查找,您可以通过使用weak_ptr代表您的边缘来获得非常相似的功能。当指向的对象消失时,weak_ptr将自动变为NULL /无效。