对于C ++中的图形问题,什么是更好的,邻接列表或邻接矩阵?

时间:2010-02-07 20:59:03

标签: c++ graph adjacency-list adjacency-matrix

对于C ++中的图形问题,什么是更好的邻接列表或邻接矩阵? 各有哪些优缺点?

11 个答案:

答案 0 :(得分:100)

这取决于问题。

Adjacency Matrix

  • 使用O(n ^ 2)内存
  • 快速查找并检查是否存在特定边缘 在任何两个节点O(1)
  • 之间
  • 迭代所有边缘的速度很慢
  • 添加/删除节点很慢;复杂的操作O(n ^ 2)
  • 添加新边O(1)
  • 的速度很快

Adjacency List

  • 内存使用量取决于边数(不是节点数),
    如果邻接矩阵稀疏,可能会节省大量内存
  • 查找任意两个节点之间是否存在特定边缘 比矩阵O(k)略慢;其中k是邻居节点的数量
  • 迭代所有边缘的速度很快,因为您可以直接访问任何节点邻居
  • 添加/删除节点很快;比矩阵表示更容易
  • 快速添加新边O(1)

答案 1 :(得分:73)

这个答案不仅适用于C ++,因为所提到的一切都是关于数据结构本身,无论语言如何。而且,我的答案是假设你知道邻接列表和矩阵的基本结构。

内存

如果内存是您主要关心的问题,您可以按照此公式获得一个允许循环的简单图表:

邻接矩阵占用n 2 / 8字节空间(每个条目一位)。

邻接列表占用8e空间,其中e是边数(32位计算机)。

如果我们将图的密度定义为d = e / n 2 (边数除以最大边数),我们可以找到列表占用的“断点”比矩阵更多的记忆:

8e>当 d>时,n 2 / 8 1/64

因此,对于这些数字(仍然是32位特定的),断点落在 1/64 。  如果密度(e / n 2 )大于1/64,那么如果你想节省内存,最好选择矩阵

您可以在wikipedia(关于邻接矩阵的文章)和许多其他网站上阅读此内容。

旁注:可以通过使用哈希表来提高邻接矩阵的空间效率,其中键是顶点对(仅限于非直接)。

迭代和查找

邻接列表是仅表示现有边的紧凑方式。然而,这是以可能缓慢查找特定边缘为代价的。由于每个列表与顶点的程度一样长,因此检查特定边的最坏情况查找时间可以变为O(n),如果列表是无序的。 然而,查找顶点的邻居变得微不足道,对于稀疏或小图,迭代邻接列表的成本可能可以忽略不计。

另一方面,邻接矩阵使用更多空间以提供恒定的查找时间。由于存在每个可能的条目,因此您可以使用索引在恒定时间内检查是否存在边。但是,邻居查找需要O(n),因为您需要检查所有可能的邻居。 显而易见的空间缺点是,对于稀疏图形,添加了大量填充。有关详细信息,请参阅上面的内存讨论。

如果您仍然不确定要使用什么:大多数现实问题会产生稀疏和/或大图,这些图更适合邻接列表表示。它们似乎更难实现,但我向你保证它们不是,当你编写BFS或DFS并想要获取节点的所有邻居时,它们只是一行代码。但请注意,我并不是在推广邻接列表。

答案 2 :(得分:31)

好的,我已经在图表上编制了时间和空间基本操作的复杂性 下图应该是不言自明的 请注意当我们期望图形密集时,邻接矩阵是否更可取,以及当我们期望图形稀疏时,邻接列表如何更可取。 我做了一些假设。问我复杂性(时间或空间)是否需要澄清。 (例如,对于稀疏图,我将En作为一个小常数,因为我假设添加一个新顶点只会添加一些边,因为我们希望图形在添加之后仍保持稀疏顶点。)

请告诉我是否有任何错误。

enter image description here

答案 3 :(得分:17)

这取决于你在寻找什么。

使用 邻接矩阵 ,您可以快速回答有关两个顶点之间的特定边缘是否属于图形的问题,还可以快速插入和删除边缘。 缺点是你必须使用过多的空间,特别是对于有许多顶点的图形,这是非常低效的,特别是如果你的图形很稀疏。

另一方面,使用 邻接列表 ,更难检查给定边是否在图中,因为您必须搜索相应的列表才能找到边缘,但它们更节省空间。

一般来说,邻接列表是大多数图形应用程序的正确数据结构。

答案 4 :(得分:8)

如果您正在查看C ++中的图形分析,可能首先要开始的是boost graph library,它实现了许多算法,包括BFS。

修改

关于SO的上一个问题可能会有所帮助:

how-to-create-a-c-boost-undirected-graph-and-traverse-it-in-depth-first-searcħ

答案 5 :(得分:7)

假设我们有一个图表,其中 n 节点数和 m 边数,

示例图
enter image description here

邻接矩阵 我们正在创建一个具有 n 行数和列数的矩阵,因此在内存中它将占用与n 2 成比例的空间。检查名为 u v 的两个节点之间是否有边缘将占用Θ(1)时间。例如,检查(1,2)是代码中的边缘如下所示:

