当前位置: 首页 > news >正文

多个id如何用js_将多个MSA连超级高铁网络,如何用最少的轨道连接所有MSA?

假设我们要把所有15个最大的MSA都连入超级高铁网络,目标是要最大限度地降低网络的铺设成本,于是就意味着所用的轨道数量要最少。于是问题就成了:如何用最少的轨道连接所有MSA?

4.4.1 权重的处理

要了解建造某条边所需的轨道数量,就需要知道这条边表示的距离。现在是再次引入权重概念的时候了。在超级高铁网络中,边的权重是两个所连MSA之间的距离。图4-5与图4-2几乎相同,差别只是每条边多了权重,表示边所连的两个顶点之间的距离(以英里为单位)。

037612a1380bdf434122a2f2f7b6fcb1.png

图4-5 美国15个最大的MSA的加权图,权重代表两个MSA之间的距离,单位英里(1英里≈1.6093 km)

为了处理权重,需要建立Edge的子类WeightedEdge和Graph的子类WeightedGraph。每个WeightedEdge都带有一个与其关联的表示其权重的float类型数据。下面马上就会介绍Jarník算法,它能够比较两条边并确定哪条边权重较低。采用数值型的权重就很容易进行比较了。具体代码如代码清单4-6所示。

代码清单4-6 weighted_edge.py

from __future__ import annotationsfrom dataclasses import dataclassfrom edge import Edge@dataclassclass WeightedEdge(Edge):    weight: float    def reversed(self) -> WeightedEdge:        return WeightedEdge(self.v, self.u, self.weight)    # so that we can order edges by weight to find the minimum weight edge    def __lt__(self, other: WeightedEdge) -> bool:        return self.weight str:        return f"{self.u} {self.weight}> {self.v}"

WeightedEdge的实现代码与Edge的实现代码并没有太大的区别,只是添加了一个weight属性,并通过__lt__()实现了“WeightedEdge就可以相互比较了。“u和v),因为Jarník的算法只关注如何找到权重最小的边。

如代码清单4-7所示,WeightedGraph从Graph继承了大部分功能,此外,它还包含了__init__方法和添加WeightedEdge的便捷方法,并且实现了自己的__str__()方法。它还有一个新方法neighbors_for_index_with_weights(),这一方法不仅会返回每一位邻居,还会返回到达这位邻居的边的权重。这一方法对其__str__()十分有用。

代码清单4-7 weighted_graph.py

from typing import TypeVar, Generic, List, Tuplefrom graph import Graphfrom weighted_edge import WeightedEdgeV = TypeVar('V') # type of the vertices in the graphclass WeightedGraph(Generic[V], Graph[V]):    def __init__(self, vertices: List[V] = []) -> None:        self._vertices: List[V] = vertices        self._edges: List[List[WeightedEdge]] = [[] for _ in vertices]    def add_edge_by_indices(self, u: int, v: int, weight: float) -> None:        edge: WeightedEdge = WeightedEdge(u, v, weight)        self.add_edge(edge) # call superclass version    def add_edge_by_vertices(self, first: V, second: V, weight: float) -> None:        u: int = self._vertices.index(first)        v: int = self._vertices.index(second)        self.add_edge_by_indices(u, v, weight)    def neighbors_for_index_with_weights(self, index: int) -> List[Tuple[V, float]]:        distance_tuples: List[Tuple[V, float]] = []        for edge in self.edges_for_index(index):            distance_tuples.append((self.vertex_at(edge.v), edge.weight))        return distance_tuples    def __str__(self) -> str:        desc: str = ""        for i in range(self.vertex_count):            desc += f"{self.vertex_at(i)} -> {self.neighbors_for_index_with_weights(i)}"        return desc

现在可以实际定义加权图了。这里将会用到图4-5表示的加权图,名为city_graph2。具体代码如代码清单4-8所示。

代码清单4-8 weighted_graph.py(续)

