一般来讲,实现图的过程中需要有两个自定义的类进行支撑:顶点(Vertex)类,和图(Graph)类。按照这一架构,Vertex类至少需要包含名称(或者某个代号、数据)和邻接顶点两个参数,前者作为顶点的标识,后者形成顶点和顶点相连的边,相应地必须有访问获取和设定参数的方法加以包装。Graph类至少需要拥有一个包含所有点的数据结构(列表或者map等),相应地应该有新增顶点、访问顶点、新增连接边等方法。当然,为了实现Dijkstra算法(一种基本的最短路径算法),除了可以在Graph类里增加一个执行Dijkstra算法的方法以外,还需要在Vertex类里增加用于Dijkstra算法的一些参数:某一个顶点距离Dijkstra搜索起点的距离,以及一旦完成Dijkstra搜索需要回溯路径时,前驱顶点的信息。

在这里记录用python实现以上基本方法和Dijkstra算法的代码,因为Python中的现成的数据结构类型便于使用,比如字典dict类,很方便地能够构造一个类似于map的映射,而且Python的sort方法也特别好用。首先是自定义类顶点(Vertex)的代码,如上所述,为了满足BFS算法的执行和回溯的需要,Vertex类一共有四个参数:id(标记)、connections(邻接顶点字典,键值为邻接顶点,对应值为权重或者边长)、前驱顶点pre和从起点的距离distance。这二者在Dijkstra算法的执行过程中被赋值,在回溯时需要利用pre的信息。

Vertex类的方法中多数为基本的访问参数(get)-设定参数(set)的方法对,比如访问/设定邻接顶点信息的 add_neighbour(neighbour, weight) 和 get_connection() ,访问/设定前驱顶点信息的 get_pre()  和 set_pre(prev) ,以及访问/设定从起点起距离的 get_distance()  和 set_distance(dist) 。除此之外重载了字符串化方法,即__str__,这便于print函数和str函数将Vertex类转化为一个有意义的字符串。

1 classVertex:2 #初始化构造函数,name为字符串,connections字典为

3 #前驱顶点pre,从起点距离distance,在Dijkstra执行赋值后有意义

4 def __init__(self, name):5 self.id =name6 self.pre =None7 self.distance = float('inf')8 self.connections =dict()9

10 #重载字符串化函数,返回字符串

11 def __str__(self):12 return str(self.id) + "connected to:" + str([x.id for x inself.connections])13

14 #增加相邻顶点,neighbour为Vertex类,weight为浮点型边权重

15 def add_neighbour(self, neighbour, weight=0):16 self.connections[neighbour] =weight17

18 #获取顶点id函数

19 defget_id(self):20 returnself.id21

22 #获取顶点邻接点的函数,返回键值(Vertex)列表

23 defget_connections(self):24 returnself.connections.keys()25

26 #获取顶点与邻接点边权重,传入Vertex类对象neighbour,返回weight(fl)

27 defget_weight(self, neighbour):28 returnself.connections[neighbour]29

30 #获取顶点的距离(在BFS执行后使用)

31 defget_distance(self):32 returnself.distance33

34 #获取前驱顶点(在Dijkstra执行后使用)

35 defget_pre(self):36 returnself.pre37

38 #设定顶点的距离(在Dijkstra执行过程中调用)

39 defset_distance(self, dist):40 self.distance =dist41

42 #设定前驱顶点(在Dijkstra执行过程中调用)

43 defset_pre(self, prev):44 self.pre = prev

图(Graph)类:拥有两个参数:顶点字典(顶点id:顶点Vertex类)和顶点数量。顶点数量这个参数似乎没什么用,除非是增加一个判断图是否为空的 isEmpty() 方法和 getSize() 方法可能用得着。

方法中包含新增顶点的 add_vertex(name) ,按照id获取顶点的 get_vertex(name) 。dict的数据结构给按照id访问顶点在空间上和时间上都创造了极大的方便,也为此参数中为顶点字典而不用顶点列表。添加边的方法 add_edge(vertex1, vertex2, weight) ,其中需要调用Vertex类中设定邻接点的方法。除此之外,Graph类还有一些重载的方法,比如重载contains,这个函数在类似于 3 in list(range(5)) 这样的语句中被调用,比如经常用的,判断某个元素是否在列表内等。重载迭代器__iter__,类似于 for item in list(range(10)): 这样的语句中被调用,有助于图中所有Vertex的遍历。基于迭代器还可以重载字符串化方法__str__。

