Skip to content

Commit 433b8cc

Browse files
committed
added A* algorithm
1 parent 61c4ed9 commit 433b8cc

File tree

3 files changed

+283
-58
lines changed

3 files changed

+283
-58
lines changed

basic_algorithm/graph/README.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@ Ongoing...
88

99
[最小生成树](./mst.md)
1010

11-
[最短路径](./shortest_path.md)
11+
[最短路径](./shortest_path.md)
12+

basic_algorithm/graph/bfs_dfs.md

+63-55
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ class Solution:
144144
def shortestBridge(self, A: List[List[int]]) -> int:
145145

146146
M, N = len(A), len(A[0])
147+
neighors = ((-1, 0), (1, 0), (0, -1), (0, 1))
147148

148149
for i in range(M):
149150
for j in range(N):
@@ -157,67 +158,74 @@ class Solution:
157158
r, c = dfs.pop()
158159
if A[r][c] == 1:
159160
A[r][c] = -1
160-
161-
if r - 1 >= 0:
162-
if A[r - 1][c] == 0: # meet and edge
163-
A[r - 1][c] = -2
164-
bfs.append((r - 1, c))
165-
elif A[r - 1][c] == 1:
166-
dfs.append((r - 1, c))
167-
168-
if r + 1 < M:
169-
if A[r + 1][c] == 0:
170-
A[r + 1][c] = -2
171-
bfs.append((r + 1, c))
172-
elif A[r + 1][c] == 1:
173-
dfs.append((r + 1, c))
174-
175-
if c - 1 >= 0:
176-
if A[r][c - 1] == 0:
177-
A[r][c - 1] = -2
178-
bfs.append((r, c - 1))
179-
elif A[r][c - 1] == 1:
180-
dfs.append((r, c - 1))
181-
182-
if c + 1 < N:
183-
if A[r][c + 1] == 0:
184-
A[r][c + 1] = -2
185-
bfs.append((r, c + 1))
186-
elif A[r][c + 1] == 1:
187-
dfs.append((r, c + 1))
161+
162+
for dr, dc in neighors:
163+
nr, nc = r + dr, c + dc
164+
if 0<= nr < M and 0 <= nc < N:
165+
if A[nr][nc] == 0: # meet and edge
166+
A[nr][nc] = -2
167+
bfs.append((nr, nc))
168+
elif A[nr][nc] == 1:
169+
dfs.append((nr, nc))
170+
188171
flip = 1
189172
while bfs:
190173
num_level = len(bfs)
191174
for _ in range(num_level):
192175
r, c = bfs.popleft()
193176