if __name__ == "__main__":    city_graph2: WeightedGraph[str] = WeightedGraph(["Seattle", "San Francisco",       "Los Angeles", "Riverside", "Phoenix", "Chicago", "Boston", "New York", "Atlanta",      "Miami", "Dallas", "Houston", "Detroit", "Philadelphia", "Washington"])    city_graph2.add_edge_by_vertices("Seattle", "Chicago", 1737)    city_graph2.add_edge_by_vertices("Seattle", "San Francisco", 678)    city_graph2.add_edge_by_vertices("San Francisco", "Riverside", 386)    city_graph2.add_edge_by_vertices("San Francisco", "Los Angeles", 348)    city_graph2.add_edge_by_vertices("Los Angeles", "Riverside", 50)    city_graph2.add_edge_by_vertices("Los Angeles", "Phoenix", 357)    city_graph2.add_edge_by_vertices("Riverside", "Phoenix", 307)    city_graph2.add_edge_by_vertices("Riverside", "Chicago", 1704)    city_graph2.add_edge_by_vertices("Phoenix", "Dallas", 887)    city_graph2.add_edge_by_vertices("Phoenix", "Houston", 1015)    city_graph2.add_edge_by_vertices("Dallas", "Chicago", 805)    city_graph2.add_edge_by_vertices("Dallas", "Atlanta", 721)    city_graph2.add_edge_by_vertices("Dallas", "Houston", 225)    city_graph2.add_edge_by_vertices("Houston", "Atlanta", 702)    city_graph2.add_edge_by_vertices("Houston", "Miami", 968)    city_graph2.add_edge_by_vertices("Atlanta", "Chicago", 588)    city_graph2.add_edge_by_vertices("Atlanta", "Washington", 543)    city_graph2.add_edge_by_vertices("Atlanta", "Miami", 604)    city_graph2.add_edge_by_vertices("Miami", "Washington", 923)    city_graph2.add_edge_by_vertices("Chicago", "Detroit", 238)    city_graph2.add_edge_by_vertices("Detroit", "Boston", 613)    city_graph2.add_edge_by_vertices("Detroit", "Washington", 396)    city_graph2.add_edge_by_vertices("Detroit", "New York", 482)    city_graph2.add_edge_by_vertices("Boston", "New York", 190)    city_graph2.add_edge_by_vertices("New York", "Philadelphia", 81)    city_graph2.add_edge_by_vertices("Philadelphia", "Washington", 123)    print(city_graph2)

因为WeightedGraph实现了__str__(),所以我们可以美观打印出city_graph2。在输出结果中会同时显示每个顶点连接的所有顶点及这些连接的权重。

Seattle -> [('Chicago', 1737), ('San Francisco', 678)]San Francisco -> [('Seattle', 678), ('Riverside', 386), ('Los Angeles', 348)]Los Angeles -> [('San Francisco', 348), ('Riverside', 50), ('Phoenix', 357)]Riverside -> [('San Francisco', 386), ('Los Angeles', 50), ('Phoenix', 307), ('Chicago',   1704)]Phoenix -> [('Los Angeles', 357), ('Riverside', 307), ('Dallas', 887), ('Houston', 1015)]Chicago -> [('Seattle', 1737), ('Riverside', 1704), ('Dallas', 805), ('Atlanta', 588),     ('Detroit', 238)]Boston -> [('Detroit', 613), ('New York', 190)]New York -> [('Detroit', 482), ('Boston', 190), ('Philadelphia', 81)] Atlanta -> [('Dallas', 721), ('Houston', 702), ('Chicago', 588), ('Washington', 543),    ('Miami', 604)]Miami -> [('Houston', 968), ('Atlanta', 604), ('Washington', 923)]Dallas -> [('Phoenix', 887), ('Chicago', 805), ('Atlanta', 721), ('Houston', 225)]Houston -> [('Phoenix', 1015), ('Dallas', 225), ('Atlanta', 702), ('Miami', 968)]Detroit -> [('Chicago', 238), ('Boston', 613), ('Washington', 396), ('New York', 482)]Philadelphia -> [('New York', 81), ('Washington', 123)]Washington -> [('Atlanta', 543), ('Miami', 923), ('Detroit', 396), ('Philadelphia', 123)]

4.4.2 查找最小生成树

