数据结构图的遍历:bfs、dfs和最短路径算法:Dijkstra、Floyd的Python实现

代码的主要实现内容有:

  • 图的表示
  • 图的遍历
    • 广度优先遍历
    • 深度优先遍历
  • 最短路径算法
    • 单点最短路径算法:Dijkstra
    • 多点最短路径算法:Floyd
  • 拓扑排序
import copy

class Edge(object):
  def __init__(self, node, weight, name=""):
    self.node = node
    self.weight = weight
    self.name = name
    self.node.in_degree += 1

class Node(object):
  def __init__(self, name=""):
    self.edges = []
    self.name = name
    self.in_degree = 0

  def add_edge(self, edge):
    self.edges.append(edge)

class Graph(object):
  def __init__(self, nodes: list = None, name=""):
    self.name = name
    self.name_of_nodes = set()
    self.__index = 0
    if nodes is None:
      self.nodes = []
    else:
      for node in nodes:
        self.check_node_name(node)
      self.nodes = nodes

  # 检查节点名称是否唯一
  def check_node_name(self, node):
    if node.name in self.name_of_nodes:
      raise ValueError("Node name must be unique.")
    if node.name == "":
      node.name = str(self.__index)
      self.__index += 1
    self.name_of_nodes.add(node.name)

  @staticmethod
  def from_adjacency_matrix(matrix: list, name=""):
    nodes = [Node(name=str(i)) for i in range(len(matrix))]
    for i in range(len(matrix)):
      for j in range(len(matrix[i])):
        if matrix[i][j] != 0:
          nodes[i].add_edge(Edge(nodes[j], matrix[i][j]))
    return Graph(nodes, name)

  def add_node(self, node):
    self.check_node_name(node)
    self.nodes.append(node)

  def add_edge(self, node1, node2, weight=1, is_directed=False):
    node1.add_edge(Edge(node2, weight)) # node1 -> node2
    if not is_directed:
      node2.add_edge(Edge(node1, weight)) # node2 -> node1
  
  # 广度优先搜索
  @staticmethod
  def bfs(graph, start_node: Node):
    nodes = []
    queue = [start_node]
    visited = set()
    while queue:
      node = queue.pop(0)
      if node not in visited:
        nodes.append(node)
        visited.add(node)
        for edge in node.edges:
          queue.append(edge.node)
    return nodes
  
  # 深度优先搜索
  @staticmethod
  def dfs(graph, start_node: Node):
    nodes = []
    stack = [start_node]
    visited = set()
    while stack:
      node = stack.pop()
      if node not in visited:
        nodes.append(node)
        visited.add(node)
        for edge in node.edges:
          stack.append(edge.node)
    return nodes
  
  # 最短路径,Dijkstra算法
  @staticmethod
  def dijkstra(graph, start_node: Node, end_node: Node):
    s = [start_node] # 已经找到最短路径的节点集合
    v = set(graph.nodes) # 未找到最短路径的节点集合
    v.remove(start_node)
    d = {node: float("inf") for node in graph.nodes} # 起点到各个节点的最短路径长度
    d[start_node] = 0
    p = {node: None for node in graph.nodes} # 起点到各个节点的最短路径上的前一个节点

    while v:
      curr = s[-1]
      for edge in curr.edges:
        if edge.node in v and d[curr] + edge.weight < d[edge.node]:
          d[edge.node] = d[curr] + edge.weight # 更新最短路径长度
          p[edge.node] = curr  # 更新前一个节点

      # 从v中找到最短路径的节点
      min_node = None
      for node in v:
        if min_node is None:
          min_node = node
        elif d[node] < d[min_node]:
          min_node = node
      s.append(min_node) # 加入s
      v.remove(min_node) # 从v中移除

    path = [end_node] # 最短路径, 从终点开始, 往前找
    while p[end_node] is not None:
      path.append(p[end_node])
      end_node = p[end_node]
    path.reverse()
    return path, d[path[-1]]

  # Floyd算法,求任意两点之间的最短路径
  @staticmethod
  def floyd(graph):
    # 初始化,d[i][j]表示从i到j的最短路径长度,p[i][j]表示从i到j的最短路径上的前一个节点
    d = {node: {node2: 0 if node == node2 else float("inf") for node2 in graph.nodes} for node in graph.nodes}
    p = {node: {node2: None for node2 in graph.nodes} for node in graph.nodes}
    for node in graph.nodes:
      for edge in node.edges:
        d[node][edge.node] = edge.weight
        p[node][edge.node] = node
    
    # 计算最短路径长度和前一个节点,满足d[i][j] = min(d[i][j], d[i][k] + d[k][j])
    for k in graph.nodes:
      for i in graph.nodes:
        for j in graph.nodes:
          if d[i][k] + d[k][j] < d[i][j]:
            d[i][j] = d[i][k] + d[k][j]
            p[i][j] = p[k][j]
    return d, p
  
  # 计算最短路径中的节点顺序
  @staticmethod
  def get_path(p, start_node, end_node):
    path = [end_node]
    while p[start_node][end_node] is not None:
      path.append(p[start_node][end_node])
      end_node = p[start_node][end_node]
    path.reverse()
    return path
  
  # 拓扑排序
  @staticmethod
  def topological_sort(graph):
    g_nodes = [copy.deepcopy(node) for node in graph.nodes]
    nodes = []
    queue = [node for node in g_nodes if node.in_degree == 0]
    while queue:
      node = queue.pop(0)
      nodes.append(node)
      for edge in node.edges:
        edge.node.in_degree -= 1
        if edge.node.in_degree == 0:
          queue.append(edge.node)
    return nodes



