我正在做面试准备和审查图表实施。我一直看到的最重要的是邻接列表和邻接矩阵。当我们考虑基本操作的运行时,为什么我从未看到使用散列的数据结构?
例如,在Java中,邻接列表通常为ArrayList<LinkedList<Node>>
,但为什么人们不使用HashMap<Node, HashSet<Node>>
?
设n =节点数,m =边数。
在这两种实现中,删除节点v涉及搜索所有集合并删除v。在邻接列表中,那是O(n ^ 2),但在“邻接集”中,它是O(n)。同样,删除边缘涉及从v列表中删除节点u,从u列表中删除节点v。在邻接列表中,那是O(n),而在邻接集中,它是O(1)。其他操作,例如查找节点后继,查找两个节点之间是否存在路径等,对于这两种实现都是相同的。空间复杂度也都是O(n + m)。
我能想到的邻接集的唯一缺点是添加节点/边是分摊O(1),而在邻接列表中这样做是真的O(1)。
也许我没有看到任何东西,或者我在计算运行时时忘了考虑事情,所以请告诉我。
答案 0 :(得分:5)
与DavidEisenstat的答案一样,图表的实现也有很大不同。这是演讲中没有遇到的事情之一。有两种概念设计:
1) Adjacency list
2) Adjacency matrix
但您可以轻松扩充任一设计,以获得更快的插入/删除/搜索等属性。价格往往只是存储额外的数据!考虑实现一个相对简单的图形算法(比如...... Euler),看看你的图形实现如何对运行时复杂性造成巨大的影响。
为了使我的观点更清楚,我说“邻接列表”并不需要你使用LinkedList
。例如,维基引用了page:
Guido van Rossum建议的实现使用哈希表进行关联 图中的每个顶点都有一个相邻顶点的数组。在这 表示,顶点可以由任何可清洗对象表示。有 没有将边缘明确表示为对象。
答案 1 :(得分:1)
为什么人们不使用
HashMap<Node, HashSet<Node>>
?
除非同一组节点上有多个图形,否则HashMap
可以替换为Node
的成员变量。
HashSet
与LinkedList
的问题更为有趣。我猜想,对于稀疏图,LinkedList
在时间上(对于等效渐近复杂度的运算)和在空间中都会更有效。我对这两种表示都没有多少经验,因为根据算法要求,我通常更喜欢(i)将邻接列表存储为连续子阵列,或者(ii)为每个边缘都有一个存储信息的显式对象或一对对象关于边缘(例如,权重)并参与两个循环的双向链表(我自己的实现,因为Java和C ++标准库不支持侵入式数据结构),使节点删除与节点的程度和边删除成正比O (1)。
你为哈希引用的运行时间不是最坏的情况,只是对一个不经意的对手的高概率,尽管它们可以以进一步降低常数因子为代价进行无法分摊。
答案 2 :(得分:1)
许多理论问题涉及一组固定的顶点和边 - 没有删除。
许多/大多数图算法涉及简单地遍历邻接列表中的所有边或更复杂的东西(需要额外的数据结构)。
鉴于上述情况,您可以获得数组的所有优点(例如O(1)随机访问,节省空间)来表示没有任何缺点的顶点(例如固定大小,O(n)搜索/索引插入/删除),以及链表的所有优点(例如O(1)插入,对未知数量的元素有效空间)来表示没有任何缺点的边(O(n)搜索/随机访问)。
但是......哈希怎么样?
当然,散列具有与所需操作相当的效率,但是常数因素更差并且存在不可预测性,因为性能依赖于良好的散列函数和良好分布的数据。
现在不应该使用散列,如果你的问题需要散列,那就去吧。
答案 3 :(得分:0)
我们可能通常不会看到这种表示,因为很少需要检查图中是否存在任意边缘(我无法想到任何依赖于它的日常图算法),以及它在哪里需要时,我们只能为整个图形使用一个哈希映射,存储对(v1, v2)
来表示边缘。这似乎更有效。
(大多数常见的图算法都说&#34;对于顶点v的每个邻居,做...&#34;,然后邻接列表是完美的。)