树是一种特殊的图,它在任意两个顶点之间只存在一条路径,这意味着树中没有环路(cycle),有时被称为无环(acyclic)。环路可以被视作循环。如果可以从一个起始顶点开始遍历图,不会重复经过任何边,并返回到起始顶点,则称存在一条环路。任何不是树的图都可以通过修剪边而成为树。图4-6演示了通过修剪边把图转换为树的过程。

1257da24669a6c7485d336b87077c855.png

图4-6 在左图中,在顶点B、C和D之间存在一个环路,因此它不是树。在右图中,
连通C和D的边已被修剪掉了,因此它是一棵树

连通图(connected graph)是指从图的任一顶点都能以某种路径到达其他任何顶点的图。本章中的所有图都是连通图。生成树(spanning tree)是把图所有顶点都连接起来的树。最小生成树(minimum spanning tree)是以最小总权重把加权图的每个顶点都连接起来的树(相对于其他的生成树而言)。对于每张加权图,我们都能高效地找到其最小生成树。

这里出现了一大堆术语!“查找最小生成树”和“以权重最小的方式连接加权图中的所有顶点”的意思相同,这是关键点。对任何设计网络(交通网络、计算机网络等)的人来说,这都是一个重要而实际的问题:如何能以最低的成本连接网络中的所有节点呢?这里的成本可能是电线、轨道、道路或其他任何东西。以电话网络来说,这个问题的另一种提法就是:连通每个电话机所需要的最短电缆长度是多少?

1.重温优先队列

优先队列在第2章中已经介绍过了。Jarník算法将需要用到优先队列。我们可以从第2章的程序包中导入PriorityQueue类,要获得详情请参阅紧挨着代码清单4-5之前的注意事项,也可以把该类复制为一个新文件并放入本章的程序包中。为完整起见,在代码清单4-9中,我们将重新创建第2章中的PriorityQueue,这里假定import语句会被放入单独的文件中。

代码清单4-9 priority_queue.py

from typing import TypeVar, Generic, Listfrom heapq import heappush, heappopT = TypeVar('T')class PriorityQueue(Generic[T]):    def __init__(self) -> None:         self._container: List[T] = []    @property    def empty(self) -> bool:        return not self._container  # not is true for empty container    def push(self, item: T) -> None:        heappush(self._container, item)  # in by priority    def pop(self) -> T:        return heappop(self._container)  # out by priority    def __repr__(self) -> str:        return repr(self._container)

2.计算加权路径的总权重

在开发查找最小生成树的方法之前,我们需要开发一个用于检测某个解的总权重的函数。最小生成树问题的解将由组成树的加权边列表构成。首先,我们会将WeightedPath定义为WeightedEdge的列表,然后会定义一个total_weight()函数,该函数以WeightedPath的列表为参数并把所有边的权重相加,以便得到总权重。具体代码如代码清单4-10所示。

代码清单4-10 mst.py

from typing import TypeVar, List, Optionalfrom weighted_graph import WeightedGraphfrom weighted_edge import WeightedEdgefrom priority_queue import PriorityQueueV = TypeVar('V') # type of the vertices in the graphWeightedPath = List[WeightedEdge] # type alias for pathsdef total_weight(wp: WeightedPath) -> float:    return sum([e.weight for e in wp])

3.Jarník算法

查找最小生成树的Jarník算法把图分为两部分:正在生成的最小生成树的顶点和尚未加入最小生成树的顶点。其工作步骤如下所示。

(1)选择要被包含于最小生成树中的任一顶点。

(2)找到连通最小生成树与尚未加入树的顶点的权重最小的边。

(3)将权重最小边末端的顶点添加到最小生成树中。

(4)重复第2步和第3步,直到图中的每个顶点都加入了最小生成树。

注意 Jarník算法常被称为Prim算法。在20世纪20年代末,两位捷克数学家OtakarBorůvka和VojtěchJarník致力于尽量降低铺设电线的成本,提出了解决最小生成树问题的算法。他们提出的算法在几十年后又被其他人“重新发现”[3]。

