adj_matrix.py
"""
邻接矩阵
:class VertexNode: 顶点结点类
:class ArcNode: 弧结点类
:class AdjMatrix: 邻接矩阵类
:method create_adj_matrix: 创建一个邻接矩阵对象
:method depth_first_search_recursion: 深度优先递归遍历
:method depth_first_search_nonrecursion: 深度优先非递归遍历
:method breadth_first_search_nonrecursion: 广度优先非递归遍历
:class TravVistor: 访问者类,对深度优先递归、深度优先非递归、广度优先非递归的访问
:method travers_graph: 遍历接口,设置不同参数利用TravVistor对象实现深度优先递归、深度优先非递归、广度优先非递归调用
:method mini_span_tree_prim: 最小生成树,普利姆算法
:method mini_span_tree_kruskal: 最小生成树,克鲁斯卡尔算法
:method topo_sort: 拓扑排序
:method shortest_path_djs: 迪杰斯特拉算法,计算从某一顶点开始到其余顶点的最短路径
:method shortest_path_floyd: 弗洛伊德算法,计算任意两个顶点之间的最短路径
:method center_vex: 计算中心结点
"""
import numpy as np
from typing import List, Tuple, Union
from collections import deque
from array import array
# 所创建的图的种类:DG表示有向图,DN表示有向网,UDG表示无向图,UDN表示无向图
graph_kind_ = {"DG": 0, "DN": 32768, "UDG": 0, "UDN": 32768} # 对于有权网此处默认权值范围小于32768
class VertexNode:
def __init__(self, name: str):
self.name = name # 顶点结点名
self.other_info = None
class ArcNode:
def __init__(self, arc_tail: VertexNode, arc_head: VertexNode, weight: Union[int, float]):
self.arc_head = arc_head # 该弧弧结点的弧头顶点结点
self.arc_tail = arc_tail # 该弧弧结点的弧尾顶点结点
self.weight = weight # 弧的权重
self.other_info = None
class AdjMatrix:
def __init__(self):
self.vertexs: List = [] # 该邻接矩阵中顶点结点列表
self.vex_num: int = 0 # 顶点的数量
self.arc_num: int = 0 # 弧的数量
self.arcs_matrix: np = None # 准备创建的邻接矩阵
self.graph_kind: str = "" # 该邻接矩阵表示的图的类型
def locate_vertex(adj_matrix: AdjMatrix, vertex_node: VertexNode):
"""
获取顶点的位置
:param adj_matrix: 在该邻接矩阵对象的顶点列表中查找顶点结点位置
:param vertex_node: 所需查找位置的顶点结点
:return: 顶点结点的索引
"""
for index, vertex_node_item in enumerate(adj_matrix.vertexs):
if vertex_node.name == vertex_node_item.name:
return index
def create_adj_matrix(adj_matrix: AdjMatrix, vertexs: List[VertexNode],
arcs: List[Union[Tuple[VertexNode, VertexNode, int], Tuple[VertexNode, VertexNode, float]]],
graph_kind: str = 'DN'):
"""
创建一个邻接矩阵
:param adj_matrix: 一个空的邻接矩阵
:param vertexs: 顶点列表
:param arcs: 弧列表
:param graph_kind: 所创建的图的种类:DG表示有向图,DN表示有向网,UDG表示无向图,UDN表示无向图
:return: None
"""
adj_matrix.vertexs = vertexs # 设置该邻接矩阵对象的顶点列表
adj_matrix.arc_num = len(arcs) # 设置该邻接矩阵对象的弧结点个数
adj_matrix.vex_num = len(adj_matrix.vertexs) # 设置该邻接矩阵对象的顶点结点个数
adj_matrix.graph_kind = graph_kind # 设置该邻接矩阵对象所表示的图的类型
adj_matrix.arcs_matrix = np.ones([adj_matrix.vex_num, adj_matrix.vex_num]) * graph_kind_[graph_kind] # 为该邻接矩阵对象创建邻接矩阵
for arc in arcs: # 获得一条弧的两个顶点及权值
arc_node = ArcNode(*arc) # 创建弧结点
i = locate_vertex(adj_matrix, arc_node.arc_tail) # 获得弧尾结点的位置
j = locate_vertex(adj_matrix, arc_node.arc_head) # 获得弧头结点的位置
if graph_kind == "DN" or graph_kind == "DG": # 在邻接矩阵中将相应位置设置为弧的权重,相当于建立弧
adj_matrix.arcs_matrix[i][j] = arc_node.weight
else:
adj_matrix.arcs_matrix[i][j] = arc_node.weight
adj_matrix.arcs_matrix[j][i] = arc_node.weight
if graph_kind == 'DN' or graph_kind == 'UDN':
for i in range(adj_matrix.vex_num):
adj_matrix.arcs_matrix[i][i] = 0
visited = array('i', []) # 访问标志数组
trav_seq = deque() # 遍历队列
def depth_first_search_recursion(adj_matrix: AdjMatrix, vertex_node: VertexNode):
"""
深度遍历图
算法思想:
(1) 访问出发点v0;
(2) 依次以v0的未被访问的临界点为出发点,深度优先搜索图,直至图中所有与v0有路径相通的顶点都被访问。
:param adj_matrix: 已创建好的邻接矩阵对象
:param vertex_node: 访问出发点v0
:return: 深度遍历序列
"""
vi_index = locate_vertex(adj_matrix, vertex_node) # 获取当前顶点节点在ad_matrix中的下标
trav_seq.append(adj_matrix.vertexs[vi_index]) # 访问当前顶点结点,放入访问对列中
visited[vi_index] = 1 # 将访问过的当前顶点结点标志置为1,表示已访问过
for vj_index in range(adj_matrix.vex_num): # 寻找当前结点的所有邻接顶点结点
# 判断adj_matrix.arcs_matrix[vi_index][vj_index]是否是当前顶点结点的邻接顶点结点,并判断是否被访问过,访问过不能入栈
if not visited[vj_index] and 0 < adj_matrix.arcs_matrix[vi_index][vj_index] <= graph_kind_['DN']:
depth_first_search_recursion(adj_matrix, adj_matrix.vertexs[vj_index]) # 对当前顶点结点的邻接点顶点结点进行深度遍历
return trav_seq # 返回深度遍历序列
def depth_first_search_nonrecursion(adj_matrix: AdjMatrix, vertex_node: VertexNode):
"""
深度非递归遍历图
算法思想:
(1) 首先将v0入栈
(2) 只要栈不空,则重复下述处理:栈顶顶点出栈,如果未访问,则访问并置访问标志;然后将该顶点所有未访问的邻接点入栈。
:param adj_matrix: 已创建好的邻接矩阵对象
:param vertex_node: 访问出发点v0
:return: 深度遍历序列
"""
temp_stack = deque() # 栈
temp_stack.append(vertex_node) # 将起始顶点结点入栈
while temp_stack: # 循环直到栈为空为止
vi_node = temp_stack.pop() # 将栈顶顶点结点出栈成为当前结点
vi_index = locate_vertex(adj_matrix, vi_node) # 获取当前顶点结点在邻接表adj_list的顶点列表中的下标
if not visited[vi_index]: # 判断当前顶点结点是否被访问过,对于同一个顶点结点栈中可能出现多次,重复出现的在第一次出栈的时候已被访问过
trav_seq.append(vi_node) # 将当前顶点结点放入访问队列
visited[vi_index] = 1 # 将当前顶点节点设置为1,表示已被访问
for vj_index in range(adj_matrix.vex_num): # 将所有以当前顶点结点为弧尾顶点结点的的弧的弧头结点入栈
# 判断adj_matrix.arcs_matrix[vi_index][vj_index]是否是当前顶点结点的邻接顶点结点,并判断是否被访问过,访问过不能入栈
if not visited[vj_index] and 0 < adj_matrix.arcs_matrix[vi_index][vj_index] <= graph_kind_["DN"]:
temp_stack.append(adj_matrix.vertexs[vj_index])
return trav_seq # 返回以当前顶点结点为起始节点的深度遍历序列
def breadth_first_search_nonrecursion(adj_matrix: AdjMatrix, vertex_node: VertexNode):
temp_queue = deque() # 队列
temp_queue.append(vertex_node) # 将起始顶点结点入对
while temp_queue: # 循环直到队列为空为止
vi_node = temp_queue.popleft() # 将栈顶顶点结点出对成为当前结点
vi_index = locate_vertex(adj_matrix, vi_node) # 获取当前顶点结点在邻接表adj_list的顶点列表中的下标
if not visited[vi_index]: # 判断当前顶点结点是否被访问过,对于同一个顶点结点对中可能出现多次,重复出现的在第一次出对的时候已被访问过
trav_seq.append(vi_node) # 将当前顶点结点放入访问队列
visited[vi_index] = 1 # 将当前顶点节点设置为1,表示已被访问
for vj_index in range(adj_matrix.vex_num): # 将所有以当前顶点结点为弧尾顶点结点的的弧的弧头结点入对
# 判断adj_matrix.arcs_matrix[vi_index][vj_index]是否是当前顶点结点的邻接顶点结点,并判断是否被访问过,访问过不能入对
if not visited[vj_index] and 0 < adj_matrix.arcs_matrix[vi_index][vj_index] <= graph_kind_["DN"]:
temp_queue.append(adj_matrix.vertexs[vj_index])
return trav_seq # 返回以当前顶点结点为起始节点的广度遍历序列
class TravVistor:
"""
遍历访问者
访问者设计模式:使用同一个方法对不同对象访问具有不同的行为
对不同的遍历方法使用同一个方法traver_graph进行遍历
"""
__recursion_nonrecursion = {"r": "recursion", "nr": "nonrecursion"} # 递归遍历与非递归遍历
__depth_breadth = {"d": "depth", "b": "breadth"}
def __init__(self, adj_list, vertex_node, depth_breadth: str, recursion_nonrecursion: str):
self.adj_list = adj_list
self.vertex_node = vertex_node
self.depth_breadth = depth_breadth
self.recursion_nonrecursion = recursion_nonrecursion
def traver_graph(self):
func_name = f'{self.__depth_breadth[self.depth_breadth]}_first_search_{self.__recursion_nonrecursion[self.recursion_nonrecursion]}'
func_obj = getattr(self, func_name, None)
return func_obj()
def depth_first_search_recursion(self):
return depth_first_search_recursion(self.adj_list, self.vertex_node)
def depth_first_search_nonrecursion(self):
return depth_first_search_nonrecursion(self.adj_list, self.vertex_node)
def breadth_first_search_nonrecursion(self):
return breadth_first_search_nonrecursion(self.adj_list, self.vertex_node)
def travers_graph(adj_matrix: AdjMatrix, deepth_breadth: str = "d", recursion_nonrecursion: str = "nr"):
"""
遍历图
算法思想:
若是非连通图,则途中一定还有顶点未被访问,需要从途中另选一个未被访问过的顶点作为起始顶点,重复深度优先搜索过程,
直至图中所有的顶点均被访问过为止。
:param adj_list: 已创建好的邻接表对象
:param deepth_breadth: 选用深度deepth_breadth = 'd', 选用广度deepth_breadth = 'b'
:param recursion_nonrecursion: 选用递归way = 'r' ,选用非递归way = 'nr'
:return: 返回多个连通子图的遍历序列
"""
travers_list = deque()
for vi_index in range(adj_matrix.vex_num): # 初始化visited
visited.append(0)
for vi_index in range(adj_matrix.vex_num): # 循环调用遍历方法来遍历连通子图的操作,若图adj_list是连通图,则此调用只执行一次
if not visited[vi_index]:
trav_vistor = TravVistor(adj_matrix, adj_matrix.vertexs[vi_index], deepth_breadth,
recursion_nonrecursion) # 创建一个遍历访问者对象
travers_list.append(trav_vistor.traver_graph().copy()) # 将遍历结果添加到travers_list
trav_seq.clear() # 清空trav_seq,以便下一轮的使用
return travers_list # 返回多个连通子图的遍历序列
def min_(closedage) -> int:
temp_weight = 32768
temp_index = 0
# print(closedage)
for i in range(0, len(closedage)):
if closedage[i][1] != 0 and temp_weight > closedage[i][1]:
# print(temp_index, temp_weight)
temp_weight = closedage[i][1]
temp_index = i
return temp_index
def mini_span_tree_prim(adj_matrix: AdjMatrix, vertex_start: VertexNode):
"""
最小生成树:普利姆算法:加点法,适用于稠密网
算法思想:
(1) 首先将初始顶点vertex_start加入到U中,对其余的每一个顶点i,将closedge[i]初始化为i到vertex_start边信息;
(2) 循环n-1次,做如下处理:
a. 从各组最小边closedge[v]中选出最小的最小边closedge[k](v,k属于V-U)
b. 将k加入到U中
c. 更新剩余的每组最小边信息closedge[v](v属于V-U)
#closedge[v] = List[List[adjvex, lowcost]]
a. adjvex 记录最小边在U中的那个顶点
b. lowcost 存储最小边的权值
算法时间复杂度:
由于算法中有两个for循环嵌套,故它的时间复杂度为O(n**2)
:param adj_matrix:
:param vertex_start:
:return:
"""
vk_index = locate_vertex(adj_matrix, vertex_start)
vex_num = adj_matrix.vex_num
closedage = []
edges = deque()
for i in range(vex_num): # 初始化closedge
if i == vk_index:
closedage.append([adj_matrix.vertexs[vk_index], 0])
else:
closedage.append([adj_matrix.vertexs[vk_index], adj_matrix.arcs_matrix[vk_index][i]])
for i in range(1, vex_num): # 找n-1条边
k = min_(closedage) # closedge[k]中存有当前最小边(arc_tial, arc_head)的信息
arc_tail = closedage[k][0] # arc_tail属于U
arc_head = adj_matrix.vertexs[k] # arc_head属于V-U
edges.append((arc_tail, arc_head, closedage[k][1])) # 保存生成树的当前最小边(arc_tial, arc_head, weight)
closedage[k][1] = 0 # 将顶点arc_tail纳入U集合
for j in range(vex_num): # 在顶点arc_tail编入U之后,更新closedge[j]
if adj_matrix.arcs_matrix[k][j] < closedage[j][1]:
closedage[j][1] = adj_matrix.arcs_matrix[k][j]
closedage[j][0] = arc_head
return edges
def adj_matrix_arcs(adj_matrix: AdjMatrix):
# 此函数可进行进一步优化,此处只是为了实现功能,因对于UDG和UDN来说,邻接矩阵是对称阵,用以下算法会占用两倍的空间
arcs = []
if adj_matrix.graph_kind == "UDG" or adj_matrix.graph_kind =="DG":
for i in range(adj_matrix.vex_num):
for j in range(adj_matrix.vex_num):
if adj_matrix.arcs_matrix[i][j] == 1:
arcs.append((adj_matrix.vertexs[i], adj_matrix.vertexs[j], 1))
else:
for i in range(adj_matrix.vex_num):
for j in range(adj_matrix.vex_num):
if adj_matrix.arcs_matrix[i][j] < 32768:
arcs.append((adj_matrix.vertexs[i], adj_matrix.vertexs[j], adj_matrix.arcs_matrix[i][j]))
return arcs
def mini_span_tree_kruskal(adj_matrix: AdjMatrix):
"""
最小生成树:克鲁斯卡尔算法:加边发,适合于稀疏网
算法思想:
假设N = (V, {#})是联通图,将N中的边按边权值从小到大的顺序排列。
(1) 将n个顶点看成n个集合。
(2) 按权值由小到大的顺序选择边,所选边应满足两个顶点不在同一个顶点集合内,将改变放到生成树边的集合中,同时将该边的
两个顶尖所在的顶点集合合并
(3) 重复(2)直到所有的顶点都在同一个顶点集合内
算法时间复杂度:
克鲁斯卡尔算法的时间复杂度主要由排序方法决定,而克鲁斯卡尔算法的排序方法只与网中边的条数有关,而与网中顶点的个数无
关,当使用时间复杂度为O(elog2e)的排序方法时,克鲁斯卡尔算法的时间复杂度即为O(log2e),因此当网的顶点个数较多、
而边的条数较少时,使用克鲁斯卡尔算法构造最小生成树效果较好
:param adj_matrix: 邻接矩阵
:return: 最小生成树的边序列
"""
awaited_edges = adj_matrix_arcs(adj_matrix) # 从邻接矩阵中获取所有的边
vex_index_dict = {} # 用于存放邻接矩阵中顶点结点在其顶点列表vertexs中元素与索引的键值对,因所编写的并查集只接受数值型和字符串类型的值
for index, vertex_node in enumerate(adj_matrix.vertexs): # 初始化vex_index_dict
vex_index_dict.setdefault(vertex_node, index)
from mf_set import MFSet, SeqList
vex_set = MFSet() # 并查集对象,包含了所有顶点集合之间的关系
vex_set.initialization(SeqList([i for i in range(index+1)])) # 初始化vex_set
tree_edges = deque() # 用于保存最小生成树的边序列
abc = lambda edge: edge[2] # 表示按边的权重降序排序
awaited_edges = sorted(awaited_edges, key=abc, reverse=True) # 对边按权重进行降序排序
while awaited_edges: # 生成n-1条边,权值最小
edge_min = awaited_edges.pop() # 获取权重最小的一条边
i = vex_set.find_2(vex_index_dict[edge_min[0]]) # 查找当前边中一个顶点所在树的根
j = vex_set.find_2(vex_index_dict[edge_min[1]]) # 查找当前边中另一个顶点所在树的根
if i == j: # 当前边的两个端点位于同一个顶点集中,出现回路,抛弃该边
continue
tree_edges.append(edge_min) # 将当前边放入最小生成树的边序列
vex_set.merge(i, j) # 合并当前边的两个端点所在的顶点集
return tree_edges # 返回最小生成树的边序列
def find_id(adj_matrix: AdjMatrix, indegree: array):
for i in range(adj_matrix.vex_num):
indegree.append(0)
for arc_head_index in range(adj_matrix.vex_num):
for arc_tail_index in range(adj_matrix.vex_num):
if adj_matrix.arcs_matrix[arc_tail_index][arc_head_index] == 1:
indegree[arc_head_index] += 1
return indegree
def topo_sort(adj_matrix: AdjMatrix):
"""
拓扑排序:邻接矩阵
拓扑排序:
在有向图G=(V, {E})中,V中顶点的线性序列(vi1,vi2,vi3,...,vin)称为拓扑序列。如果此序列满足条件:对序列中任意两个顶点
vi,vj,在G中有一条从vi到vj的路径,则在序列中vi必排在vj之前。
拓扑排序基本思想:
(1) 从有向图中选一个无前驱的结点输出;
(2) 将此结点和以它为起点的边删除
(3) 重复(1)(2),直到不存下来无前驱的结点
(4) 若此时输出的结点数小于有向图的顶点数,则说明有向图中存在回路,否则输出的顶点顺序即为一个拓扑序列
基于邻接矩阵的存储结构:
此时入度为0的顶点即没有前驱的顶点,因此可以附设一个存放各顶点入度的数组indegree[],于是有:
(1) 找G中无前驱的顶点--查找indegree[i]为零的顶点i;
(2) 删除以i为起点的所有弧--对链在顶点i后面的所有临界顶点k,将对应的indegree[k]减1
为了避免重复检测入读为0的顶点,可以再设置一个辅助栈,若某一顶点的入度减为0,则将它入栈,每当输出
某一顶点时,便将它从栈种删除。
算法思想:
(1) 首先求出各顶点的入度,并将入度为0的顶点入栈。
(2) 只要栈不空,则重复下面处理:
a. 将栈顶顶点i出栈并打印
b. 将栈顶顶点i的每一个邻接点k的入度减1,如果顶点k的入度变为0,则将顶点k入栈。
算法时间复杂度:
若有向无环图有n个顶点和e条弧,则在拓扑排序的算法中,for循环需要执行n次,时间复杂度为O(n);对于while循
环,由于每一顶点必定出栈一次,出一次栈,其时间复杂度为O(n),每次都有一个n列的循环;故该算法的时间复杂
度为O(n+n**2),即O(n**2)
:param adj_matrix: 需要拓扑排序的邻接矩阵
:return: 拓扑排序序列
"""
indegree = array('i', []) # 入度计数
temp_stack = deque() # 辅助栈,避免重复检测入度为0的顶点
vex_seq = deque() # 用于保存排好序的顶点结点
counter = 0 # 用于记录顶点个数,便于最后判断AOV网中是否存在回路
indegree = find_id(adj_matrix, indegree)
for index, value in enumerate(indegree): # 初始化temp_stack
if value == 0:
temp_stack.append((adj_matrix.vertexs[index], index)) # 将入度为0的结点及其在adj_matrix.vertexs列表中的下标构成一个元组入栈
indegree[index] -= 1 # 若没有此语句当有多个入度为0的顶点时,在进入出栈访问时,会导致重复入栈
while temp_stack: # 循环直到栈为空为止
vertex_node_index = temp_stack.pop() # 栈顶顶点出栈
vex_seq.append(vertex_node_index[0]) # 访问当前顶点,进行保存
counter += 1 # 计数,当前顶点是序列中的第几个顶点
arc_tail_index = vertex_node_index[1]
for arc_head_index in range(adj_matrix.vex_num):
if adj_matrix.arcs_matrix[arc_tail_index][arc_head_index] == 1: # 为1表示两个顶点结点之间有弧
indegree[arc_head_index] -= 1
if indegree[arc_head_index] == 0:
# 表示adj_list.vertexs[arc_head_index]该顶点结点已没有前驱结点需对其入栈
temp_stack.append((adj_matrix.vertexs[arc_head_index], arc_head_index))
indegree[arc_head_index] -= 1 # 当在该轮出栈访问循环中有多个减为0入栈的顶点时,如果没有此语句在下轮出栈访问时会导致重复入栈
if counter < adj_matrix.vex_num: # 判断AOV网中是否存在回路
return "ERROR" # 表示AOV网中存在回路
return vex_seq # 成功返回拓扑序列
def min__(dist) -> int:
min_value = graph_kind_['DN']
min_p = None
for index, value in enumerate(dist):
if value and min_value > value:
min_value = value
min_p = index
print(min_p, )
return min_p
def shortest_path_djs(adj_matrix: AdjMatrix, vertex_node: VertexNode):
"""
迪杰斯特拉算法:邻接矩阵
最短路径:求一个顶点到其他各顶点的最短路径
算法中使用了辅助数组dist[],dist[i]表示目前已经找到的、从开始点v0到终点vi的当面最短路径的长度。它的初值为:如
果v0到vi有弧,则dist[i]为弧的权值;否则dist[i]为 ∞。
长度最短的一条最短路径必为(v0,vk),vk满足如下条件:
dist[k] = Min{dist[i]|vi∈V-S}
求得顶点vk的最后路径后,将vk加入到第一组顶点集S中。
每加入一个新的顶点vk到顶点集S中,对第二组剩余的各个顶点而言,就多了一个"中转"结点,从而多了一个"中转"路径,所
以要对第二组剩余的各个顶点的最短路径长度dist[i]进行修正。
原来v0到vi的最短路径长度为dist[i],加紧vk后,已vk作为中间顶点的"中转"路径长度为dist[k]+wki,(wki为弧<vk, vi>上
的权值),若“中转”路径长度小于dist[i],则将顶点vi的最短路径长度修正为“中转”路径长度。
修正后,在选择数组dist[]中最小的顶点加入到第一组顶点集S中,如此进行下去,直到图中所有顶点都加入到第一组顶点集S
中为止。
另外,为了记录从v0出发到其余各点的最短路径(顶点序列),引进辅助数组paths[],path[i]表示目前已经找到的、从开始点
v0到终点vi的当前最短路径顶点序列。它的初值为:如果从v0到vi有弧,则path[i]为(v0,vi);否则path[i]为空。
算法思想:
(1) S←{v0};
dist[i] = g.arcs[v0][vi].adj(vi∈V-S)
(将v0到其余顶点的最短路径长度初始化为权值)
(2) 选择vk,使得dist[k] = Min(dist[i]|vi∈V-S),vk为目前求得的下一条从v0出发的最短路径的终点;
(3) 将vk加入S;
(4) 修正从v0出发到集合V-S上任一顶点vi的最短路径的长度;
从vo出发到集合V-S上任一顶点vi的当前最短路径的长度为dist[i],
从v0出发,中间经过新加入S的vk,然后到达集合V-S上任一顶点vi的路径长度为:
dist[k]+g.arcs[k][i].adj
如果dist[k] + g.arcs[k][i].adj < dist[i],则dist[i]=dist[k] + g.arcs[k][i].adj。
(5) 重复(2)-(4)共n-1次,即可按最短路径长度的递增循序,主格求出v0到途中其他每个顶点的最短路径。
算法时间复杂度:
算法前半部分完成了对向量最短路径长度dist[]、路径path[]及顶点集S的初始化工作。
算法后半部分通过n-1次循环,将第二组顶点集V-S中的顶点按照递增有序方式加入到集合S中,并求得从顶
点v0出发到达图中其余顶点的最短路径。
显然,算法的时间复杂度为O(n**2)
:param adj_matrix: 创建好的邻接矩阵对象,即图g
:param vertex_node: 起始顶点
:return: paths, paths_weight
"""
paths = deque() # 存放起始顶点到达不同顶点的路径,一个二维列表
paths_weight = deque() # 存放起始顶点到达不同顶点的路径长度
dist = deque() # 辅助列表
for i in range(adj_matrix.vex_num): # 为初始化作准备
paths.append(deque())
paths_weight.append(None)
dist.append(0)
vertex_index = locate_vertex(adj_matrix, vertex_node) # 获取起始顶点在adj_matrix.vertexs中的下标
for i in range(adj_matrix.vex_num): # 初始化
if vertex_index == i: # 开始时将起始顶点移入S集合
dist[i] = None # 对于移入S集的顶点dist[i]设置为None,避免多次被访问
paths[i].append(adj_matrix.vertexs[i]) # 将起始顶点自身作为其路径,加入其路径序列
paths_weight[i] = 0 # 起始顶点路径长度为0,并保存
else: # 对非起始顶点进行初始化操作
dist[i] = adj_matrix.arcs_matrix[vertex_index][i] # 非起始顶点的dist[i]为起始顶点到该顶点弧权,无弧的此处默认为32768
if adj_matrix.arcs_matrix[vertex_index][i] < graph_kind_['DN']: # 初始化从起始顶点到每个可达的非起始顶点路径
paths[i].append(adj_matrix.vertexs[vertex_index]) # 起始顶点作为路径的第一个顶点
paths[i].append(adj_matrix.vertexs[i]) # 非起始顶点作为路径的第二个顶点
for i in range(adj_matrix.vex_num): # 循环n-1次,因计算的是从起始顶点到其余n-1各顶点的最短路径
arc_tail_index = min__(dist) # 从dist中获取路径长度最小的下标
if not arc_tail_index: # 下标返回None时表明从起始顶点到其余n-1各顶点的最短路径已计算完成
return paths, paths_weight # 返回计算结果
for arc_head_index in range(adj_matrix.vex_num): # 对还未访问到的非起始节点的dist[i]以及paths[i]进行修改
if dist[arc_head_index] and adj_matrix.arcs_matrix[arc_tail_index][arc_head_index] < graph_kind_['DN'] \
and dist[arc_tail_index] + adj_matrix.arcs_matrix[arc_tail_index][arc_head_index] < \
dist[arc_head_index]: # 满足条件的才可修改
dist[arc_head_index] = dist[arc_tail_index] + adj_matrix.arcs_matrix[arc_tail_index][arc_head_index]
paths[arc_head_index] = paths[arc_tail_index].copy()
paths[arc_head_index].append(adj_matrix.vertexs[arc_head_index]) # 结点自身作为路径的终止点
paths_weight[arc_tail_index] = dist[arc_tail_index] # 保存路径长度
dist[arc_tail_index] = None # 修改已访问过的非起始顶点结点dist[i]值,避免被多次访问
def shortest_path_floyd(adj_matrix: AdjMatrix):
"""
求任一对顶点间的最短路径:弗洛伊德算法:邻接矩阵
算法思想:
设g用邻接矩阵表示,求图g中任意对顶点vi与vj间的最短路径(以下序号是D,P两个矩阵的下表)
(-1)将vi到vj的最短的路径长度初始化为g.arcs[i][j].adj,然后进行如下n次比较和修正:
(0) 在vi与vj间加入顶点v0,得到(vi,v0,vj)和(vi,vj)的路径长度,取其中较短的路径作为vi到vj的且中间顶点号
不大于0的最短路径。
(1) 在vi与vj间加入顶点v1,得到(vi,...,v1)和(v1,...,vj),其中(vi,...,v1)是vi到v1的且中间顶点号不大于0的最
短路径,(v1,...,v1,...,vj)与上一步已求出的且vi到vj中间顶点号不大于0的最短路径比较,取其中较短的路径
作为vi到vj的且中间顶点号不大于1的最短路径
(2) 在vi与vj间加入顶点v2,得(vi,...,v2)和(v2,...,vj),其中(vi,...,v2)是vi到v2的且中间顶点号不大于1的最
短路径,(v2,...,vj)是v2到vj的且中间顶点号不大于1的最短路径,这两条路径在上一步中已求出。将(vi,...,v2,
...,vj)与上一步已求出的且vi到vj中间顶点号不大于1的最短路径比较,取其中较短的路径作为vi到vj的且中间顶
点号不大于2的最短路径。
..........
以此类推,经过n次比较和修正,在第n-1步,将求得vi到vj的且中间顶点号不大于n-1的最短路径,这必是从vi到vj
的最短路径。
图g中所有顶点偶对vi与vj间的最短路径长短对应一个n阶方阵D。在上述N+1步中,D的值不断变化,对应一个n阶方
阵序列。
算法时间复杂度:
O(n**3)
:param adj_matrix: 已创建好的邻接矩阵
:return: 返回各路径与路径长度信息
"""
paths = deque() # 用于存放路径
dist = adj_matrix.arcs_matrix.copy() # 初始化dist
for i in range(adj_matrix.vex_num): # 初始化paths
paths.append(deque())
for j in range(adj_matrix.vex_num):
paths[i].append(deque())
if adj_matrix.arcs_matrix[i][j] < graph_kind_['DN'] and adj_matrix.arcs_matrix[i][j] != 0:
paths[i][j].append(adj_matrix.vertexs[i]) # 将弧尾顶点结点保存到路径中
paths[i][j].append(adj_matrix.vertexs[j]) # 将弧头顶点结点保存到路径中
for k in range(adj_matrix.vex_num): # 共n个顶点,每次循环将一个顶点作为两个不同结点之间的连接顶点
for i in range(adj_matrix.vex_num): # dist 共n行
for j in range(adj_matrix.vex_num): # dist 共n列
if k == i or k == j or i == j:
continue
weight_i_k_j = dist[i][k] + dist[k][j] # 以vk作为中间连接点计算从vi到vk的路径长度
if weight_i_k_j < dist[i][j]:
# 以vk作为中间连接点从vi到vk的路径长度小于从vi到vk的原路径长度,需要更新dist[i][j],path[i][j]
dist[i][j] = weight_i_k_j
temp_path = paths[i][k].copy() # 注意必须copy(),否则在更新过程中会出错
temp_path.pop() # 因paths[i][k]的最后一个顶点结点与paths[k][j]的第一个顶点结点重复
temp_path.extend(paths[k][j].copy()) # 注意必须copy(),否则在更新过程中会出错
paths[i][j] = temp_path
return paths, dist # 返回各路径与路径长度信息
def center_vex(adj_list: AdjMatrix):
paths, path_weight = shortest_path_floyd(adj_list)
temp_row_index = 0
temp_row_sum = graph_kind_['DN']*adj_list.vex_num
path_row_sum = 0
for row in range(adj_list.vex_num):
for col in range(adj_list.vex_num):
path_row_sum += path_weight[row][col]
if temp_row_sum > path_row_sum:
temp_row_sum = path_row_sum
temp_row_index = row
return adj_list.vertexs[temp_row_index], paths[temp_row_index], path_weight[temp_row_index], temp_row_sum
test_adj_matrix.py
from graph.adj_matrix import VertexNode, ArcNode, AdjMatrix, create_adj_matrix, travers_graph, mini_span_tree_prim, \
mini_span_tree_kruskal, topo_sort, shortest_path_djs, shortest_path_floyd, center_vex
if __name__ == '__main__':
# v1 = VertexNode('v1')
# v2 = VertexNode('v2')
# v3 = VertexNode('v3')
# v4 = VertexNode('v4')
# v5 = VertexNode('v5')
# v6 = VertexNode('v6')
#
# arc1 = (v1, v2, 5)
# arc2 = (v1, v4, 7)
# arc3 = (v2, v3, 4)
# arc4 = (v3, v1, 8)
# arc5 = (v3, v6, 9)
# arc6 = (v4, v3, 5)
# arc7 = (v4, v6, 6)
# arc8 = (v5, v4, 5)
# arc9 = (v6, v1, 2)
# arc10 = (v6, v5, 1)
#
# adj_martix = AdjMatrix()
# vertexs = [v1, v2, v3, v4, v5, v6]
# arcs = [arc1, arc2, arc3, arc4, arc5, arc6, arc7, arc8, arc9, arc10]
# create_adj_matrix(adj_martix, vertexs, arcs, 'DN')
# print(adj_martix.arcs_matrix)
# travs_list = travers_graph(adj_martix, 'b', 'nr')
# for trav_seq in travs_list:
# for vertex_node in trav_seq:
# print(vertex_node.name)
# v1 = VertexNode('v1')
# v2 = VertexNode('v2')
# v3 = VertexNode('v3')
# v4 = VertexNode('v4')
# v5 = VertexNode('v5')
# v6 = VertexNode('v6')
#
# arc1 = (v1, v2, 6)
# arc2 = (v2, v5, 3)
# arc3 = (v5, v6, 6)
# arc4 = (v4, v6, 2)
# arc5 = (v1, v4, 5)
# arc6 = (v1, v3, 1)
# arc7 = (v2, v3, 5)
# arc8 = (v5, v3, 6)
# arc9 = (v6, v3, 4)
# arc10 = (v4, v3, 5)
#
# adj_matrix = AdjMatrix()
# vertexs = [v1, v2, v3, v4, v5, v6]
# arcs = [arc1, arc2, arc3, arc4, arc5, arc6, arc7, arc8, arc9, arc10]
# create_adj_matrix(adj_matrix, vertexs, arcs, 'UDN')
# edges = mini_span_tree_kruskal(adj_matrix)
# for edge in edges:
# print((edge[0].name, edge[1].name, edge[2]))
# 测试torpo_sort方法
# v1 = VertexNode('v1')
# v2 = VertexNode('v2')
# v3 = VertexNode('v3')
# v4 = VertexNode('v4')
# v5 = VertexNode('v5')
# v6 = VertexNode('v6')
#
# arc1 = (v1, v2, 1)
# arc2 = (v1, v3, 1)
# arc3 = (v1, v4, 1)
# arc4 = (v4, v5, 1)
# arc5 = (v6, v4, 1)
# arc6 = (v6, v5, 1)
# arc7 = (v3, v5, 1)
# arc8 = (v3, v2, 1)
#
# adj_matrix = AdjMatrix()
# vertexs = [v1, v2, v3, v4, v5, v6]
# arcs = [arc1, arc2, arc3, arc4, arc5, arc6, arc7, arc8]
# create_adj_matrix(adj_matrix, vertexs, arcs, 'DG')
# topo_seq = topo_sort(adj_matrix)
# # print(topo_seq)
# topo_seq = ",".join([vertex_node.name for vertex_node in topo_seq])
# print(topo_seq)
# 测试迪杰斯特拉算法
# v0 = VertexNode('v0')
# v1 = VertexNode('v1')
# v2 = VertexNode('v2')
# v3 = VertexNode('v3')
# v4 = VertexNode('v4')
# v5 = VertexNode('v5')
#
# arc1 = (v0, v1, 50)
# arc2 = (v0, v2, 10)
# arc3 = (v0, v4, 45)
# arc4 = (v1, v2, 15)
# arc5 = (v1, v4, 10)
# arc6 = (v2, v0, 20)
# arc7 = (v2, v3, 15)
# arc8 = (v3, v1, 20)
# arc9 = (v3, v4, 35)
# arc10 = (v4, v3, 30)
# arc11 = (v5, v3, 3)
#
# adj_matrix = AdjMatrix()
# vertexs = [v0, v1, v2, v3, v4, v5]
# arcs = [arc1, arc2, arc3, arc4, arc5, arc6, arc7, arc8, arc9, arc10, arc11]
# create_adj_matrix(adj_matrix, vertexs, arcs, 'DN')
# # print(topo_seq)
# paths, paths_weight = shortest_path_djs(adj_matrix, v0)
# # print(paths)
# for i in range(adj_matrix.vex_num):
# # print(paths[i])
# # if len(paths[i]) != 0:
# # print(','.join([vex_node.name for vex_node in paths[i]]))
# print((','.join([vex_node.name for vex_node in paths[i]]), paths_weight[i]))
# 测试弗洛伊德算法
# v0 = VertexNode('A')
# v1 = VertexNode('B')
# v2 = VertexNode('C')
#
# arc1 = (v0, v1, 4)
# arc2 = (v1, v0, 12)
# arc3 = (v1, v2, 5)
# arc4 = (v2, v0, 6)
#
#
# adj_matrix = AdjMatrix()
# vertexs = [v0, v1, v2]
# arcs = [arc1, arc2, arc3, arc4]
# create_adj_matrix(adj_matrix, vertexs, arcs, 'DN')
# # print(topo_seq)
# paths, paths_weight = shortest_path_floyd(adj_matrix)
# # print(paths)
# print(paths_weight)
# for i in range(adj_matrix.vex_num):
# for j in range(adj_matrix.vex_num):
# if paths[i][j]:
# print(','.join([vex_node.name for vex_node in paths[i][j]]), paths_weight[i][j])
# 测试寻找中心点center_vex算法
v0 = VertexNode('A')
v1 = VertexNode('B')
v2 = VertexNode('C')
arc1 = (v0, v1, 4)
arc2 = (v1, v0, 12)
arc3 = (v1, v2, 5)
arc4 = (v2, v0, 6)
adj_matrix = AdjMatrix()
vertexs = [v0, v1, v2]
arcs = [arc1, arc2, arc3, arc4]
create_adj_matrix(adj_matrix, vertexs, arcs, 'UDN')
centre_vertex, paths, paths_weight, sum_weight = center_vex(adj_matrix)
print((centre_vertex.name, sum_weight))
print("具体每条路径".center(50, '*'))
for i in range(adj_matrix.vex_num):
if paths[i]:
print(','.join([vex_node.name for vex_node in paths[i]]), paths_weight[i])