@@ -891,3 +891,318 @@ def fun(num, list=None):
891891x = 9* 5
892892print(fun(x))# [3, 3, 5]
893893` ` `
894+
895+ # ## 最少换乘
896+
897+ # ### 描述
898+
899+ 假设你要从起点去往终点,路上有多个** 单向** 站点,怎么设计一个算法,找到最少换乘的路线?
900+
901+ 这是一个经典的图论问题,可以用广度优先搜索(BFS)来解决。每条路线可以看作是一条边,换乘次数就是路径的边数。
902+
903+ # ### 思路
904+
905+ 1. 找到起点能去的所有节点
906+ 2. 检查可去节点是否包含终点
907+ 3. 如果不包含则继续前往这些节点的可去节点
908+ 4. 使用广度优先搜索(BFS)确保找到的是最少换乘路线
909+ 5. 使用队列记录当前层级的节点,逐层扩展直到找到终点
910+
911+ # ### 题解
912+
913+ ` ` ` python showLineNumbers
914+
915+ def min_transfers(edges, start, end):
916+ " " "
917+ 找到从起点到终点的最少换乘路线
918+
919+ :param edges: 图的邻接表表示,edges[u] = [v1, v2, ...] 表示从u可以到达的站点
920+ :param start: 起点
921+ :param end: 终点
922+ :return: 最少换乘次数和路径,如果无法到达返回-1和None
923+ " " "
924+ if start == end:
925+ return 0, [start]
926+
927+ # BFS队列,存储(当前站点, 换乘次数, 路径)
928+ queue = [(start, 0, [start])]
929+ visited = {start} # 已访问的站点
930+
931+ # 只要队列不为空,就继续遍历
932+ while queue:
933+ # 获取队列中最左侧(最左边是第一个进入队列的元素)元素,并从队列中删除
934+ # 当前站点, 换乘次数, 路径
935+ current, transfers, path = queue.pop(0)
936+
937+ # 遍历所有可达的下一个站点(即当前站点可去的站点)
938+ # 如果当前站点没有下一个站点,则跳过
939+ # 如果当前站点有下一个站点,则将下一个站点加入队列
940+ for next_station in edges.get(current, []):
941+ if next_station == end:
942+ # 找到终点
943+ return transfers + 1, path + [end]
944+ # 如果下一个站点没有被访问过,则将下一个站点加入已访问集合,并加入队列
945+ # 这一步可以避免重复访问同一个站点,提升算法效率
946+ if next_station not in visited:
947+ # 将下一个站点加入已访问集合
948+ visited.add(next_station)
949+ # 将下一个站点加入队列
950+ # 换乘次数加1,路径加上下一个站点
951+ queue.append(( next_station, transfers + 1 , path + [next_station]))
952+
953+ # 无法到达终点
954+ return -1, None
955+
956+ # 测试数据
957+ if __name__ == ' __main__' :
958+ # 示例:站点A->B->D, A->C->D, A->D
959+ edges = {
960+ ' A' : [' B' , ' C' ],
961+ ' B' : [' C' ],
962+ ' C' : [' X' ],
963+ ' X' : [],
964+ ' D' : []
965+ }
966+
967+ transfers, path = min_transfers(edges, ' A' , ' D' )
968+ print(f" 最少换乘次数: {transfers}" )
969+ print(path) # 最少换乘次数: 1, 路径: A -> D
970+ ` ` `
971+
972+ # ## 最少价格路径
973+
974+ # ### 描述
975+
976+ 给定一个带权有向图,每条边都有一个价格(权重),请找到从起点到终点的** 价格总和最小** 的路径。
977+
978+ 这是一个典型的** 单源最短路径** 问题,可以使用Dijkstra算法来解决(假设所有边权非负)。
979+
980+ # ### 思路
981+
982+ 1. 初始化起点的距离为0,其他所有节点距离为无穷大
983+ 2. 找到起点能去的所有节点,计算到达这些节点的价格
984+ 3. 选择价格最小的节点作为下一个访问节点
985+ 4. 检查该节点的可去节点,更新到达这些节点的最少价格
986+ 5. 重复步骤3-4,直到访问到终点或所有可达节点都访问完毕
987+ 6. 使用优先队列(堆)来快速找到价格最小的节点
988+
989+ # ### 题解
990+
991+ ` ` ` python showLineNumbers
992+ def dijkstra(graph, start, end):
993+ " " "
994+ 使用Dijkstra算法找到从起点到终点的最少价格路径
995+
996+ :param graph: 图的邻接表表示,graph[u] = [(v1, weight1), (v2, weight2), ...]
997+ :param start: 起点
998+ :param end: 终点
999+ :return: 最少价格和路径,如果无法到达返回-1和None
1000+ " " "
1001+ # 初始化节点信息字典:每个节点包含价格、前驱节点等信息
1002+ nodes = {}
1003+ processed = set () # 已处理的节点集合
1004+
1005+ # 获取所有节点
1006+ # 1. 获取所有节点放入 集合,集合具有去重的特点
1007+ all_nodes = set(graph.keys ())
1008+ # 2. 获取所有邻居节点
1009+ for neighbors in graph.values ():
1010+ for neighbor, _ in neighbors:
1011+ all_nodes.add(neighbor) # 将邻居节点加入所有节点集合
1012+
1013+ # 步骤1:初始化起点的距离为0,其他所有节点距离为无穷大
1014+ for node in all_nodes:
1015+ nodes[node] = {
1016+ ' price' : 0 if node == start else float(' inf' ),
1017+ ' previous' : None # 前一个节点
1018+ }
1019+ " " "
1020+ all_nodes = {'A', 'B', 'C', 'D'}
1021+
1022+ nodes = {'A': {'price': 0, 'previous': None},
1023+ 'D': {'price': inf, 'previous': None},
1024+ 'C': {'price': inf, 'previous': None},
1025+ 'B': {'price': inf, 'previous': None}}
1026+ " " "
1027+
1028+ def find_lowest_price_node ():
1029+ " " " 在所有未处理的节点中,找到价格最小的节点" " "
1030+ lowest_price = float(' inf' )
1031+ lowest_price_node = None
1032+ for node_name, node_info in nodes.items ():
1033+ # 依次取出每个节点,并检查其价格是否小于最低价格
1034+ # 初始选择中,除了起点是0,其他都是无穷大,所以一定会选择起点
1035+ if node_name not in processed: # 只从未处理的节点中查找
1036+ cost = node_info[' price' ]
1037+ if cost < lowest_price:
1038+ lowest_price = cost
1039+ lowest_price_node = node_name
1040+ return lowest_price_node
1041+
1042+ # 步骤3-5:重复选择价格最小的节点,更新其邻居节点的价格
1043+ node = find_lowest_price_node () # 在所有未处理的节点中,找到价格最小的节点
1044+
1045+ while node is not None: # 只要存在未处理的节点,就继续循环
1046+
1047+ # 如果到达终点,可以提前结束(可选优化)
1048+ if node == end:
1049+ break
1050+
1051+ price = nodes[node][' price' ] # 当前节点的价格
1052+
1053+ # 步骤2和4:找到当前节点能去的所有节点,计算到达这些节点的价格
1054+ neighbors = graph.get(node, []) # 当前节点的邻居(从图的邻接表中获取)
1055+ for neighbor_name, edge_weight in neighbors: # 遍历当前节点的邻居
1056+ # 计算到达邻居的价格 = 当前节点价格 + 边权重
1057+ new_price = price + edge_weight
1058+ # 如果到达邻居的价格比当前价格更小,则更新邻居的价格
1059+ if new_price < nodes[neighbor_name][' price' ]:
1060+ nodes[neighbor_name][' price' ] = new_price # 更新邻居的价格
1061+ nodes[neighbor_name][' previous' ] = node # 更新邻居的前一个节点
1062+
1063+ processed.add(node) # 将当前节点标记为已处理
1064+ node = find_lowest_price_node () # 在所有未处理的节点中,找到价格最小的节点
1065+
1066+ # 检查是否能到达终点
1067+ if nodes[end][' price' ] == float(' inf' ):
1068+ return -1, None
1069+
1070+ # 重建路径
1071+ def get_path(node_name):
1072+ " " " 根据前驱节点重建路径" " "
1073+ path = []
1074+ current = node_name
1075+ while current is not None:
1076+ path.append(current)
1077+ current = nodes[current][' previous' ]
1078+ path.reverse ()
1079+ return path if path else None
1080+
1081+ path = get_path(end)
1082+ return nodes[end][' price' ], path
1083+
1084+
1085+ # 测试数据
1086+ if __name__ == ' __main__' :
1087+ # 示例图:A->B(1), B->C(2),
1088+ # B->D(5),
1089+ # A->C(4), C->D(1)
1090+ graph = {
1091+ ' A' : [(' B' , 1), (' C' , 4)],
1092+ ' B' : [(' C' , 2), (' D' , 5)],
1093+ ' C' : [(' D' , 1)],
1094+ ' D' : []
1095+ }
1096+
1097+ price, path = dijkstra(graph, ' A' , ' D' )
1098+ print(f" 最少价格: {price}" )
1099+ print(f" 路径: {' -> '.join(path)}" ) # 最少价格: 4, 路径: A -> B -> C -> D
1100+ ` ` `
1101+
1102+ # ## 最少交换价格
1103+
1104+ # ### 描述
1105+
1106+ 给定一个带权有向图,每条边都有一个价格(权重),** 考虑负权边** 的情况,请找到从起点到所有其他顶点的最少价格路径。
1107+
1108+ 当图中存在负权边时,Dijkstra算法不再适用(因为可能出现负环),需要使用** Bellman-Ford算法** 或** SPFA算法** 。Bellman-Ford算法可以检测负权环,并找到最短路径。
1109+
1110+ # ### 思路
1111+
1112+ 1. 初始化起点的距离为0,其他所有节点距离为无穷大
1113+ 2. 对所有边进行V-1次松弛操作(V为顶点数),每次检查所有边是否能更新最短距离
1114+ 3. 对于每条边(u, v, w),如果dist[u] + w < dist[v],则更新dist[v] = dist[u] + w
1115+ 4. 经过V-1次松弛后,如果没有负权环,所有最短路径应该已经找到
1116+ 5. 再进行一次松弛操作,如果还能更新距离,说明存在负权环
1117+ 6. 使用前驱数组记录路径,便于重建最短路径
1118+
1119+ # ### 题解
1120+
1121+ ` ` ` python showLineNumbers
1122+ def bellman_ford(graph, start):
1123+ " " "
1124+ 使用Bellman-Ford算法找到从起点到所有顶点的最少价格路径
1125+ 可以处理负权边,并检测负权环
1126+
1127+ :param graph: 图的边列表表示,[(u, v, weight), ...]
1128+ :param start: 起点
1129+ :return: (距离字典, 前驱字典, 是否存在负权环)
1130+ " " "
1131+ # 获取所有顶点
1132+ vertices = set ()
1133+ for u, v, w in graph:
1134+ vertices.add(u)
1135+ vertices.add(v)
1136+ vertices = list(vertices)
1137+
1138+ # 初始化距离和前驱
1139+ distances = {v: float(' inf' ) for v in vertices}
1140+ predecessors = {v: None for v in vertices}
1141+ distances[start] = 0
1142+
1143+ # 松弛操作:进行V-1次(V为顶点数)
1144+ for _ in range(len(vertices) - 1):
1145+ updated = False
1146+ for u, v, weight in graph:
1147+ if distances[u] ! = float(' inf' ) and distances[u] + weight < distances[v]:
1148+ distances[v] = distances[u] + weight
1149+ predecessors[v] = u
1150+ updated = True
1151+ if not updated:
1152+ break # 提前退出优化
1153+
1154+ # 检测负权环:再进行一次松弛,如果还能更新,说明存在负权环
1155+ has_negative_cycle = False
1156+ for u, v, weight in graph:
1157+ if distances[u] ! = float(' inf' ) and distances[u] + weight < distances[v]:
1158+ has_negative_cycle = True
1159+ break
1160+
1161+ return distances, predecessors, has_negative_cycle
1162+
1163+ def get_path(predecessors, start, end):
1164+ " " "
1165+ 根据前驱字典重建路径
1166+ " " "
1167+ if end not in predecessors or predecessors[end] is None:
1168+ return None
1169+
1170+ path = []
1171+ current = end
1172+ while current is not None:
1173+ path.append(current)
1174+ current = predecessors[current]
1175+ if current == start:
1176+ path.append(start)
1177+ break
1178+
1179+ path.reverse ()
1180+ return path if path[0] == start else None
1181+
1182+ # 测试数据
1183+ if __name__ == ' __main__' :
1184+ # 示例图:A->B(-1), A->C(4), B->C(3), B->D(2), B->E(2), D->B(1), D->C(5), E->D(-3)
1185+ graph = [
1186+ (' A' , ' B' , -1),
1187+ (' A' , ' C' , 4),
1188+ (' B' , ' C' , 3),
1189+ (' B' , ' D' , 2),
1190+ (' B' , ' E' , 2),
1191+ (' D' , ' B' , 1),
1192+ (' D' , ' C' , 5),
1193+ (' E' , ' D' , -3)
1194+ ]
1195+
1196+ distances, predecessors, has_cycle = bellman_ford(graph, ' A' )
1197+
1198+ if has_cycle:
1199+ print(" 警告:图中存在负权环!" )
1200+ else:
1201+ print(" 从A到各顶点的最少价格:" )
1202+ for vertex, dist in distances.items ():
1203+ if dist ! = float(' inf' ):
1204+ path = get_path(predecessors, ' A' , vertex)
1205+ print(f" A -> {vertex}: {dist}, 路径: {' -> '.join(path)}" )
1206+ else:
1207+ print(f" A -> {vertex}: 无法到达" )
1208+ ` ` `
0 commit comments