一、问题描述
印刷电路板将布线区域划分成个方格阵列。精确的电路布线问题要求确定连接方格的中点到方格的中点的最短布线方案。在布线时,电路只能沿直线或直角布线,为了避免线路相交,已布了线的方格做了封锁标记,其他线路不允许穿过被封锁的方格。
如下图所示:
二、解法一:广度优先搜索(BFS)
(一)算法思路
- 将起始方格的中点作为起始节点,放入队列中。
- 从队列中取出一个节点,检查是否为目标方格的中点。如果是,则找到最短路径,算法结束。如果不是,则检查该节点周围的四个相邻方格(上、下、左、右)的中点,看它们是否在布线区域内,是否未被封锁,并且是否尚未被访问过。如果满足这些条件,则将这些相邻节点加入队列,并标记为已访问。
- 重复步骤 2,直到找到目标节点或队列为空。
(二)代码实现(Python)
from collections import deque
def shortest_path_bfs(m, n, a, b, blocked):
visited = [[False] * n for _ in range(m)]
queue = deque()
queue.append((a[0] + 0.5, a[1] + 0.5))
visited[a[0]][a[1]] = True
parent = {}
while queue:
x, y = queue.popleft()
if x == b[0] + 0.5 and y == b[1] + 0.5:
path = []
while (x, y)!= (a[0] + 0.5, a[1] + 0.5):
path.append((int(x), int(y)))
x, y = parent[(x, y)]
path.append((a[0] + 0.5, a[1] + 0.5))
return path[::-1]
for dx, dy in [(-0.5, 0), (0.5, 0), (0, -0.5), (0, 0.5)]:
new_x, new_y = x + dx, y + dy
if 0 <= new_x < m and 0 <= new_y < n and not blocked[int(new_x)][int(new_y)] and not visited[int(new_x)][int(new_y)]:
queue.append((new_x, new_y))
visited[int(new_x)][int(new_y)] = True
parent[(new_x, new_y)] = (x, y)
return None
(三)时间复杂度和空间复杂度分析
- 时间复杂度:在最坏的情况下,需要遍历整个布线区域,即O(mn)。对于每个节点,最多需要检查四个相邻节点,因此时间复杂度为O(4mn)=O(mn)。
- 空间复杂度:需要使用一个二维布尔数组来记录每个方格是否被访问过,空间复杂度为O(mn)。队列中最多可能存储O(mn)个节点。还需要使用一个字典来记录每个节点的父节点,空间复杂度为O(mn)。因此,总的空间复杂度为O(mn)。
三、解法二:动态规划
(一)算法思路
- 创建一个二维数组,其中dp[i][j]表示从方格a的中点到方格(i,j)的中点的最短距离。
- 初始化dp数组,将dpa[0]a[1]设为 0,其他位置设为无穷大。
- 遍历整个布线区域,对于每个方格(i,j),如果它未被封锁,则更新的值为其相邻方格(上、下、左、右)的最短距离加上 1。
- 最终,dp[b[0]b[1]]即为从方格的中点到方格的中点的最短距离。如果dp[b[0]b[1]]为无穷大,则说明不存在从方格a到方格b的路径。
import math
def shortest_path_dp(m, n, a, b, blocked):
dp = [[math.inf] * n for _ in range(m)]
dp[a[0]][a[1]] = 0
for i in range(m):
for j in range(n):
if not blocked[i][j]:
if i > 0 and not blocked[i - 1][j]:
dp[i][j] = min(dp[i][j], dp[i - 1][j] + 1)
if i < m - 1 and not blocked[i + 1][j]:
dp[i][j] = min(dp[i][j], dp[i + 1][j] + 1)
if j > 0 and not blocked[i][j - 1]:
dp[i][j] = min(dp[i][j], dp[i][j - 1] + 1)
if j < n - 1 and not blocked[i][j + 1]:
dp[i][j] = min(dp[i][j], dp[i][j + 1] + 1)
return dp[b[0]][b[1]] if dp[b[0]][b[1]]!= math.inf else None
(三)时间复杂度和空间复杂度分析
- 时间复杂度:需要遍历整个布线区域两次,一次用于初始化dp数组,一次用于更新dp数组的值。因此,时间复杂度为O(mn)。
- 空间复杂度:需要创建一个二维数组dp,空间复杂度为O(mn)
四、解法三:A * 算法
(一)算法思路
- A算法是一种启发式搜索算法,它结合了贪心算法和 Dijkstra 算法的优点。在印刷电路板布线问题中,我们可以使用 A算法来寻找从方格的中点到方格的中点的最短路径。
- 定义一个评估函数f(n),它由两部分组成:从起始节点到当前节点n的实际代价g(n)和从当前节点n到目标节点的预估代价h(n)。在这个问题中,我们可以使用曼哈顿距离作为预估代价h(n)。
- 从起始节点开始,将其放入优先队列中。每次从优先队列中取出f(n)值最小的节点进行扩展,检查其周围的四个相邻方格(上、下、左、右)的中点,计算它们的f(n)值并放入优先队列中。
- 重复步骤 3,直到找到目标节点或优先队列为空。如果队列为空,则说明不存在从起始节点到目标节点的路径。
(二)代码实现(Python)
import heapq
def heuristic(a, b):
return abs(a[0] - b[0]) + abs(a[1] - b[1])
def shortest_path_astar(m, n, a, b, blocked):
visited = [[False] * n for _ in range(m)]
queue = [(0, 0, a[0] + 0.5, a[1] + 0.5)]
while queue:
f, g, x, y = heapq.heappop(queue)
if x == b[0] + 0.5 and y == b[1] + 0.5:
return g
visited[int(x)][int(y)] = True
for dx, dy in [(-0.5, 0), (0.5, 0), (0, -0.5), (0, 0.5)]:
new_x, new_y = x + dx, y + dy
if 0 <= new_x < m and 0 <= new_y < n and not blocked[int(new_x)][int(new_y)] and not visited[int(new_x)][int(new_y)]:
new_g = g + 1
new_h = heuristic((new_x, new_y), (b[0] + 0.5, b[1] + 0.5))
new_f = new_g + new_h
heapq.heappush(queue, (new_f, new_g, new_x, new_y))
return None
三)时间复杂度和空间复杂度分析
- 时间复杂度:时间复杂度取决于优先队列中的节点数量和每次从队列中取出节点的时间。在最坏的情况下,可能需要遍历整个布线区域,即O(mn)。但是由于 A * 算法具有一定的启发式性质,通常可以更快地找到目标节点。每次从优先队列中取出节点和插入节点的时间复杂度为O(logN),其中N是优先队列中的节点数量。
- 空间复杂度:需要使用一个二维布尔数组来记录每个方格是否被访问过,优先队列中最多可能存储O(mn)个节点。因此,总的空间复杂度为O(mn)。
五、方法对比总结
方法 | 优点 | 缺点 | 时间复杂度 | 空间复杂度 |
---|---|---|---|---|
广度优先搜索(BFS) | 能够找到最短路径,思路直观。 | 在大规模问题上可能效率较低。 | O(mn) | O(mn) |
动态规划 | 适用于求解具有最优子结构性质的问题。 | 需要额外的空间存储中间结果。 | O(mn) | O(mn) |
A * 算法 | 结合了贪心算法和 Dijkstra 算法的优点,通常可以更快地找到目标节点。 | 需要定义合适的预估代价函数,否则可能影响算法的性能。 | 取决于优先队列中的节点数量,通常接近O(mn)。 | O(mn) |
通过对比可以看出,三种方法在时间复杂度和空间复杂度上基本相同。在实际应用中,可以根据问题的特点和需求选择合适的方法。如果需要找到具体的最短路径,可以选择 BFS 或 A算法;如果只需要知道最短距离,可以选择动态规划。同时,A算法在一些情况下可能会比 BFS 更快地找到目标节点,但需要定义合适的预估代价函数。