Reputation: 661
I found the time complexity of Prims algorithm everywhere as O((V + E) log V) = E log V. But as we can see the algorithm:
It seems like the time complexity is O(V(log V + E log V)). But if its time complexity is O((V + E) log V). Then the nesting must have to be like this:
But the above nesting is seems to be wrong.
Upvotes: 19
Views: 91215
Reputation: 1453
MST-PRIM(G, w, r)
1 for each u ∈ G.V
2 u.key ← ∞
3 u.π ← NIL
4 r.key ← 0
5 Q ← G.V
6 while Q ≠ Ø
7 u ← EXTRACT-MIN(Q)
8 for each v ∈ G.Adjacent[u]
9 if v ∈ Q and w(u, v) < v.key
10 v.π ← u
11 v.key ← w(u, v)
Using a Binary Heap
The time complexity required for one call to EXTRACT-MIN(Q)
is O(log V)
using a min priority queue. The while loop at line 6 is executing total V times.so EXTRACT-MIN(Q)
is called V
times. So the complexity of EXTRACT-MIN(Q)
is O(V logV)
.
The for
loop at line 8 is executing total 2E
times as length of each adjacency lists is 2E
for an undirected graph. The time required to execute line 11 is O(log v)
by using the DECREASE_KEY
operation on the min heap. Line 11 also executes total 2E
times. So the total time required to execute line 11 is O(2E logV) = O(E logV)
.
The for
loop at line 1 will be executed V
times. Using the procedure to perform lines 1 to 5 will require a complexity of O(V)
.
Total time complexity of MST-PRIM
is the sum of the time complexity required to execute steps 1 through 3 for a total of O((VlogV) + (E logV) + (V)) = O(E logV)
since |E| >= |V|
.
Using a Fibonacci Heap
O(1)
amortized time. Line 11 executes a total of 2E
times. So the total time complexity is O(E)
.So the total time complexity of MST-PRIM
is the sum of executing steps 1 through 3 for a total complexity of O(V logV + E + V)=O(E + V logV)
.
Upvotes: 45
Reputation: 151
The time complexity of Prim's algorithm is O(VlogV + ElogV). It seems like you understand how the VlogV
came to be, so let's skip over that. So where does ElogV
come from? Let's start by looking at Prim's algorithm's source code:
| MST-PRIM(Graph, weights, r)
1 | for each u ∈ Graph.V
2 | u.key ← ∞
3 | u.π ← NIL
4 | r.key ← 0
5 | Q ← Graph.V
6 | while Q ≠ Ø
7 | u ← EXTRACT-MIN(Q)
8 | for each v ∈ Graph.Adj[u]
9 | if v ∈ Q and weights(u, v) < v.key
10| v.π ← u
11| v.key ← weights(u, v)
Lines 8-11 are executed for every element in Q
, and we know that there are V
elements in Q
(representing the set of all vertices). Line 8's loop is iterating through all the neighbors of the currently extracted vertex; we will do the same for the next extracted vertex, and for the one after that. Djistkra's Algorithm does not repeat vertices (because it is a greedy, optimal algorithm), and will have us go through each of the connected vertices eventually, exploring all of their neighbors. In other words, this loop will end up going through all the edges of the graph twice at some point (2E
).
Why twice? Because at some point we come back to a previously explored edge from the other direction, and we can't rule it out until we've actually checked it. Fortunately, that constant 2
is dropped during our time complexity analysis, so the loop is really just doing E
amounts of work.
Why wasn't it V*V
? You might reach that term if you just consider that we have to check each Vertex and its neighbors, and in the worst case graph the number of neighbors approaches V
. Indeed, in a dense graph V*V = E
. But the more accurate description of the work of these two loops is "going through all the edges twice", so we refer to E
instead. It's up to the reader to connect how sparse their graph is with this term's time complexity.
Let's look at a small example graph with 4 vertices:
1--2
|\ |
| \|
3--4
Assume that Q
will give us the nodes in the order 1, 2, 3, and then 4.
The total iterations was 10, which is twice the number of edges (2*5
).
Extracting the minimum and tracking the updated minimum edges (usually done with a Fibonacci Heap, resulting in log(V)
time complexity) occurs inside the loop iterations - the exact mechanisms involve end up needing to occur inside the inner loop enough times that they are controlled by the time complexity of both loops. Therefore, the complete time complexity for this phase of the algorithm is O(2*E*log(V))
. Dropping the constant yields O(E*log(V))
.
Given that the total time complexity of the algorithm is O(VlogV + ElogV)
, we can simplify to O((V+E)logV)
. In a dense graph E > V
, so we can conclude O(ElogV)
.
Upvotes: 5
Reputation: 89
Your idea seems correct. Let's take the complexity as
V(lg(v) + E(lg(v)))
Then notice that in the inner for loop, we are actually going through all the vertices, and not the edge, so let's modify a little to
V(lg(v) + V(lg(v)))
which means
V(lg(v)) + V*V(lg(v))
But for worst case analysis(dense graphs), V*V is roughly equal to number of edges, E
V(lg(v)) + E(lg(v))
(V+E((lg(v))
but since V << E
, hence
E(lg(v))
Upvotes: 8
Reputation: 21
actually as you are saying as for is nested inside while time complexity should be v.E lg V is correct in case of asymptotic analysis. But in cormen they have done amortized analysis thats why it comes out to be (Elogv)
Upvotes: 2