为了高效地运行Jarník算法,需要用到优先队列。每次将新的顶点加入最小生成树时,所有连接到树外顶点的出边都会被加入优先队列中。从优先队列中弹出的一定是权重最小的边,算法将持续运行直至优先队列为空为止。这样就确保了权重最小的边一定会优先加入树中。如果被弹出的边与树中的已有顶点相连,则它将被忽略。

代码清单4-11中的mst()完整实现了Jarník算法[4],它还带了一个用来打印WeightedPath的实用函数。

警告 Jarník算法在有向图中不一定能正常工作,它也不适用于非连通图。

代码清单4-11 mst.py(续)

def mst(wg: WeightedGraph[V], start: int = 0) -> Optional[WeightedPath]:    if start > (wg.vertex_count - 1) or start < 0:        return None    result: WeightedPath = [] # holds the final MST    pq: PriorityQueue[WeightedEdge] = PriorityQueue()    visited: [bool] = [False] * wg.vertex_count # where we've been    def visit(index: int):        visited[index] = True # mark as visited        for edge in wg.edges_for_index(index):             # add all edges coming from here to pq            if not visited[edge.v]:                pq.push(edge)    visit(start) # the first vertex is where everything begins    while not pq.empty: # keep going while there are edges to process        edge = pq.pop()        if visited[edge.v]:            continue # don't ever revisit        # this is the current smallest, so add it to solution        result.append(edge)         visit(edge.v) # visit where this connects    return resultdef print_weighted_path(wg: WeightedGraph, wp: WeightedPath) -> None:    for edge in wp:        print(f"{wg.vertex_at(edge.u)} {edge.weight}> {wg.vertex_at(edge.v)}")    print(f"Total Weight: {total_weight(wp)}")

下面逐行过一遍mst()。

def mst(wg: WeightedGraph[V], start: int = 0) -> Optional[WeightedPath]:    if start > (wg.vertex_count - 1) or start < 0:        return None

本算法将返回某一个代表最小生成树的WeightedPath对象。运算本算法的起始位置无关紧要(假定图是连通和无向的),因此默认设为索引为0的顶点。如果start无效,则mst()返回None。

result: WeightedPath = [] # holds the final MSTpq: PriorityQueue[WeightedEdge] = PriorityQueue()visited: [bool] = [False] * wg.vertex_count # where we've been

result将是最终存放加权路径的地方,也即包含了最小生成树。随着权重最小的边不断被弹出以及图中新的区域不断被遍历,WeightedEdge会不断被添加到result中。因为Jarník算法总是选择权重最小的边,所以被视为贪婪算法(greedy algorithm)之一。pq用于存储新发现的边并弹出次低权重的边。visited用于记录已经到过的顶点索引,这用Set也可以实现,类似于bfs()中的explored。

def visit(index: int):    visited[index] = True # mark as visited    for edge in wg.edges_for_index(index):         # add all edges coming from here        if not visited[edge.v]:            pq.push(edge)

visit()是一个便于内部使用的函数,用于把顶点标记为已访问,并把尚未访问过的顶点所连的边都加入pq中。不妨注意一下,使用邻接表模型能够轻松地找到属于某个顶点的边。

visit(start) # the first vertex is where everything begins

除非图是非连通的,否则先访问哪个顶点是无所谓的。如果图是非连通的,是由多个不相连的部分组成的,那么mst()返回的树只会涵盖图的某一部分,也就是起始节点所属的那部分图。

while not pq.empty: # keep going while there are edges to process    edge = pq.pop()    if visited[edge.v]:        continue # don't ever revisit    # this is the current smallest, so add it    result.append(edge)     visit(edge.v) # visit where this connectsreturn result

只要优先队列中还有边存在,我们就将它们弹出并检查它们是否会引出尚未加入树的顶点。因为优先队列是以升序排列的,所以会先弹出权重最小的边。这就确保了结果确实具有最小总权重。如果弹出的边不会引出未探索过的顶点,那么就会被忽略,否则,因为该条边是目前为止权重最小的边,所以会被添加到结果集中,并且对其引出的新顶点进行探索。如果已没有边可供探索了,则返回结果。