最后就是Dijkstra算法。关于这个算法的信息准备记录在另一篇随笔中,基本思路是创建一个优先队列(即距离起始点路程从小到大的队列),每次查看列表中路程最短的点A,观察在这个顶点A的邻接点中,是否有可能因为通过顶点A而使得该邻接点的路程缩减。每次这样一轮操作结束以后就从队列中删去这个点A,然后把队列重新排列一遍。在以前课上的学习中,优先队列使用的是二叉堆(Binary Heap)实现,在这里我直接调用了Python内置的sort函数,虽然计算复杂度不敢保证,但是从后面用的实际例子的效率来说也没有任何影响。

1 classGraph:2 #无参数构造函数,vertex_dict为映射字典

3 def __init__(self):4 self.vertex_dict =dict()5 self.vertex_num =06

7 #增加顶点函数,传入新增顶点id

8 defadd_vertex(self, name):9 if name not inself.vertex_dict.keys():10 new_vertex =Vertex(name)11 self.vertex_dict[name] =new_vertex12 self.vertex_num = self.vertex_num + 1

13

14 #增加边函数, 传入顶点1名称、顶点2名称、权重

15 defadd_edge(self, vertex1, vertex2, weight):16 if vertex1 not inself.vertex_dict:17 self.add_vertex(vertex1)18 if vertex2 not inself.vertex_dict:19 self.add_vertex(vertex2)20 self.vertex_dict[vertex1].add_neighbour(self.vertex_dict[vertex2], weight)21 self.vertex_dict[vertex2].add_neighbour(self.vertex_dict[vertex1], weight)22

23 #按照id检索顶点函数,传入id,返回Vertex类

24 defget_vertex(self, name):25 if name inself.vertex_dict.keys():26 returnself.vertex_dict[name]27 else:28 returnNone29

30 #重载contains方法,传入id,返回bool值

31 def __contains__(self, item):32 return item inself.vertex_dict33

34 #重载迭代器,返回对应迭代器

35 def __iter__(self):36 returniter(self.vertex_dict.values())37

38 #重载字符串化方法,返回字符串

39 def __str__(self):40 o_str =str()41 for item inself:42 o_str = o_str + str(item) + '\n'

43 returno_str44

45 #Dijkstra算法,传入起点

46 defdijkstra_search(self, start):47 #优先队列priority

48 priority =list(self.vertex_dict.values())49 #起点距离置零

50 start.set_distance(0)51 #优先队列重排

52 priority.sort(key=lambda x: x.get_distance(), reverse=True)53 whilepriority:54 #重排标记changed,若存在顶点发生distance变化则标记为True

55 changed =False56 #弹出最高优先顶点current

57 current =priority.pop()58 #遍历current邻接顶点

59 for vertex_tmp incurrent.get_connections():60 dist_tmp = current.get_distance() +current.get_weight(vertex_tmp)61 #若发现优势路径则更改邻接顶点的distance和前驱顶点pre

62 if dist_tmp

67 ifchanged:68 priority.sort(key=lambda x: x.get_distance(), reverse=True)

最后,和Dijkstra算法配合还需要一个路径回溯的方法。本来把回溯作为图类的一个方法也可以,但是由于通过访问顶点的pre参数已经可以回溯到上一个Vertex类,中间不涉及到图的操作,因此可以安全地把它写成一个静态方法的形式,即不放在Graph类中:

1 #回溯路径函数,传入参数终点destination,返回路径列表list(Vertex)

2 defreverse_trace(destination):3 current =destination4 trace =[destination]5 whilecurrent.get_pre():6 current =current.get_pre()7 trace.append(current)8 trace.reverse()9 return trace

这个函数返回的是一个路径中顺序出现的顶点Vertex类列表。

作为图的一个练习和测试,我拿了北京市的地铁信息。首先我在某一个文件夹里新建了编号为line1到line10的10个.txt文件,录入了10条地铁线的信息。后来又新增了13号线和15号线的信息。这些txt里的信息是这样储存的:a)一行一个站名,表示起点站;b)一行一个站名+一个数字,表示一个站和前一站的距离;c)一行两个站名+一个数字,表示两个站之间的距离。然后可以写一个读入的函数,把这些信息读进来,每个站名就是一个id,形成一张图,选定一个起点S以后执行Dijkstra算法,然后选定一个终点D回溯,就得到了从S到D的最短路径。

