以下算法的时间复杂度是多少?

时间:2019-10-07 10:40:38

标签: algorithm graph time-complexity

我只想确认以下算法的时间复杂度。

编辑:在下面的内容中,我们不假定正在使用最佳数据结构。但是,可以随意提出使用这种结构的解决方案(清楚地提到使用了什么数据结构以及它们对复杂性有什么有益的影响)。

enter image description here

符号:n = | V |,m = | E |和avg_neigh之后的数字表示图中任何给定节点的平均邻居数。

假设:图形未加权,无向,并且其邻接列表表示已加载到内存中(包含每个顶点邻居的列表列表)。

这是我们到目前为止所拥有的:

第1行:计算度为O(n),因为它只涉及获取邻接列表表示形式中每个子列表的长度,即执行n O(1)个操作。

第3行:找到最低值需要检查所有值,即O(n)。由于它嵌套在一次访问所有节点的while循环中,因此它变为O(n ^ 2)。

第6-7行:删除顶点v为O(avg_neigh ^ 2),因为我们知道从邻接列表表示中v的邻居,并且从每个邻居的子列表中删除v为O( avg_neigh)。第6-7行嵌套在while循环中,因此它变为O(n * avg_neigh ^ 2)。

第9行:它是O(1),因为它只涉及获取一个列表的长度。它嵌套在for循环和while循环中,因此变为O(n * avg_neigh)。

摘要:总复杂度为O(n)+ O(n ^ 2)+ O(n * avg_neigh ^ 2)+ O(n * avg_neigh)= O(n ^ 2)

注释1 :如果每个子列表的长度都不可用(例如,由于无法将邻接列表加载到内存中),则计算第1行的度为O(n * avg_neigh),因为每个列表平均包含avg_neigh元素,所以有n个子列表。在第9行,总复杂度变为O(n * avg_neigh ^ 2)。

注释2 :如果对图形进行了加权,则可以将边缘权重存储在邻接列表表示中。但是,要获得第1行中的度数,需要对每个子列表求和,如果邻接表已加载到RAM中,则现在为O(n * avg_neigh),否则为O(n * avg_neigh ^ 2)。同样,第9行变为O(n * avg_neigh ^ 2)或O(n * avg_neigh ^ 3)。

2 个答案:

答案 0 :(得分:3)

有一种算法可以将(1)识别为算法1的实现,而(2)在时间O(| E | + | V |)上运行。

首先,让我们考虑算法1的本质。在图形为空之前,请执行以下操作:将优先级最低的节点的优先级记录为该节点的核心编号,然后删除该节点。动态地将节点的优先级定义为(1)残差图中的度数和(2)已删除邻居的核心数的最大值。观察到节点的优先级永远不会增加,因为它的程度永远不会增加,并且优先级更高的邻居不会被首先删除。而且,优先级总是在每次外循环迭代中都恰好减少一个。

数据结构支持足以实现将O(| E | + | V |)时间很好地分割成

  1. 从初始图(初始化时间O(| E | + | V |))开始支持的图

    • 删除节点(O(度+ 1),所有节点的总和为O(| E | + | V |)),
    • 获取节点的剩余度(O(1))。
  2. 从初始度(初始化时间O(| V |))开始支持的优先级队列

    • 插入最小优先级元素(分摊O(1),
    • 将元素的优先级降低一个(O(1)),永远不小于零。

合适的图形实现使用双向链接的邻接表。每个无向边都有两个对应的列表节点,除了源自同一顶点的前一个/下一个节点之外,它们还相互链接。度数可以存储在哈希表中。事实证明,我们只需要残差度来实现该算法,因此不必费心存储残差图。

由于优先级是介于0和| V |之间的数字-1 bucket queue的效果非常好。此实现由双向链接列表的| V |元素向量组成,其中索引p处的列表包含优先级p的元素。我们还跟踪队列中元素的最低优先级的下限。为了找到最小优先级元素,我们从此边界开始按顺序检查列表。在最坏的情况下,这会花费O(| V |),但是如果我们在算法增加边界时就记入算法,而在边界减小时就借记该算法,那么我们将获得一种抵消此扫描的可变成本的摊销方案。

答案 1 :(得分:2)

如果选择的数据结构可以支持对最小值的有效查找和有效的删除(例如self balancing binary treemin heap),则可以在O(|E|log|V|)中完成。

  • O(|V|)中完成第1行中的数据结构的创建,或者 O(|V|log|V|)
  • 循环在v中的每个节点V上恰好循环一次。
  • 对于每个这样的节点,它需要查看最小值,并调整DS,以便您可以有效地查看下一个迭代的下一个最小值。为此需要O(log|V|)(否则,您可以在o(nlogn)中进行堆排序-在此处注意little o notation)。
  • 使用有效的集实现,例如hash table,可以在neighbors中获取V,并从EO(|neighbors|)中删除项目。由于在此步骤中每个边缘和节点仅选择一次,因此它会汇总O(|V| + |E|)中的所有迭代。
  • neighbors上的for循环总计为O(|E|),这要归功于每个边在这里精确地计数一次。
  • 但是,在此循环中,您可能需要更新p,而这种更新的费用为O(log|V|),因此总计为O(|E|log|V|)

总和为O(|V|log|V| + |E|log|V|)。假设图不是很稀疏(|E|Omega(|V|中),则为O(|E|log|V|)