最后再回到用轨道最少的超级高铁网络连接美国15个最大的MSA的问题吧。结果路径就是city_graph2的最小生成树。下面尝试对city_graph2运行一下mst(),具体代码如代码清单4-12所示。

代码清单4-12 mst.py(续)

if __name__ == "__main__":    city_graph2: WeightedGraph[str] = WeightedGraph(["Seattle", "San Francisco", "Los        Angeles", "Riverside", "Phoenix", "Chicago", "Boston", "New York", "Atlanta",      "Miami", "Dallas", "Houston", "Detroit", "Philadelphia", "Washington"])    city_graph2.add_edge_by_vertices("Seattle", "Chicago", 1737)    city_graph2.add_edge_by_vertices("Seattle", "San Francisco", 678)    city_graph2.add_edge_by_vertices("San Francisco", "Riverside", 386)    city_graph2.add_edge_by_vertices("San Francisco", "Los Angeles", 348)    city_graph2.add_edge_by_vertices("Los Angeles", "Riverside", 50)    city_graph2.add_edge_by_vertices("Los Angeles", "Phoenix", 357)    city_graph2.add_edge_by_vertices("Riverside", "Phoenix", 307)    city_graph2.add_edge_by_vertices("Riverside", "Chicago", 1704)    city_graph2.add_edge_by_vertices("Phoenix", "Dallas", 887)    city_graph2.add_edge_by_vertices("Phoenix", "Houston", 1015)    city_graph2.add_edge_by_vertices("Dallas", "Chicago", 805)    city_graph2.add_edge_by_vertices("Dallas", "Atlanta", 721)    city_graph2.add_edge_by_vertices("Dallas", "Houston", 225)    city_graph2.add_edge_by_vertices("Houston", "Atlanta", 702)    city_graph2.add_edge_by_vertices("Houston", "Miami", 968)    city_graph2.add_edge_by_vertices("Atlanta", "Chicago", 588)    city_graph2.add_edge_by_vertices("Atlanta", "Washington", 543)    city_graph2.add_edge_by_vertices("Atlanta", "Miami", 604)    city_graph2.add_edge_by_vertices("Miami", "Washington", 923)    city_graph2.add_edge_by_vertices("Chicago", "Detroit", 238)    city_graph2.add_edge_by_vertices("Detroit", "Boston", 613)    city_graph2.add_edge_by_vertices("Detroit", "Washington", 396)    city_graph2.add_edge_by_vertices("Detroit", "New York", 482)    city_graph2.add_edge_by_vertices("Boston", "New York", 190)    city_graph2.add_edge_by_vertices("New York", "Philadelphia", 81)    city_graph2.add_edge_by_vertices("Philadelphia", "Washington", 123)    result: Optional[WeightedPath] = mst(city_graph2)    if result is None:        print("No solution found!")    else:        print_weighted_path(city_graph2, result)

好在有美观打印方法printWeightedPath(),最小生成树的可读性很不错。

Seattle 678> San FranciscoSan Francisco 348> Los AngelesLos Angeles 50> RiversideRiverside 307> PhoenixPhoenix 887> DallasDallas 225> HoustonHouston 702> AtlantaAtlanta 543> WashingtonWashington 123> PhiladelphiaPhiladelphia 81> New YorkNew York 190> BostonWashington 396> DetroitDetroit 238> ChicagoAtlanta 604> MiamiTotal Weight: 5372

换句话说,这是加权图中连通所有MSA的总边长最短的组合,至少需要轨道8645 km。图4-7呈现了这棵最小生成树。

2c05080aa11c72c7fe7d6ecf6d0f75b1.png

图4-7 高亮的边代表连通全部15个MSA的最小生成树

本文摘自《算法精粹:经典计算机科学问题的Python实现》,大卫·科帕克(David Kopec) 著,戴旭 译。

238e59b91c3b5927840884a71eb1dc85.png

