邻接列表最有效的实现?

时间:2015-05-17 14:36:21

标签: java performance adjacency-list

我想在Java中创建一个邻接列表,因为稍后我将获得一大组节点作为输入,它需要非常高效。

这种情况最适合哪种实施方式?

列表或地图列表?我还需要在某处保存边缘权重。我无法弄清楚如何做到这一点,因为邻接列表本身显然只是跟踪连接的节点,而不是边缘权重。

2 个答案:

答案 0 :(得分:1)

警告:这条路线是最无耻的,也是最难维护的,只有在需要尽可能高的性能时才推荐。

邻接列表是要优化的最笨拙的数据结构类别之一,主要是因为它们的大小从一个顶点到下一个顶点不同。在某些广义的概念层面,如果您将邻接数据作为VertexNode定义的一部分包含在内,那么这将构成Vertex / Node 变量的大小。可变大小的数据和缓存友好所需的内存连续性往往在大多数编程语言中相互竞争。

大多数面向对象的语言并未设计用于处理实际大小不同的对象。他们通过在其他地方指向/引用内存来解决这个问题,但这会导致更高的缓存未命中。

如果你想要尖端的效率并且你经常遍历相邻的顶点/节点,那么你需要一个顶点及其可变数量的引用/索引到相邻的邻居(以及它们在你的情况下的权重)以适应单个缓存线,并且可能很有可能这些相邻顶点中的一些也适合同一个高速缓存行(虽然解决这个并重新组织数据以将2D图形映射到一维存储空间是一个NP难问题,但是现有的启发式帮助很多。)

所以它不再成为数据结构使用的问题,而是使用内存布局。数组是你的朋友,但不是节点的数组。您需要一个连续的 bytes 数组打包节点数据。像这样:

[node1_data num_adj adj1 adj2 adj3 (possibly some padding for alignment and to avoid straddling) node2_data num_adj adj1 adj2 adj3 ...]

此处的节点插入和删除开始类似于您实现内存分配器的算法类型。连接新边时,实际上会改变节点的大小,并可能改变它在这些巨大的连续内存块中的位置。与内存分配器不同,如果您可以更新引用/索引,则可能允许对数据进行重新洗牌,压缩和碎片整理。

现在,只有当您需要最快的解决方案时,并且只要您的用例非常重视读取操作(评估,遍历)而不是写入(连接边缘,插入节点,删除节点)。否则它就完全过度了,而且是一个完整的PITA,因为你将丢失所有那些很好的面向对象的结构,这有助于保持代码易于维护,重用等等。这让你忘记了所有的结构,有利于处理位和字节级别的事物,如果您的软件处于某个领域,它的质量在某种程度上与该图的效率成正比,那么它是值得做的。

答案 1 :(得分:0)

您可以想到的一个解决方案是创建一个包含数据和wt的类Node。这个重量将是它连接到节点的边缘的重量。

假设您有一个Node I 的列表,该列表连接到节点 ABC ,边缘权重 ab c。并且Node J xyz 权重连接到 ABC ,因此我的adj List将包含Node对象

 I -> <A, a>,<B b>,<C c>

J的列表将包含Node对象

 J -> <A, x>,<B y>,<C z>