if __name__ == "__main__":
  # 测试最短路径,不同权值的边
  matrix = [
    [0,9,0,3,0],
    [0,0,7,0,1],
    [0,0,0,0,6],
    [0,0,0,0,8],
    [0,0,0,0,0]
  ]
  graph = Graph.from_adjacency_matrix(matrix)
  start_node = graph.nodes[0]
  end_node = graph.nodes[4]
  path, d = Graph.dijkstra(graph, start_node, end_node)

  print("dijkstra:\n" + " -> ".join([node.name for node in path]))
  print(d)

  x, y = Graph.floyd(graph)
  print("floyd:\n" + " -> ".join([node.name for node in Graph.get_path(y, start_node, end_node)]))
  print(x[start_node][end_node])
  # 测试bfs, dfs
  n = Graph.bfs(graph, start_node)
  print("bfs:\n" + " -> ".join([node.name for node in n]))
  n = Graph.dfs(graph, start_node)
  print("dfs:\n" + " -> ".join([node.name for node in n]))

  # 测试拓扑排序
  z = Graph.topological_sort(graph)
  print("topological sort:\n" + " -> ".join([node.name for node in z]))
如果发现查到的最短路径不全,可能是由于网络中存在权重为负或者结构非连通导致某些路径缺失。这里提供几种可能的改进策略: 1. **检查数据**:确保读取的数据没有遗漏或错误,特别是边缘的方向和是否存在。有时,数据文件可能不完整或者格式错误。 2. **网络结构**:确认是否为强连通(即任意两个节点都通过路径相连),如果不是,那就不可能找到从所有节点到所有其他节点的所有最短路径。 3. **深度优先搜索(DFS)或广度优先搜索(BFS)**:如果你确定网络是连通的,但是短路径算法可能没有返回所有路径,可以尝试使用这两种搜索方法。例如,将DFS用于获取单源最短路径,然后遍历所有节点作为源节点运行DFS。 4. **修改算法**:如果需要找到所有可能的最短路径,可以使用`all_pairs_shortest_paths`函数,如`nx.all_pairs_dijkstra_path(graph)`或`nx.all_pairs_bellman_ford(graph)`,但这会带来更高的计算复杂度。 5. **剪枝策略**:对于大型网络,考虑采用启发式算法或近似方法,比如A*搜索,它可以在实际应用中减少搜索空间,但可能不是最短路径。 6. **并行化**:对于计算密集型任务,考虑使用多线程或多进程来加速搜索过程。 在修改代码时,你需要根据上述策略选择合适的优化方案。例如,如果是在使用`shortest_path`之后的问题,你可以尝试使用`all_pairs_...`函数替换它: ```python from collections import defaultdict all_shortest_paths = defaultdict(list) # 使用all_pairs_shortest_paths for node_i in nodes: for node_j in nodes: paths = nx.all_shortest_paths(graph, source=node_i, target=node_j) for path in paths: all_shortest_paths[(node_i, node_j)].append(list(map(str, path))) # 输出结果 for key, value in all_shortest_paths.items(): print(key, ":", value) ``` 这将会生成所有节点对之间的所有最短路径。记得更新`find_shortest_paths`函数,使其不再单独使用`shortest_path`。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值