Python实战开发及案例分析(24)—— 图搜索算法

        图搜索算法在计算机科学和人工智能领域中广泛应用,主要用于解决路径查找和优化问题。以下是如何使用Python实现常见图搜索算法的方法及其案例分析。

1. 深度优先搜索(Depth-First Search, DFS)

实现步骤

  1. 使用栈来存储路径。
  2. 访问一个节点后,将其标记为已访问,并将其邻居节点添加到栈中。
  3. 重复上述过程,直到栈为空或找到目标节点。

代码示例

def depth_first_search(graph, start, goal):
    stack = [(start, [start])]
    visited = set()

    while stack:
        (vertex, path) = stack.pop()
        if vertex in visited:
            continue
        visited.add(vertex)
        for neighbor in graph[vertex]:
            if neighbor == goal:
                return path + [neighbor]
            else:
                stack.append((neighbor, path + [neighbor]))
    return None

# 案例分析
graph = {
    'A': ['B', 'C'],
    'B': ['D', 'E'],
    'C': ['F'],
    'D': [],
    'E': ['F'],
    'F': []
}

print(depth_first_search(graph, 'A', 'F'))
# 输出: ['A', 'C', 'F']

2. 广度优先搜索(Breadth-First Search, BFS)

实现步骤

  1. 使用队列来存储路径。
  2. 访问一个节点后,将其标记为已访问,并将其邻居节点添加到队列中。
  3. 重复上述过程,直到队列为空或找到目标节点。

代码示例

from collections import deque

def breadth_first_search(graph, start, goal):
    queue = deque([(start, [start])])
    visited = set()

    while queue:
        (vertex, path) = queue.popleft()
        if vertex in visited:
            continue
        visited.add(vertex)
        for neighbor in graph[vertex]:
            if neighbor == goal:
                return path + [neighbor]
            else:
                queue.append((neighbor, path + [neighbor]))
    return None

# 案例分析
graph = {
    'A': ['B', 'C'],
    'B': ['D', 'E'],
    'C': ['F'],
    'D': [],
    'E': ['F'],
    'F': []
}

print(breadth_first_search(graph, 'A', 'F'))
# 输出: ['A', 'C', 'F']

3. A* 搜索算法

实现步骤

  1. 使用优先队列(通常使用堆)来存储路径,优先级为估计的总路径长度。
  2. 访问一个节点后,将其标记为已访问,并将其邻居节点添加到优先队列中。
  3. 重复上述过程,直到优先队列为空或找到目标节点。

代码示例

import heapq

def heuristic(a, b):
    # 使用曼哈顿距离作为启发函数
    return abs(ord(a) - ord(b))

def a_star_search(graph, start, goal):
    queue = [(0, start, [start])]
    visited = set()

    while queue:
        (cost, vertex, path) = heapq.heappop(queue)
        if vertex in visited:
            continue
        visited.add(vertex)
        if vertex == goal:
            return path
        for neighbor in graph[vertex]:
            if neighbor not in visited:
                total_cost = cost + 1 + heuristic(neighbor, goal)
                heapq.heappush(queue, (total_cost, neighbor, path + [neighbor]))
    return None

# 案例分析
graph = {
    'A': ['B', 'C'],
    'B': ['D', 'E'],
    'C': ['F'],
    'D': [],
    'E': ['F'],
    'F': []
}

print(a_star_search(graph, 'A', 'F'))
# 输出: ['A', 'C', 'F']

4. Dijkstra 算法

        Dijkstra 算法是一种用于查找图中从单个源节点到所有其他节点的最短路径的算法,适用于加权图。

实现步骤

  1. 初始化源节点到自身的距离为0,其他所有节点的距离为无限大。
  2. 使用优先队列(通常使用堆)来存储节点,优先级为当前已知的最短路径长度。
  3. 从优先队列中取出距离最短的节点,更新其邻居的距离,并将更新后的邻居重新加入优先队列。
  4. 重复上述过程,直到队列为空。

代码示例

import heapq

def dijkstra(graph, start):
    queue = [(0, start)]
    distances = {vertex: float('inf') for vertex in graph}
    distances[start] = 0
    visited = set()

    while queue:
        (current_distance, current_vertex) = heapq.heappop(queue)
        if current_vertex in visited:
            continue
        visited.add(current_vertex)
        
        for neighbor, weight in graph[current_vertex].items():
            distance = current_distance + weight
            if distance < distances[neighbor]:
                distances[neighbor] = distance
                heapq.heappush(queue, (distance, neighbor))
    
    return distances

# 案例分析
graph = {
    'A': {'B': 1, 'C': 4},
    'B': {'A': 1, 'C': 2, 'D': 5},
    'C': {'A': 4, 'B': 2, 'D': 1},
    'D': {'B': 5, 'C': 1}
}

print(dijkstra(graph, 'A'))
# 输出: {'A': 0, 'B': 1, 'C': 3, 'D': 4}