本书是一本面向中高级程序员的算法教程,借助Python语言,用经典的算法、编码技术和原理来求解计算机科学的一些经典问题。全书共9章,不仅介绍了递归、结果缓存和位操作等基本编程组件,还讲述了常见的搜索算法、常见的图算法、神经网络、遗传算法、k均值聚类算法、对抗搜索算法等,运用了类型提示等Python高级特性,并通过各级方案、示例和习题展开具体实践。

本书将计算机科学与应用程序、数据、性能等现实问题深度关联,定位独特,示例经典,适合有一定编程经验的中高级Python程序员提升用Python解决实际问题的技术、编程和应用能力。

相关文章:

  • python上传excel文件_利用django如何解析用户上传的excel文件
  • js悬浮二级菜单代码_纯CSS实现简单二级导航下拉效果
  • microbit python扩展_【micro:bit扩展】如何用慧编程扩展设计器为 micro:bit 编写扩展...
  • boost原理与sklearn源码_从sklearn源码简析GBDT
  • 信息隐藏将txt文件合并到jpg文件中_GIS工作中让你事半功倍,在数据处理中常用的小技巧...
  • android欢迎界面引导页_uni-app: 引导页功能如何实现?
  • 六位小数的字符串怎么转化成double类型而不损失精度?_C# 一次数据类型强转失败的翻车原因分析...
  • 互动整合营销_企业做整合营销,有什么实际的意义
  • vue+bootstrap响应式布局_实现 Vue 的响应式系统
  • python扫雷代码源码_利兹联足球俱乐部 2018
  • java写入txt文件_Java面试题如何将字符串写入文件?
  • python支持面向过程_python之面向过程,函数式编程,面向对象浅析
  • 小组取什么名字好_寓意好的公司名字大全 公司名字取什么好
  • c语言计算日出日落时间_高中地理——每日讲1题(日出日落时间、昼夜长短、气压带风带)...
  • request对象_Struts(七)- 获取 request对象和 response对象
  • 实现windows 窗体的自己画,网上摘抄的,学习了
  • Magento 1.x 中文订单打印乱码
  • PHP CLI应用的调试原理
  • rabbitmq延迟消息示例
  • react-core-image-upload 一款轻量级图片上传裁剪插件
  • Vue2 SSR 的优化之旅
  • 从零开始在ubuntu上搭建node开发环境
  • 分布式熔断降级平台aegis
  • 分享几个不错的工具
  • 关于使用markdown的方法(引自CSDN教程)
  • 和 || 运算
  • 基于axios的vue插件,让http请求更简单
  • 如何正确配置 Ubuntu 14.04 服务器?
  • 深入浅出Node.js
  • 一文看透浏览器架构
  • 正则与JS中的正则
  • Salesforce和SAP Netweaver里数据库表的元数据设计
  • ​ 全球云科技基础设施:亚马逊云科技的海外服务器网络如何演进
  • #stm32驱动外设模块总结w5500模块
  • (2022版)一套教程搞定k8s安装到实战 | RBAC
  • (4)事件处理——(7)简单事件(Simple events)
  • (附源码)springboot美食分享系统 毕业设计 612231
  • (附源码)ssm智慧社区管理系统 毕业设计 101635
  • (利用IDEA+Maven)定制属于自己的jar包
  • .Net 4.0并行库实用性演练
  • .NET Core、DNX、DNU、DNVM、MVC6学习资料
  • .Net Remoting(分离服务程序实现) - Part.3
  • .NET 设计模式—简单工厂(Simple Factory Pattern)
  • .secret勒索病毒数据恢复|金蝶、用友、管家婆、OA、速达、ERP等软件数据库恢复
  • @Mapper作用
  • @SuppressLint(NewApi)和@TargetApi()的区别
  • [1204 寻找子串位置] 解题报告
  • [android] 看博客学习hashCode()和equals()
  • [BZOJ 4129]Haruna’s Breakfast(树上带修改莫队)
  • [CentOs7]图形界面
  • [ffmpeg] aac 音频编码
  • [Intel Edison开发板] 05、Edison开发基于MRAA实现IO控制,特别是UART通信
  • [LeetCode]: 145: Binary Tree Postorder Traversal
  • [LeetCode]—Longest Palindromic Substring 最长回文子串
  • [NOSQL] Redis介绍