if(matrix[1][2] == 1)

如果你想识别所有边,你必须在矩阵上迭代,这将需要两个嵌套循环,它将采用Θ(n 2 )。 (您可以使用矩阵的上三角形部分来确定所有边,但它将再次为Θ(n 2 ))

邻接列表: 我们正在创建一个列表,每个节点也指向另一个列表。您的列表将包含 n 元素,每个元素将指向一个列表,该列表的项目数等于此节点的邻居数(查看图像以获得更好的可视化)。因此,它将占用内存中与 n + m 成比例的空间。检查(u,v)是否为边将花费O(deg(u))时间,其中deg(u)等于u的邻居数。因为最多,你必须遍历u指向的列表。识别所有边将采用Θ(n + m)。

示例图表的邻接列表

enter image description here
您应该根据自己的需要做出选择。 由于我的声誉,我无法放置矩阵图像,对不起

答案 6 :(得分:5)

最好用例子来回答。

Floyd-Warshall为例。我们必须使用邻接矩阵,否则算法会渐近变慢。

或者如果它是30,000个顶点上的密集图形怎么办?然后一个邻接矩阵可能有意义,因为你将每对顶点存储1位,而不是每边缘16位(你需要一个邻接列表的最小值):那是107 MB,而不是1.7 GB。

但对于像DFS,BFS(以及使用它的那些,如Edmonds-Karp),优先级优先搜索(Dijkstra,Prim,A *)等算法,邻接列表与矩阵一样好。好吧,当图表密集时,矩阵可能会有一个微小的边缘,但只有一个不起眼的常数因子。 (多少钱?这是一个试验的问题。)

答案 7 :(得分:3)

添加到keyser5053关于内存使用情况的答案。

对于任何有向图,邻接矩阵(每边1位)消耗n^2 * (1)位内存。

对于complete graph,邻接列表(具有64位指针)消耗n * (n * 64)个内存,不包括列表开销。

对于不完整的图,邻接列表消耗0个内存,不包括列表开销。


对于邻接列表,您可以使用以下公式来确定邻接矩阵最适合内存之前的最大边数(e)。

edges = n^2 / s确定最大边数,其中s是平台的指针大小。

如果您的图表是动态更新的,那么您可以使用n / s的平均边数(每个节点)来保持此效率。


一些例子(64位指针)。

对于有向图,其中n为300,使用邻接列表的每个节点的最佳边数为:

= 300 / 64
= 4

如果我们将其插入keyser5053的公式,d = e / n^2(其中e是总边数),我们可以看到我们低于断点(1 / s):

d = (4 * 300) / (300 * 300)
d < 1/64
aka 0.0133 < 0.0156

但是,指针的64位可能过度。 如果你改为使用16位整数作为指针偏移,我们在断点之前最多可以容纳18个边。

= 300 / 16
= 18

d = ((18 * 300) / (300^2))
d < 1/16
aka 0.06 < 0.0625

这些示例中的每一个都忽略了邻接列表本身的开销(64*2用于向量和64位指针。)

答案 8 :(得分:3)

根据邻接矩阵的实施,&#39; n&#39;为了有效实施,应该早先知道图的图形。如果图形过于动态并且需要时不时地扩展矩阵,这也可以算作下行?

答案 9 :(得分:2)

如果您使用哈希表而不是邻接矩阵或列表,那么您将获得更好或相同的大O运行时和空间用于所有操作(检查边缘是O(1),获取所有相邻边都是O(degree)等。

虽然对于运行时和空间都有一些常数因素开销(哈希表不像链表或数组查找那么快,并且需要相当多的额外空间来减少冲突)。 / p>

答案 10 :(得分:1)

接下来要讨论克服常规邻接列表表示的权衡,因为其他答案已涵盖其他方面。

通过利用 Dictionary HashSet 数据结构,可以在摊销的常量时间内使用 EdgeExists 查询在邻接列表中表示图形。我们的想法是将顶点保留在字典中,并且对于每个顶点,我们保留一个散列集,引用它具有边缘的其他顶点。

在这个实现中的一个小的权衡是它将具有空间复杂度O(V + 2E)而不是O(V + E),因为在常规邻接列表中,因为边缘在这里被表示两次(因为每个顶点都有它自己的哈希边集)。但是 AddVertex AddEdge RemoveEdge 等操作可以使用此实现在摊销时间O(1)中完成,但 RemoveVertex除外取O(V)之类的邻接矩阵。这意味着除了实现简单性邻接矩阵之外,没有任何特定的优势。我们可以在稀疏图上节省空间,在这个邻接列表实现中具有几乎相同的性能。

详细了解Github C#存储库中的以下实现。请注意,对于加权图,它使用嵌套字典而不是字典 - 哈希集组合,以便适应权重值。类似地,对于有向图,在&amp;中存在单独的散列集。出边缘。

Advanced-Algorithms

注意:我相信使用延迟删除我们可以进一步优化 RemoveVertex 操作到O(1)摊销,即使我还没有测试过这个想法。例如,删除时只需在字典中将顶点标记为已删除,然后在其他操作期间懒惰地清除孤立的边缘。