5. 贝尔曼-福特算法(Bellman-Ford Algorithm)

        贝尔曼-福特算法适用于加权图,能够处理包含负权边的情况,并且可以检测负权回路。

实现步骤

  1. 初始化源节点到自身的距离为0,其他所有节点的距离为无限大。
  2. 对图中的每条边进行松弛操作,重复 |V|-1 次(|V|为节点数)。
  3. 检查是否存在负权回路。

代码示例

def bellman_ford(graph, start):
    distances = {vertex: float('inf') for vertex in graph}
    distances[start] = 0

    for _ in range(len(graph) - 1):
        for vertex in graph:
            for neighbor, weight in graph[vertex].items():
                if distances[vertex] + weight < distances[neighbor]:
                    distances[neighbor] = distances[vertex] + weight
    
    # 检查负权回路
    for vertex in graph:
        for neighbor, weight in graph[vertex].items():
            if distances[vertex] + weight < distances[neighbor]:
                return "Graph contains a negative-weight cycle"
    
    return distances

# 案例分析
graph = {
    'A': {'B': 1, 'C': 4},
    'B': {'C': -3, 'D': 2},
    'C': {'D': 3},
    'D': {}
}

print(bellman_ford(graph, 'A'))
# 输出: {'A': 0, 'B': 1, 'C': -2, 'D': 0}

6. Floyd-Warshall算法

        Floyd-Warshall算法是一种用于查找加权图中所有节点对之间最短路径的动态规划算法。

实现步骤

  1. 初始化距离矩阵,直接相邻节点的距离为其权重,其它节点之间的距离为无限大。
  2. 使用动态规划方法更新距离矩阵,对于每一对 (i, j),通过中间节点 k 更新最短路径距离。
  3. 重复上述过程,直到考虑所有节点作为中间节点。

代码示例

def floyd_warshall(graph):
    vertices = list(graph.keys())
    distances = {v: {u: float('inf') for u in vertices} for v in vertices}
    
    for v in vertices:
        distances[v][v] = 0
    
    for v in graph:
        for u, weight in graph[v].items():
            distances[v][u] = weight
    
    for k in vertices:
        for i in vertices:
            for j in vertices:
                distances[i][j] = min(distances[i][j], distances[i][k] + distances[k][j])
    
    return distances

# 案例分析
graph = {
    'A': {'B': 3, 'C': 8, 'D': -4},
    'B': {'E': 1, 'D': 7},
    'C': {'B': 4},
    'D': {'C': -5, 'E': 6},
    'E': {'A': 2}
}

print(floyd_warshall(graph))
# 输出:
# {
#     'A': {'A': 0, 'B': 1, 'C': -3, 'D': -4, 'E': -2},
#     'B': {'A': 3, 'B': 0, 'C': -4, 'D': -1, 'E': 1},
#     'C': {'A': 7, 'B': 4, 'C': 0, 'D': 3, 'E': 5},
#     'D': {'A': 2, 'B': -1, 'C': -5, 'D': 0, 'E': 2},
#     'E': {'A': 2, 'B': 3, 'C': -1, 'D': -2, 'E': 0}
# }

7. 双向搜索算法 (Bidirectional Search)

        双向搜索算法是对传统单向搜索算法的一种优化,它同时从起点和终点开始搜索,当两者相遇时结束。这样可以将搜索空间从 O(b^d) 减少到 O(b^(d/2)),其中 b 是分支因子,d 是搜索深度。

实现步骤

  1. 从起点和终点同时开始搜索。
  2. 使用两个队列分别存储从起点和终点的搜索路径。
  3. 每次从两个队列中分别取出一个节点进行扩展,如果两个搜索路径相遇,则找到最短路径。
  4. 重复上述过程,直到两个搜索路径相遇或两个队列都为空。

代码示例

from collections import deque

def bidirectional_search(graph, start, goal):
    if start == goal:
        return [start]
    
    queue_start = deque([(start, [start])])
    queue_goal = deque([(goal, [goal])])
    visited_start = {start: [start]}
    visited_goal = {goal: [goal]}
    
    while queue_start and queue_goal:
        if queue_start:
            (vertex_start, path_start) = queue_start.popleft()
            for neighbor in graph[vertex_start]:
                if neighbor not in visited_start:
                    new_path = path_start + [neighbor]
                    visited_start[neighbor] = new_path
                    queue_start.append((neighbor, new_path))
                    if neighbor in visited_goal:
                        return new_path + visited_goal[neighbor][::-1][1:]
        
        if queue_goal:
            (vertex_goal, path_goal) = queue_goal.popleft()
            for neighbor in graph[vertex_goal]:
                if neighbor not in visited_goal:
                    new_path = path_goal + [neighbor]
                    visited_goal[neighbor] = new_path
                    queue_goal.append((neighbor, new_path))
                    if neighbor in visited_start:
                        return visited_start[neighbor] + new_path[::-1][1:]
    
    return None

