【问题标题】:Running time of Prim's algorithmPrim算法的运行时间
【发布时间】:2015-06-18 03:05:05
【问题描述】:

假设图中的所有边权重都是 1 到 |V| 范围内的整数。你能让 Prim 的算法运行多快?如果对于某个常数 W,边权重是 1 到 W 范围内的整数怎么办?

MST-PRIM(G,w,r)
1.  for each u∈  G.V
2.       u.key=inf
3.       u.π=NIL
4.  r.key=0
5.  Q=G.V
6.  while Q != Ø 
7.     u=EXTRACT-MIN(Q)
8.     for each v ∈  G.Adj[u]
9.         if v∈  Q and w(u,v)<v.key
10.          v. π=u
11.          v.key=w(u,v)

根据我的教科书:

Prim 算法的运行时间取决于我们如何实现最小优先级队列 Q。如果我们将 Q 实现为二进制最小堆,我们可以使用 BUILD-MIN-HEAP 过程执行 O 中的第 1-5 行(五)时间。 while 循环的主体执行 |V|次,并且由于每个 EXTRACT-MIN 操作需要 O(lg V) 时间,所以对 EXTRACT-MIN 的所有调用的总时间为 O(VlgV)。第 8-11 行中的 for 循环总共执行 O(E) 次,因为所有邻接列表的长度之和为 2|E|。在 for 循环中,我们可以在第 9 行中通过为每个顶点保留一个位来判断它是否在 Q 中,并在顶点从 Q 中删除时更新该位,从而在恒定时间内实现对 Q 中的成员资格的测试。第 11 行的赋值涉及对最小堆的隐式 DECREASE-KEY 操作,二进制最小堆在 O(lg V) 时间内支持该操作。因此,Prim 算法的总时间为 O(V lg V+E lg V)=O(E lg V)。

第 1-4 行需要 O(V) 时间。我已经阅读了一些解释为什么 BUILD-MIN-HEAP 过程需要线性时间,但我还没有理解它们。你能解释一下为什么 MIN-HEAP 过程的时间复杂度是 O(V) 吗?

此外,我认为在最小堆中,最小元素位于根部。那么,为什么每个 EXTRACT-MIN 操作都需要 O(lg V) 时间?

然后,for 循环执行 O(Σ_{v in V.G} deg(v)) 次,对吗?你能解释一下为什么 Σ_{v in V.G} deg(v)=2E 吗?

另外,如果我们知道对于某个常数 W,边权重是从 1 到 W 范围内的整数,会有什么不同?

编辑

假设图中所有边的权重都是范围内的整数 从 1 到 |V|。你能让 Prim 的算法运行多快?

我们可以对上述算法进行哪些更改,以使 Prim 算法尽可能快地运行?

【问题讨论】:

  • 只是出于好奇:... 大学? ; )

标签: algorithm time heap prims-algorithm


【解决方案1】:

答案-1

第一个问题可以在stackoverflow中找到-this question

答案 2

Extract min 操作在 O(1) 时间内从根中获取最小元素,但与此同时,我们需要执行使下一个 Extract min 操作在 O(1) 时间内给出最小元素的操作。我们做什么?我们只是将根元素与最右边的叶节点交换,然后删除该叶节点并堆积根,这可能会向下渗透到叶节点,这意味着 O(logn) 复杂度。 (如 2^h=n 然后 h=log2(n))。

答案-3

对于无向图

现在为什么是 2E。好的!每个节点都有一些连接到它的边缘。(如果没有连接,那么它的度数为零)。现在节点的度数为 n 意味着它有 n 条边连接到它。现在举个例子

1---2
|  /
| /
3
1 has degree=2
2 has degree=2
3 has degree=2
-+-+-+-+-+-+-+-
so sum(deg(v))=2+2+2=6= 2*(no of edges);

为什么会这样?您可以考虑将这种情况建模为握手 b/2 朋友。每个节点表示朋友,每条边表示握手。如果你和 B 握手,那么 B 也会和你握手。所以每个握手(边缘)都会被节点(朋友)考虑两次。

注意:对于有向图,结果将等于E

答案 4

这些链接已经解释过了。检查这个希望一切都会清楚

1.link1

2.link2

让我们在这里说清楚。算法是什么-在cormen中是这样给出的-

MST-PRIM(G, w, r)
 1  for each u ∈ V [G]  ~~~~~~~~~> O(V)
 2       do key[u] ← ∞
 3          π[u] ← NIL
 4  key[r] ← 0
 5   Q ← V [G]          ~~~~~~~~~> If binary min-heap used then heapification's comlexity is O(V)
 6   while Q ≠ Ø        ~~~~~~~~~> Executed |V| times
 7       do u ← EXTRACT-MIN(Q)~~~> O(logV) in binary heap
 8          for each v ∈ Adj[u] ~~~> For each vertex the degree of that vertex times it will loop. So total O(E) times
 9              do if v ∈ Q and w(u, v) < key[v]
10                    then π[v] ← u
11                         key[v] ← w(u, v)~~~> decrease key operation in min-heap(binary) O(logV) times.

所以总复杂度= O(V)+O(VlogV)+O(ElogV) = O((V+E)logV) //VlogV支配V

现在如果所有边的权重都在 1 到 |V| 的范围内那么我们可以只取一个列表数组 A[1..|V|]。现在假设边权重为 1(E1)、3(E2)、2(E3)、5(E4)、7(E5)、3(E6),并且有 7 个顶点和 6 个边。