如果只是想得到站与站之间的最短距离,这个方法当然是合适的;不过,如果考虑到换乘因素,就会发现这个算法没有考虑到换乘的时间代价,这会使得搜索出一些需要换乘很多次的结果,而这在现实生活中并不是最快的。一个改进成本很小的方案是,把每个站所属的线路号码加在站名后面作为id的一部分,比如用“王府井1”作为一个id。同时,利用存储形式c来规定换乘站的间距,比如规定“海淀黄庄4”和“海淀黄庄10”的距离。这样一来就可以考虑换乘代价了。在这个思路下,Graph类相应的读入函数:

1 classGraph:2 #读取顶点文件(适用于地铁路线例子的方法),传入文件路径path和线路编号subway

3 defread_in(self, path, subway):4 with open(path, 'r') as file:5 #按行读取文件

6 line =file.readline()7 station =None8 whileline:9 info_list =line.split()10 #当行中仅含有1个元素时,仅创建

11 if len(info_list) == 1:12 station =info_list[0]13 station = station +str(subway)14 self.add_vertex(station)15 #当行中含有2个元素时,第1个元素为车站名称,第2个元素为距上个车站距离

16 elif len(info_list) == 2:17 pre_station =station18 [station, distance] =info_list19 distance =int(distance)20 station = station +str(subway)21 self.add_vertex(station)22 self.add_edge(pre_station, station, distance)23 #否则当行中含有3个元素,前2个元素为车站,第3个元素为前二者间距

24 else:25 [station1, station2, distance] =info_list26 distance =int(distance)27 self.add_edge(station1, station2, distance)28 line = file.readline()

最后是初始化函数和个人偏爱的交互式菜单代码,仅供参考:

1 #初始化函数(适用于地铁路线例子的方法)

2 #读入在file_path所示文件夹下的北京地铁线数据,返回生成的图

3 definitialize():4 graph =Graph()5 file_path = 'D:\Personal Documents\Project\BeijingSubway\line'

6 for i in range(1, 11):7 path = file_path + str(i) + '.txt'

8 graph.read_in(path, i)9 path = file_path + '13.txt'

10 graph.read_in(path, 13)11 path = file_path + '15.txt'

12 graph.read_in(path, 15)13 returngraph14

15

16 #交互式菜单函数(适用于地铁路线例子的方法),传入图

17 defroute_find_menu(graph):18 print('>> 寻找乘地铁最短路线,输入0退出')19 start = input('>> 输入起始站名+地铁线(如“王府井1”):')20 while start != '0':21 graph.breadth_first_search(graph.get_vertex(start))22 destination = input('>> 输入终点站名+地铁线(如“王府井1”):')23 if destination == '0':24 break

25 trace_list =reverse_trace(graph.get_vertex(destination))26 print([x.get_id() for x intrace_list])27 start = input('>> 输入起始站名:')

最后只要分别调用 graph1 = initialize() 创建实例,并且用 route_find_menu(graph1) 进入菜单即可。

