图形实现和邻接矩阵的初始化

时间:2012-05-13 08:56:22

标签: java algorithm optimization data-structures graph

图表通常使用邻接矩阵表示。各种来源表明可以避免初始化成本为| V ^ 2 | ( V 是顶点的数量),但我可能还没弄清楚如何。

在Java中,只需通过声明矩阵,例如: boolean adj [][],运行时将自动使用false初始化数组,这将是 O(V ^ 2)成本(数组)。
我误解了吗?是否有可能避免邻接矩阵初始化的二次成本,或者这只是理论依赖于实现语言的东西?

4 个答案:

答案 0 :(得分:2)

这可以通过使用邻接矩阵的稀疏矩阵表示来实现,其中仅分配“1”的位置而不是矩阵的每个元素(可能包括大量的零)。您可能会发现this thread也很有用

答案 1 :(得分:2)

矩阵值的默认初始化实际上是一个特征。如果不是默认初始化,你是否仍然需要自己初始化每个字段,以便知道它的价值是什么?

邻接矩阵有这样的缺点:它们在内存效率方面很差(它们需要O(n 2 )存储单元)并且如你所说它们的初始化速度较慢。然而,初始化从未被认为是最大的问题。相信我,内存分配要慢很多,并且所需的内存比初始化时间更有限。


在许多情况下,人们更喜欢使用邻接列表而不是矩阵。此类列表需要O(m)个内存,其中m是图表中的边数。这样效率更高,特别是对于稀疏图。此图表表示比邻接矩阵更差的唯一操作是查询is there edge between vertices i and j。矩阵在O(1)时间内回答,列表肯定会慢得多。

然而,许多典型的图算法(如DijkstraBellman-FordPrimTarjanBFSDFS)只会需要迭代给定顶点的所有邻居。如果使用邻接列表而不是矩阵,所有这些算法都会受益匪浅。

答案 2 :(得分:2)

这个帖子中存在很多混乱和错误信息。实际上,存在一种避免邻接矩阵(以及通常的任何阵列)的初始化成本的方法。但是,不可能将该方法与Java原语一起使用,因为它们是使用默认值初始化的。

假设您可以创建一个未自动初始化的数组data[0..n]。首先,它充满了以前在内存中的垃圾。如果我们不想花费 O(n)时间来覆盖它,我们需要一些方法来区分我们从垃圾数据中添加的好数据。

"技巧"是使用辅助堆栈来跟踪包含良好数据的单元格。我们第一次写入data[i]时,会向堆栈添加索引i。由于堆栈只会随着我们的添加而增长,因此它永远不会包含我们需要担心的任何垃圾。

现在每当我们访问data[k]时,我们都可以通过扫描堆栈k来检查它是否是垃圾。但是每次读取都需要 O(n)时间,首先要击败阵列点!

为了解决这个问题,我们制作另一个也开始充满垃圾的辅助数组stack_check[0..n]。此数组包含指向堆栈中元素的指针。现在,当我们第一次写入data[i]时,我们将i推送到堆栈并将stack_check[i]设置为指向新的堆栈元素。

如果data[k]是好数据,则stack_check[k]指向包含k的堆栈元素。如果data[k]是垃圾,那么stack_check[k]的垃圾值要么指向​​堆栈外部,要么指向除k之外的某个堆栈元素(因为k从未放在堆栈上)。检查此属性只需要 O(1)时间,因此我们的数组访问速度仍然很快。

将它们整合在一起,我们可以通过让它们充满垃圾来在 O(1)时间创建我们的数组和辅助结构。在每次读写时,我们使用我们的帮助程序检查给定单元格是否在 O(1)时间内包含垃圾。如果我们正在编写垃圾,我们更新我们的帮助器结构以将单元格标记为有效数据。如果我们阅读垃圾,我们可以以适合给定问题的任何方式处理垃圾。例如,我们可以返回一个默认值,如0(现在你甚至不能告诉我们没有初始化它!)或者可能会抛出异常。

答案 3 :(得分:0)

我将详细说明A_A的答案。他建议使用稀疏矩阵,这基本上意味着你要回到维护邻接列表。

使用矩阵有两个原因 - 如果你根本不关心它的性能,比如它提供的简单代码,或者如果你关心性能但你的矩阵将会相对充满(让我们说至少20%满,为了这篇文章)。

你显然关心表现。如果你的矩阵相对空,它的初始化开销可能是有意义的,你最好使用邻接列表。如果它会非常满,初始化变得可以忽略不计 - 你需要在矩阵中填充正确的单元格(这不仅需要初始化它),而且你需要处理它们(这需要花费更多的时间)初始化它。)