【问题标题】:Representation of Large Graph with 100 million nodes in C++用 C++ 表示具有 1 亿个节点的大型图
【发布时间】:2017-03-26 05:45:48
【问题描述】:

我正在处理一个非常大的图,它有 5 亿个节点,节点的平均度数是 100。所以它是一种稀疏图。我还必须存储每条边的权重。我目前正在使用两个向量,如下所示

// V could be 100 million
vector<int> *AdjList = new vector<int>[V];
vector<int> *Weight  = new vector<int>[V];

使用vectorvector 似乎并不节省空间。它需要超过 400GB 的存储空间。有没有更好的空间效率方法来将这个大图存储在内存中?对使用任何 c++ 库有什么建议吗?

【问题讨论】:

  • 对于稀疏结构,地图可能会更好,但从这个问题中不清楚您要代表什么。 degree of nodes 是什么意思,为什么这意味着稀疏结构?
  • @flatmouse 度数表示相邻顶点的数量。对于每个顶点,我最多可以有 100 个相邻(相邻)顶点。
  • 您正在分配 V 向量的数组。老实说,看看一些 C++ 图形库,确保你了解 C++ 语言本身的扎实基础知识。
  • @Galik 您能否建议如何使用使其动态化?在我的程序中,我动态更新邻接列表。我事先不知道每个列表的大小。
  • @UlrichEckhardt 向量的数量将是V,因为我知道有V 个节点。但是程序会找到每个节点有多少个相邻节点。所以我创建了V 向量,然后在程序内部我开始将相邻节点添加到每个向量。

标签: c++ vector graph bigdata


【解决方案1】:

您应该将图表实现为binary decision diagram data structure

简单地说,这个想法是,通过使用图的characteristic function,可以将图表示为二进制函数。

有多种方法可以使用特征函数将图编码为二元函数。在我在帖子末尾发布的文章和视频中,有一种方法可以做到这一点。

BDD 以紧凑的方式对二进制函数进行编码,运算速度快。这可能是宇宙中最强大的数据结构。

BDD 的思想与trie 中的几乎相同,但在每个节点上,我们不会在下一个输入的函数中分派,而是每个节点都有属性X,它代表索引一个变量,如果函数F(..X=true..) 为真,继续在节点的高分支上到达叶true,如果F(..X=false..) 为真,继续在低分支上向下到达叶节点表示真。这称为布尔函数的Shannon expansion(通过使用相同的展开公式也是一种计算布尔函数硬件设计的方法,使用多路复用器)。

一般来说,对于函数为真的输入值 X_i 的每个可能组合,我们都有一个唯一的分支,从根节点到 true 叶,在输入变量 @987654332 的函数中的每个节点处分支@(我们根据 Xi 的值 true 或 false 向低或高方向分支)。同一张图可以用来保持多个功能(每个节点是不同的功能)。

有 2 种优化可以将二叉决策树转换为二叉决策图,从而使其紧凑。优化的思想与有限自动机的最小化算法的优化相同。与自动机的情况相同,最小 BDD 对于函数是唯一的(因此要查看 2 个任意函数是否相同,只需将它们转换为 BDD 并查看代表一个函数的节点是否与根相同另一个函数的节点(复杂度 O(1)(恒定时间)比较 2 个指针值)。

一种优化是说,如果一个节点的所有边与另一个节点位于相同的物理节点中,我们将两个节点统一为一个节点(这可以在创建时通过保留所有已创建节点的哈希表来完成)。

其他优化说,如果变量 X 的节点的低边和高边进入变量 Y 的同一个物理节点,则 X 节点消失,因为函数具有相同的 F(...X =真...)=F(...X=假...)。

有数千篇关于 BDD 及其派生的文章(改变我们得到的每个节点的调度解释,例如 ZDD,用于无序集的紧凑表示)。典型的主题文章是 C. Dong P. Molitor 的What graphs can be efficiently represented by BDDs ?

在您了解 BDD 的基础知识后,如果您有耐心进行更长时间的演示,this video 非常棒,它总结了如何将图形编码为 BDD。

BDD 是当今专业软件在需要管理数百万个节点时所做的。

【讨论】:

    【解决方案2】:

    好吧,如果不修改图形,请考虑使用两个向量:

    struct edge { int target; int weight; };
    std::vector<edge> edges;
    std::vector<int> nodes; // begin-index into edges
    

    这应该可以最大限度地减少开销。

    当然,如果你不能进一步缩小图形。

    【讨论】:

      【解决方案3】:

      前言

      您可以考虑使用向量的向量而不是使用动态内存分配:

      vector<vector<int>> AdjList(V);
      

      在任何情况下,您的邻接列表中都会有 V 个不同的vector&lt;int&gt;。每个向量都需要一些空间开销来管理其项目的大小和位置。不幸的是,通过将权重保持在不同的向量/数组中,您将这种开销(以及添加新链接时相关的隐藏内存管理)加倍。

      那么为什么不重新组合邻接表和权重呢?

      struct Link {  
         int target;   // node number that was in adj list.  Hope none is negative!!
         int weight;   
      };
      vector<vector<Link>> AdjList(V);
      

      结构是否稀疏?

      如果大多数节点都有某种链接,这很好。

      如果相反,许多节点没有传出链接(或者如果您有很大的未使用节点 ID 范围),那么您可以考虑:

      map<int, vector<Link>> AdjList;  
      

      map 是一个关联数组。对于具有传出链接的节点,只有向量。顺便说一句,你可以为你的节点使用任何你想要的编号方案,甚至是负数。

      您甚至可以更进一步,使用双地图。第一张地图为您提供传出节点。第二个映射将目标节点映射到权重:

      map<int, map<int, int>> Oulala; 
      

      但这可能会占用更多内存。

      大容量?

      mapvector 使用默认分配器动态管理内存。但是你有很多预定大小的小物体。所以你可以考虑使用你自己的allocator。这可以显着优化内存管理开销。

      另外,如果您使用向量,当您加载新节点的邻接列表时,立即保留向量的大小(如果您知道的话)可能是有效的。这可以避免向量增长的几次连续重新分配。对于数百万个节点,这可能非常昂贵。

      库?

      搜索第三方库超出了 SO 的范围。但如果以上提示还不够,您可以考虑使用现有的图形库,例如:

      周围还有几个其他图形库,但其中许多似乎不再维护或不是为大容量而设计的。

      【讨论】:

        猜你喜欢
        • 2012-03-27
        • 2019-07-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-01-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多