# 案例分析
graph = {
    'A': ['B', 'C'],
    'B': ['D', 'E'],
    'C': ['F'],
    'D': [],
    'E': ['F'],
    'F': []
}

print(bidirectional_search(graph, 'A', 'F'))
# 输出: ['A', 'C', 'F']

8. 基于优先级队列的Uniform-Cost Search (UCS)

        Uniform-Cost Search (UCS) 是一种用于加权图的图搜索算法,它使用优先级队列来扩展代价最小的节点,类似于Dijkstra算法。

实现步骤

  1. 初始化源节点到自身的距离为0,其他所有节点的距离为无限大。
  2. 使用优先队列(通常使用堆)来存储节点,优先级为当前已知的最短路径长度。
  3. 从优先队列中取出距离最短的节点,更新其邻居的距离,并将更新后的邻居重新加入优先队列。
  4. 重复上述过程,直到找到目标节点或优先队列为空。

代码示例

import heapq

def uniform_cost_search(graph, start, goal):
    queue = [(0, start, [start])]
    visited = set()

    while queue:
        (cost, vertex, path) = heapq.heappop(queue)
        if vertex in visited:
            continue
        visited.add(vertex)
        if vertex == goal:
            return path
        for neighbor, weight in graph[vertex].items():
            if neighbor not in visited:
                heapq.heappush(queue, (cost + weight, neighbor, path + [neighbor]))
    return None

# 案例分析
graph = {
    'A': {'B': 1, 'C': 4},
    'B': {'A': 1, 'C': 2, 'D': 5},
    'C': {'A': 4, 'B': 2, 'D': 1},
    'D': {'B': 5, 'C': 1}
}

print(uniform_cost_search(graph, 'A', 'D'))
# 输出: ['A', 'B', 'C', 'D']

9. 最小生成树算法 (Minimum Spanning Tree)

        最小生成树(MST)是一棵子树,它包括图中的所有节点,并使得所有边的权重之和最小。常见的算法有Kruskal和Prim。

Kruskal算法

        Kruskal算法通过逐渐添加边来构建最小生成树,直到包含所有节点。使用并查集(Union-Find)来检测是否形成环。

代码示例

class UnionFind:
    def __init__(self, n):
        self.parent = list(range(n))
        self.rank = [0] * n

    def find(self, u):
        if self.parent[u] != u:
            self.parent[u] = self.find(self.parent[u])
        return self.parent[u]

    def union(self, u, v):
        root_u = self.find(u)
        root_v = self.find(v)
        if root_u != root_v:
            if self.rank[root_u] > self.rank[root_v]:
                self.parent[root_v] = root_u
            elif self.rank[root_u] < self.rank[root_v]:
                self.parent[root_u] = root_v
            else:
                self.parent[root_v] = root_u
                self.rank[root_u] += 1

def kruskal(graph):
    edges = [(weight, u, v) for u in graph for v, weight in graph[u].items()]
    edges.sort()
    uf = UnionFind(len(graph))
    mst = []
    total_weight = 0

    for weight, u, v in edges:
        if uf.find(ord(u) - ord('A')) != uf.find(ord(v) - ord('A')):
            uf.union(ord(u) - ord('A'), ord(v) - ord('A'))
            mst.append((u, v))
            total_weight += weight
    
    return mst, total_weight

# 案例分析
graph = {
    'A': {'B': 1, 'C': 4},
    'B': {'A': 1, 'C': 2, 'D': 5},
    'C': {'A': 4, 'B': 2, 'D': 1},
    'D': {'B': 5, 'C': 1}
}

print(kruskal(graph))
# 输出: ([('A', 'B'), ('C', 'D'), ('B', 'C')], 4)

Prim算法

        Prim算法从任意节点开始,每次选择代价最小的边,扩展树的一部分,直到包含所有节点。

代码示例

import heapq

def prim(graph):
    start = next(iter(graph))
    visited = set([start])
    edges = [(weight, start, neighbor) for neighbor, weight in graph[start].items()]
    heapq.heapify(edges)
    mst = []
    total_weight = 0

    while edges:
        weight, u, v = heapq.heappop(edges)
        if v not in visited:
            visited.add(v)
            mst.append((u, v))
            total_weight += weight
            for neighbor, weight in graph[v].items():
                if neighbor not in visited:
                    heapq.heappush(edges, (weight, v, neighbor))
    
    return mst, total_weight

# 案例分析
graph = {
    'A': {'B': 1, 'C': 4},
    'B': {'A': 1, 'C': 2, 'D': 5},
    'C': {'A': 4, 'B': 2, 'D': 1},
    'D': {'B': 5, 'C': 1}
}

print(prim(graph))
# 输出: ([('A', 'B'), ('B', 'C'), ('C', 'D')], 4)

        这些算法涵盖了广泛的图搜索和路径查找需求,每种算法都有其特定的应用场景和优缺点。了解和掌握这些算法,可以帮助更好地解决实际问题。

  • 19
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

贾贾乾杯

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值