194-
if r - 1 >= 0:
195-
if A[r - 1][c] == 0:
196-
A[r - 1][c] = -2
197-
bfs.append((r - 1, c))
198-
elif A[r - 1][c] == 1:
199-
return flip
200-
201-
if r + 1 < M:
202-
if A[r + 1][c] == 0:
203-
A[r + 1][c] = -2
204-
bfs.append((r + 1, c))
205-
elif A[r + 1][c] == 1:
206-
return flip
207-
208-
if c - 1 >= 0:
209-
if A[r][c - 1] == 0:
210-
A[r][c - 1] = -2
211-
bfs.append((r, c - 1))
212-
elif A[r][c - 1] == 1:
213-
return flip
214-
215-
if c + 1 < N:
216-
if A[r][c + 1] == 0:
217-
A[r][c + 1] = -2
218-
bfs.append((r, c + 1))
219-
elif A[r][c + 1] == 1:
220-
return flip
177+
for dr, dc in neighors:
178+
nr, nc = r + dr, c + dc
179+
if 0<= nr < M and 0 <= nc < N:
180+
if A[nr][nc] == 0: # meet and edge
181+
A[nr][nc] = -2
182+
bfs.append((nr, nc))
183+
elif A[nr][nc] == 1:
184+
return flip
221185
flip += 1
222186
```
223187

188+
### [sliding-puzzle](https://leetcode-cn.com/problems/sliding-puzzle)
189+
190+
```Python
191+
class Solution:
192+
def slidingPuzzle(self, board: List[List[int]]) -> int:
193+
194+
next_move = {
195+
0: [1, 3],
196+
1: [0, 2, 4],
197+
2: [1, 5],
198+
3: [0, 4],
199+
4: [1, 3, 5],
200+
5: [2, 4]
201+
}
202+
203+
start = tuple(itertools.chain(*board))
204+
target = (1, 2, 3, 4, 5, 0)
205+
206+
if start == target:
207+
return 0
208+
209+
SPT = set([start])
210+
bfs = collections.deque([(start, start.index(0))])
211+
212+
step = 1
213+
while bfs:
214+
num_level = len(bfs)
215+
for _ in range(num_level):
216+
state, idx0 = bfs.popleft()
217+
218+
for next_step in next_move[idx0]:
219+
next_state = list(state)
220+
next_state[idx0], next_state[next_step] = next_state[next_step], next_state[idx0]
221+
next_state = tuple(next_state)
222+
223+
if next_state == target:
224+
return step
225+
226+
if next_state not in SPT:
227+
SPT.add(next_state)
228+
bfs.append((next_state, next_step))
229+
step += 1
230+
return -1
231+
```

basic_algorithm/graph/shortest_path.md

+218-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,118 @@
11
# 最短路径问题
22

3+
## BFS
4+
5+
在处理不带权图的最短路径问题时可以使用 BFS。
6+
7+
### [walls-and-gates](https://leetcode-cn.com/problems/walls-and-gates/)
8+
9+
> 给定一个二维矩阵,矩阵中元素 -1 表示墙或是障碍物,0 表示一扇门,INF (2147483647) 表示一个空的房间。你要给每个空房间位上填上该房间到最近门的距离,如果无法到达门,则填 INF 即可。
10+
11+
**图森面试真题**。典型的多源最短路径问题,将所有源作为 BFS 的第一层即可
12+
13+
```Python
14+
inf = 2147483647
15+
16+
class Solution:
17+
def wallsAndGates(self, rooms: List[List[int]]) -> None:
18+
"""
19+
Do not return anything, modify rooms in-place instead.
20+
"""
21+
22+
if not rooms or not rooms[0]:
23+
return
24+
25+
M, N = len(rooms), len(rooms[0])
26+
27+
bfs = collections.deque([])
28+
29+
for i in range(M):
30+
for j in range(N):
31+
if rooms[i][j] == 0:
32+
bfs.append((i, j))
33+
34+
dist = 1
35+
while bfs:
36+
num_level = len(bfs)
37+
for _ in range(num_level):
38+
r, c = bfs.popleft()
39+
40+
if r - 1 >= 0 and rooms[r - 1][c] == inf:
41+
rooms[r - 1][c] = dist
42+
bfs.append((r - 1, c))
43+
44+
if r + 1 < M and rooms[r + 1][c] == inf:
45+
rooms[r + 1][c] = dist
46+
bfs.append((r + 1, c))
47+
48+
if c - 1 >= 0 and rooms[r][c - 1] == inf:
49+
rooms[r][c - 1] = dist
50+
bfs.append((r, c - 1))
51+
52+
if c + 1 < N and rooms[r][c + 1] == inf:
53+
rooms[r][c + 1] = dist
54+
bfs.append((r, c + 1))
55+
56+
dist += 1
57+
58+
return
59+
```
60+
61+
### [shortest-bridge](https://leetcode-cn.com/problems/shortest-bridge/)
62+
63+
> 在给定的 01 矩阵 A 中,存在两座岛 (岛是由四面相连的 1 形成的一个连通分量)。现在,我们可以将 0 变为 1,以使两座岛连接起来,变成一座岛。返回必须翻转的 0 的最小数目。
64+
65+
**图森面试真题**。思路:DFS 遍历连通分量找边界,从边界开始 BFS找最短路径
66+
67+
```Python
68+
class Solution:
69+
def shortestBridge(self, A: List[List[int]]) -> int:
70+
71+
M, N = len(A), len(A[0])
72+
neighors = ((-1, 0), (1, 0), (0, -1), (0, 1))
73+
74+
for i in range(M):
75+
for j in range(N):
76+
if A[i][j] == 1: # start from a 1
77+
dfs = [(i, j)]
78+
break
79+
80+
bfs = collections.deque([])
81+
82+
while dfs:
83+
r, c = dfs.pop()
84+
if A[r][c] == 1:
85+
A[r][c] = -1
86+
87+
for dr, dc in neighors:
88+
nr, nc = r + dr, c + dc
89+
if 0<= nr < M and 0 <= nc < N:
90+
if A[nr][nc] == 0: # meet and edge
91+
A[nr][nc] = -2
92+
bfs.append((nr, nc))
93+
elif A[nr][nc] == 1:
94+
dfs.append((nr, nc))
95+
96+
flip = 1
97+
while bfs:
98+
num_level = len(bfs)
99+
for _ in range(num_level):
100+
r, c = bfs.popleft()
101+
102+
for dr, dc in neighors:
103+
nr, nc = r + dr, c + dc
104+
if 0<= nr < M and 0 <= nc < N:
105+
if A[nr][nc] == 0: # meet and edge
106+
A[nr][nc] = -2
107+
bfs.append((nr, nc))
108+
elif A[nr][nc] == 1:
109+
return flip
110+
flip += 1
111+
```
112+
3113
## Dijkstra's Algorithm
4114

5-
思想是 greedy 构造 shortest path tree (SPT),每次将当前距离源点最短的不在 SPT 中的结点加入SPT,与构造最小生成树 (MST) 的 Prim's algorithm 非常相似。可以用 priority queue (heap) 实现。
115+
用于求解单源最短路径问题。思想是 greedy 构造 shortest path tree (SPT),每次将当前距离源点最短的不在 SPT 中的结点加入SPT,与构造最小生成树 (MST) 的 Prim's algorithm 非常相似。可以用 priority queue (heap) 实现。
6116

7117
### [network-delay-time](https://leetcode-cn.com/problems/network-delay-time/)
8118

@@ -66,4 +176,110 @@ class Solution:
66176
heapq.heappush(min_heap, (p + price, step, n))
67177

68178
return -1
69-
```
179+
```
180+
181+
## A* Algorithm
182+
183+
当需要求解有目标的最短路径问题时,BFS 或 Dijkstra's algorithm 可能会搜索过多冗余的其他目标从而降低搜索效率,此时可以考虑使用 A* algorithm。原理不展开,有兴趣可以自行搜索。实现上和 Dijkstra’s algorithm 非常相似,只是优先级需要加上一个到目标点距离的估值,这个估值严格小于等于真正的最短距离时保证得到最优解。当 A* algorithm 中的距离估值为 0 时 退化为 BFS 或 Dijkstra’s algorithm。
184+
185+
### [sliding-puzzle](https://leetcode-cn.com/problems/sliding-puzzle)
186+
187+
思路 1:BFS。为了方便对比 A* 算法写成了与其相似的形式。
188+
189+
```Python
190+
class Solution:
191+
def slidingPuzzle(self, board: List[List[int]]) -> int:
192+
193+
next_move = {
194+
0: [1, 3],
195+
1: [0, 2, 4],
196+
2: [1, 5],
197+
3: [0, 4],
198+
4: [1, 3, 5],
199+
5: [2, 4]
200+
}
201+
202+
start = tuple(itertools.chain(*board))
203+
target = (1, 2, 3, 4, 5, 0)
204+
target_wrong = (1, 2, 3, 5, 4, 0)
205+
206+
SPT = set()
207+
bfs = collections.deque([(0, start, start.index(0))])
208+
209+
while bfs:
210+
step, state, idx0 = bfs.popleft()
211+
212+
if state == target:
213+
return step
214+
215+
if state == target_wrong:
216+
return -1
217+
218+
if state not in SPT:
219+
SPT.add(state)
220+
221+
for next_step in next_move[idx0]:
222+
next_state = list(state)
223+
next_state[idx0], next_state[next_step] = next_state[next_step], next_state[idx0]
224+
next_state = tuple(next_state)
225+
226+
if next_state not in SPT:
227+
bfs.append((step + 1, next_state, next_step))
228+
return -1
229+
```
230+
231+
思路 2:A* algorithm
232+
233+
```Python
234+
class Solution:
235+
def slidingPuzzle(self, board: List[List[int]]) -> int:
236+
237+
next_move = {
238+
0: [1, 3],
239+
1: [0, 2, 4],
240+
2: [1, 5],
241+
3: [0, 4],
242+
4: [1, 3, 5],
243+
5: [2, 4]
244+
}
245+
246+
start = tuple(itertools.chain(*board))
247+
target, target_idx = (1, 2, 3, 4, 5, 0), (5, 0, 1, 2, 3, 4)
248+
target_wrong = (1, 2, 3, 5, 4, 0)
249+
250+
@functools.lru_cache(maxsize=None)
251+
def taxicab_dist(x, y):
252+
return abs(x // 3 - y // 3) + abs(x % 3 - y % 3)
253+
254+
def taxicab_sum(state, t_idx):
255+
result = 0
256+
for i, num in enumerate(state):
257+
result += taxicab_dist(i, t_idx[num])
258+
return result
259+
260+
SPT = set()
261+
min_heap = [(0 + taxicab_sum(start, target_idx), 0, start, start.index(0))]
262+
263+
while min_heap:
264+
cur_cost, step, state, idx0 = heapq.heappop(min_heap)
265+
266+
if state == target:
267+
return step
268+
269+
if state == target_wrong:
270+
return -1
271+
272+
if state not in SPT:
273+
SPT.add(state)
274+
275+
for next_step in next_move[idx0]:
276+
next_state = list(state)
277+
next_state[idx0], next_state[next_step] = next_state[next_step], next_state[idx0]
278+
next_state = tuple(next_state)
279+
next_cost = step + 1 + taxicab_sum(next_state, target_idx)
280+
281+
if next_state not in SPT:
282+
heapq.heappush(min_heap, (next_cost, step + 1, next_state, next_step))
283+
return -1
284+
```
285+

0 commit comments

Comments
 (0)