"""
邻接表
:class VertexNode: 顶点结点类
:class ArcNode: 弧结点类
:class AdjList: 邻接表类
:method locate_vertex: 查找某个顶点结点在AdjList对象中的成员vertexs列表中的下标
:method create_adj_list: 创建一个邻接表对象
depth_first_search_recursion: 深度优先递归遍历
depth_first_search_nonrecursion: 深度优先非递归遍历
breadth_first_search_nonrecursion: 广度优先非递归遍历
:class TravVistor: 访问者类,对深度优先递归、深度优先非递归、广度优先非递归的访问
:method travers_graph: 遍历接口,设置不同参数利用TravVistor对象实现深度优先递归、深度优先非递归、广度优先非递归调用
:one_path: 寻找两个顶点间的简单路径
:method topo_sort: 拓扑排序
:method critical_path: 寻找关键路径
:method bfs_k_level: 从某一顶点开始找到距其路径长度为k以内的所有顶点
:method hamilton: 在图g中找出一条包含所有顶点的简单路径
"""
from typing import List, Tuple, Union
from collections import deque, namedtuple
from array import array
class VertexNode:
def __init__(self, name: str):
self.name = name
self.first_arc: ArcNode = None
class ArcNode:
def __init__(self, arc_head_index: int, next_arc: VertexNode = None, weight: Union[int, float] = 0):
self.arc_head_index = arc_head_index
self.next_arc = next_arc
self.weight = weight
self.otherInfo = None
graph_kind_ = {"DG": 0, "DN": 32768, "UDG": 0, "UDN": 32768}
class AdjList:
def __init__(self):
self.vertexs: List = []
self.vex_num: int = 0
self.arc_num: int = 0
self.graph_king: str = ''
def locate_vertex(adj_list: AdjList, vertex_node: VertexNode):
"""
获取顶点的位置
:param adj_list: 在该邻接表对象的顶点列表中查找顶点位置
:param vertex_node: 所需查找位置的顶点
:return: 顶点的索引
"""
for index, vertex_node_item in enumerate(adj_list.vertexs):
if vertex_node.name == vertex_node_item.name:
return index
def create_adj_list(adj_list: AdjList, vertexs: List[VertexNode],
arcs: List[Union[Tuple[VertexNode, VertexNode, int], Tuple[VertexNode, VertexNode, float]]],
graph_kind: str = 'DN'):
adj_list.vertexs = vertexs
adj_list.vex_num = len(adj_list.vertexs)
adj_list.arc_num = len(arcs)
adj_list.graph_king = graph_kind
for arc in arcs:
arc_head_index = locate_vertex(adj_list, arc[1])
arc_node = ArcNode(arc_head_index, weight=arc[2])
arc_tail_index = locate_vertex(adj_list, arc[0])
arc_node.next_arc = adj_list.vertexs[arc_tail_index].first_arc
adj_list.vertexs[arc_tail_index].first_arc = arc_node
visited = array('i', [])
trav_seq = deque()
def depth_first_search_recursion(adj_list: AdjList, vertex_node: VertexNode):
"""
深度遍历图
算法思想:
(1) 访问出发点v0;
(2) 依次以v0的未被访问的临界点为出发点,深度优先搜索图,直至图中所有与v0有路径相通的顶点都被访问。
:param adj_list: 已创建好的邻接表对象
:param vertex_node: 访问出发点v0
:return: 深度遍历序列
"""
vi_index = locate_vertex(adj_list, vertex_node)
trav_seq.append(adj_list.vertexs[vi_index])
visited[vi_index] = 1
p = adj_list.vertexs[vi_index].first_arc
while p:
if not visited[p.arc_head_index]:
depth_first_search_recursion(adj_list, adj_list.vertexs[p.arc_head_index])
p = p.next_arc
return trav_seq
def depth_first_search_nonrecursion(adj_list: AdjList, vertex_node: VertexNode):
"""
深度非递归遍历图
算法思想:
(1) 首先将v0入栈
(2) 只要栈不空,则重复下述处理:栈顶顶点出栈,如果未访问,则访问并置访问标志;然后将该顶点所有未访问的邻接点入栈。
注意:
(1) 对于同一个顶点结点栈中可能出现多次。
(2) 访问既可以在入栈时访问也可以在出栈时访问,只不过都需在访问前进行判断,当顶点结点是否已被访问过。原因是注意(1)
:param adj_list: 已创建好的邻接表对象
: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_list, vi_node)
if not visited[vi_index]:
trav_seq.append(vi_node)
visited[vi_index] = 1
p = vi_node.first_arc
while p:
if not visited[p.arc_head_index]:
temp_stack.append(adj_list.vertexs[p.arc_head_index])
p = p.next_arc
return trav_seq
def breadth_first_search_nonrecursion(adj_list: AdjList, vertex_node: VertexNode):
"""
广度非递归遍历图
算法思想:
(1) 首先将v0入对
(2) 只要对不空,则重复下述处理:对头顶点出对,如果未访问,则访问并置访问标志;然后将该顶点所有未访问的邻接点入对。
注意:
(1) 对于同一个顶点结点队中可能出现多次。
(2) 访问既可以在入队时访问也可以在出队时访问,只不过都需在访问前进行判断,当顶点结点是否已被访问过。原因是注意(1)
:param adj_list: 已创建好的邻接表对象
:param vertex_node: 访问出发点v0
:return: 广度遍历序列
"""
temp_queue = deque()
temp_queue.append(vertex_node)
while temp_queue:
vi_node = temp_queue.popleft()
vi_index = locate_vertex(adj_list, vi_node)
if not visited[vi_index]:
trav_seq.append(vi_node)
visited[vi_index] = 1
p = adj_list.vertexs[vi_index].first_arc
while p:
if not visited[p.arc_head_index]:
temp_queue.append(adj_list.vertexs[p.arc_head_index])
p = p.next_arc
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_list: AdjList, 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_list.vex_num):
visited.append(0)
for vi_index in range(adj_list.vex_num):
if not visited[vi_index]:
trav_vistor = TravVistor(adj_list, adj_list.vertexs[vi_index], deepth_breadth, recursion_nonrecursion)
travers_list.append(trav_vistor.traver_graph().copy())
trav_seq.clear()
return travers_list
def one_path(adj_list: AdjList, vertex_start: VertexNode, vertex_end:VertexNode):
for i in range(adj_list.arc_num):
visited.append(0)
def dfs_path(adj_list_, vertex_start_, vertex_end_):
"""
深度遍历搜索两个指定顶点之间的简单路径
算法思想:
从顶点vertex_start_开始,进行深度(广度)优先遍历,如果能搜索到顶点vertex_end_,则表明从顶点vertex_start_
到顶点vertex_end_有一条路径。由于在搜索过程中,每个顶点只访问一次,所以这条路径必定是一条简单路径。因此,
只要在搜索过程中,把搜索的线路记录下来,并在搜索到顶点vj时退出搜索过程,就可得到从u到v的一条简单路径。
(1) 访问出发点vertex_start_;
(2) 依次以vertex_start_的未被访问的邻接点为出发点,深度优先搜索图,直至图中所有与vertex_start_有路径相通
的顶点都被访问。
:param adj_list_: 已创建好的邻接表对象
:param vertex_start_: 访问出发结点
:param vertex_end_: 终止结点
:return: 深度遍历路径
"""
vi_index = locate_vertex(adj_list_, vertex_start_)
trav_seq.append(adj_list_.vertexs[vi_index])
visited[vi_index] = 1
p = adj_list_.vertexs[vi_index].first_arc
while p:
if not visited[p.arc_head_index]:
if adj_list_.vertexs[p.arc_head_index].name == vertex_end_.name:
vj_index = locate_vertex(adj_list_, adj_list_.vertexs[p.arc_head_index])
trav_seq.append(adj_list_.vertexs[vj_index])
visited[vj_index] = 1
return 1
elif dfs_path(adj_list_, adj_list_.vertexs[p.arc_head_index], vertex_end_):
return 1
p = p.next_arc
dfs_path(adj_list, vertex_start, vertex_end)
return trav_seq
def find_id(adj_list: AdjList, indegree: array):
for i in range(adj_list.vex_num):
indegree.append(0)
for arc_tail_node in adj_list.vertexs:
arc = arc_tail_node.first_arc
while arc:
arc_head_index = arc.arc_head_index
indegree[arc_head_index] += 1
arc = arc.next_arc
return indegree
def topo_sort(adj_list: AdjList):
"""
拓扑排序:邻接表
AOV-网:
用顶点表示活动,用有向弧表示活动间的优先关系
拓扑排序:
在有向图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(e);故该算法的时间复杂度为O(n+e)
:param adj_list: # 需要拓扑排序的邻接表
:return: # 拓扑排序的结点序列
"""
indegree = array('i', [])
temp_stack = deque()
vex_seq = deque()
counter = 0
indegree = find_id(adj_list, indegree)
for index, value in enumerate(indegree):
if value == 0:
temp_stack.append(index)
while temp_stack:
arc_tail_index = temp_stack.pop()
vertex_node = adj_list.vertexs[arc_tail_index]
vex_seq.append(vertex_node)
counter += 1
arc = vertex_node.first_arc
while arc:
arc_head_index = arc.arc_head_index
indegree[arc_head_index] -= 1
if indegree[arc_head_index] == 0:
temp_stack.append(arc_head_index)
arc = arc.next_arc
if counter < adj_list.vex_num:
return "ERROR"
return vex_seq
def topo_sort_(adj_list: AdjList):
"""
算法思想:
(1) 首先求出各顶点的入度,并将入度为0的顶点入栈temp_stack;
(2) 将各顶点的最早发生时间vexs_early[i]初始化为0
(3) 只要栈tempa_stack不空,则重复下面处理;
a. 将栈顶顶点j出栈并压入栈T(生成逆拓扑序列)
b. 将顶点j的每一个邻接点k的入度减1,如果顶点k的入度变为0,则将顶点k入栈;
c. 根据顶点j 的最早发生时间vexs_early[j]和弧<j,k>的权值,更新顶点k的最早发生时间ve[k]。
:param adj_list:
:return:
"""
indegree = array('i', [])
temp_stack = deque()
vex_seq = deque()
counter = 0
vexs_early = array('i', [])
indegree = find_id(adj_list, indegree)
for index, value in enumerate(indegree):
if value == 0:
temp_stack.append(index)
for i in range(adj_list.vex_num):
vexs_early.append(0)
while temp_stack:
arc_tail_index = temp_stack.pop()
vertex_node = adj_list.vertexs[arc_tail_index]
vex_seq.append(arc_tail_index)
counter += 1
arc = vertex_node.first_arc
while arc:
arc_head_index = arc.arc_head_index
indegree[arc_head_index] -= 1
if indegree[arc_head_index] == 0:
temp_stack.append(arc_head_index)
if vexs_early[arc_tail_index] + arc.weight > vexs_early[arc_head_index]:
vexs_early[arc_head_index] = vexs_early[arc_tail_index] + arc.weight
arc = arc.next_arc
if counter < adj_list.vex_num:
return "ERROR"
return vex_seq, vexs_early
def critical_path(adj_list: AdjList):
"""
关键路径:邻接表
AOE-网:
用顶点表示事件,用弧表示活动,弧的权值表示活动所需要的时间
关键路径:从源点到汇点的最长路径的长度即为完成整个工程任务所需的时间,该路径称为关键路径。关键路径上的活动称为关键活动
求关键路径的基本步骤:
(1) 对图中顶点进行拓扑排序,在排序过程中按拓扑序列求出每个事件的最早发生时间vexs_early(i);
(2) 逆拓扑序列求每个事件的最晚发生时间vexs_last(i)
(3) 求出每个活动ai的最早开始时间arcs_early(i)和最晚发生时间arcs_last(i);
(4) 找出arcs_early(i) == arcs_last(i)的活动ai, 即为关键活动。
下面使用拓扑排序的方法,对torpo_sort进行修改为torpo_sort_,只有细微差别
算法思想:
(1) 首先调用修改后的拓扑排序算法,求出每个事件的最早发生时间和逆拓扑序列栈T。
(2) 将各顶点的最晚发生时间vexs_last[i]初始化为汇点的最早发生时间;
(3) 只要vex_seq不空,则重复下面处理:
a.将栈顶顶点j出栈;
b.对于顶点j的每一个邻接点k,根据顶点k的最晚发生时间vexs_last和弧<j, k>的权值,更新顶点j的最晚发生时间vexs_last[j]。
(4) 扫描每一条弧,计算其最早发生时间arcs_early[i]和最晚发生时间arcs_early[i],如果arcs_early[i]等于arcs_early[i]则
falg = 1,表示该弧是关键活动。
:param adj_list: 创建好的邻接表
:return: 返回所有的活动及其相关信息
"""
vex_seq, vexs_early = topo_sort_(adj_list)
if not vex_seq:
return "ERROR"
vexs_last = array('i', [])
for i in range(adj_list.vex_num):
vexs_last.append(vexs_early[adj_list.vex_num-1])
while vex_seq:
arc_tail_index = vex_seq.pop()
arc = adj_list.vertexs[arc_tail_index].first_arc
while arc:
arc_head_index = arc.arc_head_index
if vexs_last[arc_tail_index] > vexs_last[arc_head_index] - arc.weight:
vexs_last[arc_tail_index] = vexs_last[arc_head_index] - arc.weight
arc = arc.next_arc
critical_path_seq = deque()
Active = namedtuple('Active', ['active_tail', 'active_head', 'weight', 'active_early', 'active_last', 'flag'])
for arc_tail_index in range(adj_list.vex_num):
arc = adj_list.vertexs[arc_tail_index].first_arc
while arc:
arc_head_index = arc.arc_head_index
arcs_early = vexs_early[arc_tail_index]
arcs_last = vexs_last[arc_head_index] - arc.weight
if arcs_early == arcs_last:
flag = 1
else:
flag = 0
active = Active(adj_list.vertexs[arc_tail_index].name,
adj_list.vertexs[arc_head_index].name, arc.weight, arcs_early, arcs_last, flag)
critical_path_seq.append(active)
arc = arc.next_arc
return critical_path_seq
def bfs_k_level(adj_list: AdjList, vertex_node: VertexNode, k: int):
"""
求距离顶点v0的最短路径长短为k的所有顶点
已知无向图g,设计算法求距离顶点v0的最短路径长度为k的所有顶点,要求尽可能节省时间。
【问题分析】:
由于题目要求找出最短路径长度为k的所有顶点,故从顶点v0开始进行广度优先搜索,将一步、两步可达的.....直
至k步可达的所有顶点记录下来,同时用一个队列记录所有顶点的层号,输出第k+1层的所有结点即为所求。
此算法适用于无向图和有向图
:param adj_list: 创建好的邻接表
:param vertex_node: 起始顶点
:param k: 路径长度
:return: 成功返回k步可达的所有结点及相应层号列表,失败返回"ERROR"
"""
vex_seq = deque()
temp_index_queue = deque()
temp_level_queue = deque()
visited = array('i', [])
for i in range(adj_list.vex_num):
visited.append(0)
vertex_index = locate_vertex(adj_list, vertex_node)
if vertex_index is None:
return "ERROR"
temp_index_queue.append(vertex_index)
temp_level_queue.append(1)
visited[vertex_index] = 1
vex_seq.append((adj_list.vertexs[vertex_index], 1))
while temp_index_queue:
arc_tail_index = temp_index_queue.popleft()
arc_tail_level = temp_level_queue.popleft()
arc_head_level = arc_tail_level + 1
if arc_head_level > k+1:
return vex_seq
arc = adj_list.vertexs[arc_tail_index].first_arc
while arc:
arc_head_index = arc.arc_head_index
if not visited[arc_head_index]:
temp_index_queue.append(arc_head_index)
temp_level_queue.append(arc_head_level)
visited[arc_head_index] = 1
vex_seq.append((adj_list.vertexs[arc_head_index], arc_head_level))
arc = arc.next_arc
counter_node = 0
def dfs_g_path(adj_list: AdjList, index: int):
"""
用递归的深度优先搜索法在图g中寻找一条包含所有顶点的简单路径。从当前结点adj_list.vex_num[index]成功找
到时返回1,否则返回0
:param adj_list: 传递进来的adj_list
:param index: 起始结点
:return: 成功返回1,失败返回0
"""
global counter_node, trav_seq, visited
visited[index] = 1
counter_node += 1
trav_seq.append(adj_list.vertexs[index])
if counter_node == adj_list.vex_num:
return 1
arc = adj_list.vertexs[index].first_arc
while arc:
arc_head_index = arc.arc_head_index
if not visited[arc_head_index] and dfs_g_path(adj_list, arc_head_index) == 1:
return 1
arc = arc.next_arc
visited[index] = 0
counter_node -= 1
trav_seq.pop()
return 0
def hamilton(adj_list: AdjList):
"""
找出在图g中找出一条包含所有顶点的简单路径
从任一顶点开始进行深度优先搜索,同时记录当前路径结点序列和当前路径结点数目。当递归进层时结点数目加1,
当递归退层时结点数目减1,当节点数目等于结点总数时,输出当前路径结点序列,成功返回。
:param adj_list: 已创建好的邻接表
:return: 简单路径序列
"""
for i in range(adj_list.vex_num):
visited.append(0)
for i in range(adj_list.vex_num):
if not visited[i] and dfs_g_path(adj_list, i) == 1:
return trav_seq
from graph.adj_list import VertexNode, ArcNode, AdjList, create_adj_list, travers_graph, dfs_path, \
topo_sort, topo_sort_, critical_path, bfs_k_level, hamilton
if __name__ == '__main__':
v1 = VertexNode('v1')
v2 = VertexNode('v2')
v3 = VertexNode('v3')
v4 = VertexNode('v4')
v5 = VertexNode('v5')
v6 = VertexNode('v6')
arc1 = (v1, v2, 1)
arc2 = (v2, v5, 1)
arc3 = (v5, v6, 1)
arc4 = (v6, v4, 1)
arc5 = (v1, v1, 1)
arc6 = (v1, v3, 1)
arc7 = (v2, v3, 1)
arc8 = (v5, v3, 1)
arc9 = (v6, v3, 1)
arc10 = (v4, v3, 1)
adj_list = AdjList()
vertexs = [v1, v2, v3, v4, v5, v6]
arcs = [arc1, arc2, arc3, arc4, arc5, arc6, arc7, arc8, arc9, arc10]
create_adj_list(adj_list, vertexs, arcs, 'NDG')
trav_seq = hamilton(adj_list)
print(','.join([vertex_node.name for vertex_node in trav_seq]))