python无向加权图_图:无向图(Graph)基本方法及Dijkstra算法的实现 [Python]相关推荐

  1. python无向带权图

    无向无权图见另一篇文章<python无向无权图结构>,这篇讲无向带权图,并且给出一个地铁线路例子. # -*- coding: UTF-8 -*- #!/usr/bin/python#-- ...

  2. 调用python接口并画图_【PySpark源码解析】教你用Python调用高效Scala接口

    点击 机器学习算法与Python学习 ,选择加星标 精彩内容不迷路 机器之心专栏 作者:汇量科技-陈绪 众所周知,Spark 框架主要是由 Scala 语言实现,同时也包含少量 Java 代码.Spa ...

  3. Python可视化 | Matplotlib绘制圆环图的两种方法!

    人生苦短,快学Python!今天给大家介绍Python可视化之环形图的绘制. 环形图,也被称为圆环图.它在功能上与饼图相同,只是中间有一个空白,并且能够同时支持多个统计数据.与标准饼图相比,环形图提供 ...

  4. python dict批量选择_这一定是你见过最全面的python重点

    由于总结了太多的东西,所以篇幅有点长,这也是我"缝缝补补"总结了好久的东西. Py2 VS Py3 print成为了函数,python2是关键字 不再有unicode对象,默认st ...

  5. python制作ppt动画_天呐,还能这么玩!用 Python 生成动态 PPT

    在工作的过程中,我们会发现那些能够把知识.成果讲透的人很多都会做动态图表. 这篇文章就介绍了 Python 中一种简单的动态图表制作方法,这样生成的动图就可以丰富我们的PPT啦~ 数据暴增的年代,数据 ...

  6. python可以修图吗_会照片处理的不只是ps,还有Python!

    女朋友老是吵着要修图,作为程序员,只会敲代码,不会ps啊,真是令人头大. 程序员是这么容易被难到的吗?肯定不会!最近发现了程序员的p图神器--python. python也可以修图吗?是滴!下面就带你 ...

  7. python大型项目经验_经验丰富程序员才知道的8种高级Python技巧

    全文共2330字,预计学习时长11分钟 图源:unsplash 本文将介绍8个简洁的Python技巧,若非经验十足的程序员,你肯定有些从未见过.向着更简洁更高效,出发吧! 1.通过多个键值将对象进行排 ...

  8. 运维学python用不上_数读 | 为什么运维朋友们都需要学Python?

    运维人员需不需要学开发?需不需要学Python?PythonN和Shell有什么区别?天天问这种好水的问题,我实在受不了,决定帮大家扫扫盲. 现阶段,掌握一门开发语言已经成为高级运维工程师的必备计能, ...

  9. python批量检索文献_快解锁新姿势,教你如何用Python搞定文献搜索和科研图片!...

    相比实验论文,发表SCI应该更让科研狗们重视和焦虑. 起初看到读博的同学发表SCI论文,心里面就已经酸了,后来「本科生发数篇 SCI」的新闻屡见不鲜,现在甚至连小学生都跑出来分一杯羹-- 前段时间,B ...

最新文章

  1. 小米miui系统怎么关闭文件管理里的热门视频和表情?
  2. 一项震动制药行业的研究:大型制药公司AI的生产力
  3. linux下puppet的“资源”管理
  4. (软件工程复习核心重点)第四章总体设计-第二节:设计原理
  5. 如何通过Facebook幻灯片广告让销售量疯涨
  6. NASA 美国国家航空航天局开源项目列表
  7. 从Asp.net实现资源全球化和本地化
  8. 向量的点积与叉乘的几何解释
  9. kubernetes dns详解
  10. Windows注册表开机自启,右键菜单,运行的位置介绍
  11. Windows环境下32位汇编语言程序设计(典藏版) 高清完整
  12. 电脑(伪)大神装B必备,来学几个windows脚本命令
  13. Aquarius's Trial F - 6 HDU - 2102 A计划
  14. win10内置linux读取u盘raw,U盘或磁盘分区RAW格式恢复方案
  15. 计算机应用基础第1版前言,《计算机应用基础课件》前言
  16. python制作网络社交图
  17. 移动联通电信wap和net接入点判断
  18. 读写分离怎么做,怎么实现
  19. djvu批量转换为pdf的工具和djvu阅读器(附下载链接)
  20. 什么叫反光识别读数识别_什么叫做反光识别

热门文章

  1. java.io.NotSerializableException: org.apache.shiro.util.SimpleByteSource at java.io.ObjectOutputStr
  2. 【已解决】linux redhat 6 如何打开防火墙中的某个端口?例如:5900端口
  3. 信号处理中数字频率和模拟频率简明讲解
  4. stateflow错误:Error in port widths or dimensions.
  5. python中cd是什么意思_python的cd的
  6. 2.2.1 计算机网络之传输介质(双绞线、同轴电缆、光纤、无线电缆、微波、激光、红外线)
  7. 1.2 操作系统的发展和分类(手工、单道/多道批处理、分时、实时、网络、分布式、嵌入式、个人计算机)
  8. php-箭头r含义,php中-箭头的用法和意义
  9. 火狐渗透测试浏览器_微软、火狐浏览器、Opera浏览器等主流平台纷纷布局IPFS:大势所趋...
  10. mysql query profile_MySQL Query Profile