用C++表示有1亿个节点的大图

8

我正在处理一个非常大的图形,其中节点数量达到5亿个,节点的平均度数为100。因此,这是一种稀疏图。我还必须存储每条边的权重。我目前正在使用两个向量来实现,如下所示:

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

使用vector嵌套vector似乎不太节省空间。 它需要超过400GB的存储空间。 有没有更好的节省空间的方法来在内存中存储这个大图? 有使用任何C ++库的建议吗?


2
对于稀疏结构,使用映射可能更好,但从这个问题中并不清楚您试图表示什么。节点的度是什么意思,为什么会暗示一个稀疏结构? - wally
@flatmouse 度数表示相邻顶点的数量。对于每个顶点,我最多可以有100个相邻顶点。 - viz12
你正在分配 V 向量的数组。老实说,看一下一些 C++ 图库,并确保你知道 C++ 语言本身的基础知识。 - Ulrich Eckhardt
@Galik,能否提供一些关于如何使它动态化的建议?在我的程序中,我需要动态更新邻接表。无法提前知道每个列表的大小会是多少。 - viz12
@UlrichEckhardt 我知道节点的数量将是 V,因为向量的数量也将是 V。但程序需要找出每个节点有多少相邻节点。因此,我创建了 V 个向量,然后在程序内部开始向每个向量添加相邻节点。 - viz12
尝试使用Boost图形库http://www.boost.org/doc/libs/1_62_0/libs/graph/doc/ - CPP_NEW
3个回答

7

初步说明

您可以考虑使用向量的向量来代替动态内存分配:

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

无论如何,您将在邻接表中拥有V个不同的vector<int>。每个向量都需要一些空间开销来管理其项目的大小和位置。不幸的是,通过在不同的向量/数组中保留权重,您会使这种开销加倍(以及与添加新链接相关的隐藏内存管理)。那么为什么不重新组合邻接表和权重呢?
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动态管理内存。但是如果您有很多预定大小的小对象,则可以考虑使用自己的分配器。这可以显着优化内存管理开销。
此外,如果您使用向量,在加载新节点的邻接列表时,立即为向量保留大小(如果您知道它)可能是有效的。这可以避免向量增长的几次连续重新分配。 对于数百万个节点,这可能非常昂贵。
图书馆?
在SO上搜索第三方库超出了范围。但是,如果以上提示不足够,则可以考虑使用现有的图形库,例如:

还有其他一些图形库可用,但许多库似乎已不再维护或者没有设计用于大容量。


6
你应该将图实现为二元决策图数据结构
简而言之,这个想法是,可以通过使用图的特征函数将图表示为二元函数。
通过使用特征函数,有多种方法将图编码为二元函数。在我帖子末尾发布的文章和视频中,有一种方法可以实现它。
BDD以紧凑的方式编码二元函数,并具有快速操作。可能这是宇宙中最强大的数据结构。
BDD的概念与trie类似,但每个节点不是根据下一个输入进行分配,而是每个节点都有一个属性X,表示变量的索引,如果函数F(..X=true..)为真,则继续在节点的高枝上到达叶节点true,如果F(..X=false..)为真,则继续在低枝上到达表示真的叶节点。这被称为布尔函数的Shannon扩展(使用相同的扩展公式也是计算布尔函数的硬件设计的一种方式,使用多路复用器)。
通常,对于每个可能的输入值X_i组合,使函数为真,我们有一条从根节点到true叶子的唯一分支,每个节点都根据输入变量Xi进行分支(根据Xi的true或false值在低或高方向上分支)。同一图表可用于保留多个函数(每个节点是不同的函数)。
有两种优化方法可以将二叉决策树转换为二叉决策图,从而使其更加紧凑。 优化的思想与有限自动机的最小化算法相同。 与自动机的情况一样,最小BDD对于该函数是唯一的(因此,如果要查看两个任意函数是否相同,只需将它们转换为BDD并查看表示一个函数的节点是否与另一个函数的根节点相同(比较两个指针值的复杂度为O(1)(常数时间)))。
其中一种优化方法是,如果一个节点的所有边都指向与其他节点相同的物理节点,则将两个节点合并为单个节点(可以通��保持所有创建的节点的哈希表来完成这项工作)。
另一种优化方法是,如果变量X的低边和高边进入变量Y的同一物理节点,则X节点会消失,因为该函数对于F(...X = true ...)= F(...X = false ...)具有相同的值。

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

当您了解BDD的基础知识后,如果您有耐心观看更长的演示,则this video非常好,并总结了如何将图形编码为BDD。

当需要管理数百万个节点时,BDD是现代专业软件的做法。


0

如果图形不会被修改,考虑使用两个向量:

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

这应该可以最小化开销。

当然,如果您无法进一步减少图形,则如此。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接