Skip to content

Commit 834856e

Browse files
committed
Added lazy delete with heapq
1 parent f45aa86 commit 834856e

File tree

1 file changed

+40
-31
lines changed

1 file changed

+40
-31
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,33 @@
11
# 1135. Connecting Cities With Minimum Cost
22

3-
## Prim's Algorithm Solution
4-
- Run-time: O(N^2)
5-
- Space: O(N)
6-
- N = Number of cities
3+
## Prim's Algorithm Solution with Heaps
4+
- Run-time: O((V + E)logV)
5+
- Space: O(V)
6+
- V = Vertices
7+
- E = Edges
78

8-
This is question requires a minimum spanning tree (MST) algorithm.
9-
I choose to use Prim's algorithm because it is similar to Dijkstra's algorithm over Kruskal's algorithm, both are greedy algorithms.
10-
Knowing one will help you in learning the other.
11-
12-
The idea with Prim's is to utilize a heap of vertices and an adjacent list.
13-
The resulting MST cannot produce a cycle due to the method of selecting an edge of an unvisited vertex.
14-
To select an edge, we use the heap sorted by the cost or weight of the edge.
15-
Each time we pop from the heap, we mark that vertex as visited and add it's neighboring vertices' edges to the heap.
9+
This question requires a minimum spanning tree (MST) algorithm.
10+
I choose to use Prim's algorithm over Kruskal's algorithm because it is similar to Dijkstra's algorithm, both are greedy algorithms.
11+
Knowing one will help you learn the other.
1612

1713
The pseudocode could be represented as such:
18-
1. Select an arbitrary vertex to start with and add its neighboring vertices to the heap.
19-
2. Mark that arbitrary vertex as visited.
20-
3. While there are unvisited vertices:
21-
- Select the edge with the min weight of an unvisited vertex from top of heap.
14+
1. Select an arbitrary vertex to start with and add it to the heap.
15+
2. While the heap isn't empty:
16+
- Select the min edge/weight of an unvisited vertex from top of heap.
2217
- Add the selected vertex to the visited vertices.
23-
- Add selected vertex's neighboring edges of unvisited vertices into the heap.
18+
- Take note of the cost or path taken.
19+
- Add/Update selected vertex's neighboring edges/weights of unvisited vertices into the heap.
2420

2521
As of the time of writing this, Python 3.7 doesn't have an adaptable priority queue implementation.
26-
So this is why this version of Prim's runs at O(N^2) time.
27-
This example is more of a lazy heap implementation.
28-
If we were to use an adaptable priority queue, we would see O(MlogN) run-time.
29-
This is because we will only have at most N cities in the heap, no duplicates.
30-
There would be a feature to update a specific city's cost in the heap and bubble up and down the heap to maintain the heap property.
31-
With an adaptable priority queue, there would be no need to use a visited set.
32-
22+
If there was a real adaptable priority queue, there would be a feature to update a specific city's cost in the heap and bubble up and down the heap to maintain the heap property.
3323
To read more about adaptable priority queues, check out [Data Structures and Algorithms in Python 1st Edition](https://www.amazon.com/Structures-Algorithms-Python-Michael-Goodrich/dp/1118290275).
3424

25+
This example is more of a lazy heap implementation, looking at the heapq documentation, they show an implementation of a lazy adaptable priority queue.
26+
This will help us to get O((V + E)logV) run-time.
27+
Without performing the lazy delete technique with a heaq, we would get O(V^2(V + E)logV) run-time.
28+
That is because in a undirected dense graph, we would be adding duplicate entries of the same nodes into the heap.
29+
There are a lot of incorrect implementations of Prim's Algorithm using heapq that do not perform lazy deletes out there, so be warned.
30+
3531
```
3632
from collections import defaultdict
3733
@@ -47,19 +43,32 @@ class Solution:
4743
adj_list[city2].append((city1, cost))
4844
return adj_list
4945
46+
def add_city(new_city, new_cost):
47+
new_node = [new_cost, new_city, False]
48+
city_to_heap_node[new_city] = new_node
49+
heapq.heappush(min_heap, new_node)
50+
5051
adj_list = create_adj_list()
51-
min_heap = list([(0, 1)]) # start at city #1
52-
visited = set()
53-
min_cost = 0
54-
while min_heap and len(visited) != N:
55-
cost, city = heapq.heappop(min_heap)
56-
if city in visited: # skip this city
52+
start_node = [0, 1, False] # cost, city, remove
53+
min_heap = list([start_node]) # start at city #1
54+
city_to_heap_node = dict({1 : start_node})
55+
visited, min_cost = set(), 0
56+
while min_heap:
57+
cost, city, remove = heapq.heappop(min_heap)
58+
if remove: # lazy delete
5759
continue
5860
visited.add(city)
5961
min_cost += cost
6062
for next_city, next_cost in adj_list[city]:
6163
if next_city in visited:
6264
continue
63-
heapq.heappush(min_heap, (next_cost, next_city))
65+
elif next_city in city_to_heap_node:
66+
heap_node = city_to_heap_node[next_city]
67+
if next_cost < heap_node[0]: # lower cost?
68+
heap_node[2] = True # lazy delete
69+
add_city(next_city, next_cost)
70+
else:
71+
add_city(next_city, next_cost)
72+
city_to_heap_node.pop(city)
6473
return min_cost if len(visited) == N else -1
6574
```

0 commit comments

Comments
 (0)