最初的列表数组

A  [ ] [ ] [ ] [ ] [ ] [ ] [ ] [ ]
    0   1   2   3   4   5   6   7

现在您应用计数排序的方法- 为每个边权重将边放在相应的数组位置,例如

现在遇到边缘 E1 后,数组将是

A  [ ] [E1] [ ] [ ] [ ] [ ]  [ ] [ ]
    0    1    2  3   4    5   6   7

然后是边缘 E2

A  [ ] [E1] [ ] [E2] [ ] [ ] [ ] [ ]
    0   1    2   3    4   5   6   7

所以在遍历所有边之后你会得到

                  E6
                  | 
A  [ ] [E1] [E3] [E2] [ ] [E4] [ ] [E5]
    0    1    2    3   4    5   6   7

现在你可能明白为什么有些回答提到了|V|列出或 |W|此图表中的列表。

现在你可以在 O(V) 时间内从数组中得到所有最小值。 通常,对于各种权重,堆数据结构用于存储边。

但是,在这种情况下,权重是 1 .. |V|所以你可以简单地将边缘存储在|V|中单独的列表,一个用于权重为 1 到 |V| 的边。

要找到权重最低的边,您只需从第一个列表中取一个,除非它是空的,在这种情况下,您从第二个列表中取一个边。

从列表中访问和删除元素是 O(1),您只需从列表中删除最顶部的元素。所以 Prim 的算法将在 O(V*W+E) 中运行。

所以如果 W=O(V) 那么它运行在 O(V^2+E)

如果权重在 1..W 范围内(W=O(1) 常数),那么复杂度同样将是 O(V*W+E)。 ~O(V+E)。

伪代码

在 C 中

结构边缘 { 诠释 u,v,w; } 结构列表节点 { 边 e; 结构列表节点*下一个; }

struct listnode ** A;
A=malloc(sizeof(struct list *)*N);
Intilally all of A[i]=NULL;

A[i]=malloc(sizeof(struct listnode),1);
(*A[i]).e.u=..
(*A[i]).e.v=..
(*A[i]).e.w=..

 create the array of lists don't insert anythiing.
 Then just select a vertex say s
 keep an array visisted[1..|V|]
 visited[s]=true;
 no_of_visited=1;
 recently_visted=s;
 while(all nodes are not visited)   //no_of_visited!=|V|
 {
    insert all edges from s-x (adjacent) in the array of lists.(s is recently visited) provided x is not visited
    get the minimum weight one
    if it is u-w and w is not visited
    {
        visited[w]=true;
        no_of_visited++;
        recently_visited=w;
        insert(u,w) in spanning tree
    }

}

复杂性分析

算法:

让我们的权重在 1..|W| 范围内

Then just select a vertex say s ~~~~~~~~~~> O(1)
 //keep an array visisted[1..|W|]
 for i=1 to |W| 
     visited[i]=false;   ~~~~~~~~~~> O(|W|) 
 visited[s]=true; ~~~~~~~~~~> O(1)
 no_of_visited=1; ~~~~~~~~~~> O(1)
 recently_visted=s; ~~~~~~~~~~~ O(1)
 while(all nodes are not visited) ~~~~~O(V)  //no_of_visited!=|W|
 {
    insert all edges from s-x (adjacent) in the array of lists.(s is recently visited) provided x is not visited ~~~~~O(|E|) altogether
    get the minimum weight one          ~~~~~O(|W|) 
    if it is u-w and w is not visited   --O(1)
    {
        visited[w]=true;               
        no_of_visited++;
        recently_visited=w;
        insert(u,w) in spanning tree    --O(1)
    }

}

所以复杂度=O(|W|)+O(|W|.|V|)+O(|E|)~O(E+VW) 当 W=O(V) 则 T(V,E)= O(E+VV)=O(E+V^2)

【讨论】:

  • 我想我已经理解了你的答案 1,2,3... 但是.. 我不明白我们可以让 Prim 的算法运行多快,假设图中的所有边权重都是范围从 1 到 |V| 的整数。我们可以做什么?我们必须创建一个列表、一个队列还是一个数组?如果是这样,我们能做什么?没看懂……
  • @evinda.:您不需要维护优先级队列,这是基本思想。但是我们不能为大范围的权重做到这一点,因为这将需要更多的列表..高空间要求。这就是我们在广泛的权重范围内使用堆的原因。
  • 我不明白列表数组将包含什么。我们是否创建一个长度为 |V| 的数组? , A[1....|V|] 并在每个位置放置与位置数相等的边数?还是我理解错了?
  • @evinda.: 你必须把边缘放在那里..这意味着你将为边缘选择的 dtata 结构..例如它可能是这样的一对(u,(v,w)) 这是一个有序对,其中 u,v 是顶点,w 是权重。假设边​​是从 u 到 v。那些被保存在该列表数组的每个列表中。现在,每当您删除一条边时,您只需从给定插槽中获取最顶部或最后一条边。插入的情况也是如此。列表部分非常标准。插入和删除。您可能希望将不存在的元素存储在数组的插槽中,这就是实现。
  • 所以你的意思是,在数组 A[1...|V|] 的每个位置,我们都有一个包含三个数字 u,v,w 的列表,我们将数组排序为最后一个元素 w?还是我理解错了?
猜你喜欢
  • 1970-01-01
  • 2010-10-17
  • 2010-12-18
  • 2012-10-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多