文章目录
- 概念、性质和实现
- 定义
- 图
- 完全图
- 度
- 顶点数、边数和顶点度数的关系
- 路径
- 连通图
- 带权图和网络
- 邻接矩阵表示
- 邻接表表示
- 邻接多重表表示
- 图的十字链表表示
- 图结构的python实现
- 1.邻接矩阵表示
- 2.邻接表表示
- 基本图算法
- 图的遍历和生成树
- 深度优先遍历
- 宽度优先遍历
- 深度优先遍历的非递归算法
- 生成树
- 7.4 最小生成树
- 7.5 最短路径
- 7.6 AOV/AEV网及其算法
- AOV网、拓扑排序和拓扑序列
- 工程和工作安排
- 拓扑排序和拓扑序列
- 拓扑排序算法
- 实现技术和函数定义
- AOE网和关键路径
- 关键路径算法
- 变量定义
- 算法
概念、性质和实现
定义
图
一个图是一个二元组,其中:
是非空又穷的顶点集合;
是顶顶啊偶对(称为边)的集合;
中的顶点也称为图的顶点,中的边也称为图的边。
图分为有向图和无向图两类。
完全图
完全图:任意两个顶点之间都要边的图,称为完全图。
个顶点的无向完全图有条边;
个顶点的有向完全图有条边。
度
一个顶点的度就是与它邻接的边的条数,对于有向图,顶点的度分为入度和出度。
顶点数、边数和顶点度数的关系
顶点数,边数和顶点度数:
其中表示顶点的度数。
路径
路径的长度就是该路径上的边的条数;
回路(环)指起点和终点相同的路径;
如果一个环路除起点和终点外的其他顶点均不相同,则称为简单回路;
简单路径是内部不包含回路的路径,也就是,该路径上的顶点除了起点和终点可能相同外,其他顶点均不相同。
有根图:如果在有向图里存在一个顶点,从顶点到图中其他每个顶点均有路径,则称为有根图,顶点为图的一个根。
连通图
连通:到存在路径,则这两个顶点连通。
连通无向图
强连通有向图
带权图和网络
图中的每条边都被赋予一个权值,则称图为一个带权图。
带权的连通无向图称为网络。
一个图中一些有用的操作:
ADT Graph: # 一个图抽象数据类型
Graph(self) # 图构造操作,创建一个新图
is_empty(self) # 判断是否为一个空图
vertex_num(self) # 获得这个图中的顶点个数
edge_num(self) # 获得这个图中边的条数
vertices(self) # 获得这个图中的顶点集合
edges(self) # 获得这个图中的边集合
add_vertex(self, vertex) # 将顶点vertex加入这个图
add_edge(self, v1, v2) # 将从v1到v2的边加入这个图
get_edge(self, v1, v2) # 获得v1到v2边有关的信息,没有时返回特殊值
out_edges(self, v) # 取得从v出发的所有边
degree(self, v) # 检查v的度
邻接矩阵表示
邻接表表示
邻接多重表表示
图的十字链表表示
图结构的python实现
1.邻接矩阵表示
inf = float("inf") # inf的值大于任何float类型的值
class GraphError(ValueError):
pass
class Graph: # 基本图类,采用邻接矩阵表示
def __init__(self, mat, unconn=0):
vnum = len(mat)
for x in mat:
if len(x) != vnum: # 检查是否为方阵
raise ValueError("Argument for 'Graph'.")
self._mat = [mat[i][:] for i in range(vnum)] # 做拷贝
self._unconn = unconn
self._vnum = vnum
def vertex_num(self):
"""顶点数目"""
return self._vnum
def _invalid(self, v):
"""无效顶点"""
return 0 > v or v >= self._vnum
def add_vertex(self):
"""增加顶点的函数"""
raise GeneratorExit("Adj-Matrix does not support 'add_vertex'.")
def add_edge(self, vi, vj, val=1):
"""添加vi到vj的边"""
if self._invalid(vi) or self._invalid(vj):
raise GeneratorExit(str(vi) + ' or ' + str(vj) + "is not a valid vertex.")
self._mat[vi][vj] = val
def get_edge(self, vi, vj):
"""获取vi到vj的边"""
if self._invalid(vi) or self._invalid(vj):
raise GeneratorExit(str(vi) + ' or ' + str(vj) + "is not a valid vertex.")
return self._mat[vi][vj]
def out_edges(self, vi):
"""获取从vi出发的边"""
if self._invalid(vi):
raise GeneratorExit(str(vi) + "is not a valid vertex.")
return self._out_edges(self._mat[vi], self._unconn)
@staticmethod
def _out_edges(row, unconn):
edges = []
for i in range(len(row)):
if row[i] != unconn:
edges.append((i, row[i]))
return edges
def __str__(self):
"""为这个类的对象提供一种转换为字符串的形式"""
return "[\n" + ",\n".join(map(str, self._mat)) + "\n]" + "\nUnconnected: " + str(self._unconn)
2.邻接表表示
继承邻接矩阵表示的图类
## 邻接表实现 继承邻接矩阵的类
class GraphAL(Graph):
def __init__(self, mat=[], unconn=0):
vnum = len(mat)
for x in mat:
if len(x) != vnum:
raise ValueError("Argument for 'GraphAL'.")
self._mat = [Graph._out_edges(mat[i], unconn) for i in range(vnum)]
self._vnum = vnum
self._unconn = unconn
def add_vertex(self):
"""增加顶点"""
self._mat.append([])
self._vnum += 1
return self._vnum - 1
def add_edge(self, vi, vj, val=1):
"""增加vi到vj的边"""
if self._vnum == 0:
raise GraphError("Cannot add edge to empty graph.")
if self._invalid(vi) or self._invalid(vj):
raise GraphError(str(vi) + ' or ' + str(vj) + "is not valid vertex.")
row = self._mat[vi]
i = 0
while i < len(row):
if row[i][0] == vj: # 修改mat[vi][vj]的值
self._mat[vi][i] = (vj, val)
return
if row[i][0] > vj: # 原来没有到vj的边,退出循环后加入边
break
i += 1
self._mat[vi].insert(i, (vj, val)) # 在j处插入边
def get_edge(self, vi, vj):
"""获取vi到vj的边"""
if self._invalid(vi) or self._invalid(vj):
raise GraphError(str(vi) + ' or ' + str(vj) + "is not valid vertex.")
for i, val in self._mat[vi]:
if i == vj:
return val
return self._unconn
def out_edges(self, vi):
"""vi的出边"""
if self._invalid(vi):
raise GraphError(str(vi) + "is not valid vertex.")
return self._mat[vi]
基本图算法
1.物联网和移动电话网的路由
2.集成电路(和印刷电路板)的设计和布线
3.运输和物流中的各种规划安排问题
4.工程项目的计划安排
5.许多社会问题计算,如金融监管(例如关联交易检查)
图的遍历和生成树
深度优先遍历
按照深度优先搜索(Depth-First Search)的方式实施整个遍历过程。假定从指定顶点出发:
1.首先访问顶点,并将其标记为已访问。
2.检查的邻接顶点,从中选一个尚未访问的顶点,从它出发继续进行深度优先搜索(这是递归)。不存在这种邻接顶点时回溯(邻接顶点可能排了一种顺序)。
3.反复上述操作直到从出发可达的所有顶点都已访问(递归)。
4.如果图中还存在未访问的顶点,则选出一个未访问顶点,由它出发重复前述过程,直到图中所有顶点都已访问为止。
通过深度优先遍历顺序得到的顶点序列称为该图的深度优先搜索序列(Depth-First Search序列),简称为DFS序列。
宽度优先遍历
通过宽度优先搜索(Breadth-First Search)的方式实施遍历。假设从指定顶点出发:
1.先访问顶点并将其标记为已访问。
2.依次访问的所有相邻顶点(可能规定某种顺序),再依次访问与邻接的所有尚未访问过的顶点,直到所有可达顶点都已访问。
3.如果图中还存在未访问的顶点,则选出一个未访问顶点,由它出发重复前述过程,直到图中所有顶点都已访问为止。
通过宽度优先遍历顺序得到的顶点序列称为该图的宽度优先搜索序列(Breadth-First Search序列),简称为BFS序列。
深度优先遍历的非递归算法
class StackUnderflow(ValueError): # 栈下溢(空栈访问)
pass
class SStack(): # 基于顺序表技术实现的栈类
def __init__(self): # 用list对象, _elems存储栈中的元素
self._elems = [] # 所有栈操作都映射到list操作
def is_empty(self):
"""判断是否为空"""
return self._elems == []
def top(self):
"""栈顶元素"""
if self._elems == []:
raise StackUnderflow("in SStack.top()")
return self._elems[-1]
def push(self, elem):
"""插入元素"""
self._elems.append(elem)
def pop(self):
"""删除元素"""
if self._elems == []:
raise StackUnderflow("in SStack.pop()")
return self._elems.pop()
def DFS_graph(graph, v0):
vnum = graph.vertex_num()
visited = [0]*vnum # visited记录已访问顶点
visited[v0] = 1
DFS_seq = [v0] # DFS_seq记录遍历序列
st = SSstack()
st.push((0, graph.out_edges(v0))) # 入栈(i, edges), 说明下次访问边edges[i]
while not st.is_empty():
i, edges = st.pop() # edges的是某个顶点的边表,i是边表的下标
if i < len(edges):
v, e = edges[i]
st.push((i+1, edges)) # 下次回来访问edges[i+1]
if not visited[v]: # v未访问,访问并记录其可达顶点
DFS_seq.append(v)
visited[v] = 1
st.push((0, graph.out_edges(v))) # 下次访问的边组
return DFS_seq
生成树
7.4 最小生成树
7.5 最短路径
7.6 AOV/AEV网及其算法
AOV网、拓扑排序和拓扑序列
用图中的顶点表示某个有一定规模的“工程”里的不同活动,用图中的边表示各项活动之间的先后顺序关系。(activity on vertex network)
工程和工作安排
不同工作之间存在一些制约关系,完成一些工作之后才能开始另一些工作。做出满足制约关系的工作安排。
拓扑排序和拓扑序列
对于给定的AOV网,如果中的所有顶点能排成一个线性序列
满足:如果中存在从顶点到顶点的路径,那么里就排在之前,则称为的一个拓扑序列,而构造拓扑序列的操作称为拓扑排序。
1.不唯一
2.拓扑序列的逆网也是拓扑序列
拓扑排序算法
任何无回路的AOV网都可以做出拓扑序列:
1.从中选出一个入度为0的顶点作为序列的下一顶点
2.从网中删除所选顶点及其所有的出边。
3.反复执行上面两步操作,直到已经选出了图中的所有顶点,或者再也找不到入度非0的顶点时算法结束。
实现技术和函数定义
工作中需要反复找出入度为0的顶点,提出一种技巧:在indegree里维持一个“0度表”,表中记录当时已知的所有入度为0但还没有处理的顶点。用一个变量zerov记录“第一个”入度为0的顶点的下标;用表元素indegree[zerov]记录下一个入度为0的顶点的下标;如此类推。
1.确定所有顶点的入度存入indegree,用0度表记录其中入度为0的顶点;
2.反复选择入度为0的顶点并维护0度表;
3.最后返回拓扑序列,失败时返回False.
def toposort(graph):
vnum = graph.vertex_num() # 顶点数
indegree, toposeq = [0]*vnum, [] # 入度表,拓扑排序表
zerov = -1
for vi in range(vnum):
for v, w in graph.out_edges(vi): # 建立初始的入度表,vi的出边,下标和权重
indegree[v] += 1
for vi in range(vnum): # 建立初始的0度表
if indegree[vi] == 0:
indegree[vi] = zerov
zerov = vi
for n in range(vnum):
if zerov == -1: # 没有入度为0的顶点,不存在拓扑序列
return False
vi = zerov # 从0度表弹出顶点vi
zerov = indegree[zerov]
toposeq.append(vi) # 把一个vi加入拓扑序列
for v, w in graph.out_edges(vi): # 检查vi的出边
indegree[v] -= 1
if indegree[v] == 0:
indegree[v] = zerov
zerov = v
return toposeq
AOE网和关键路径
AOE网是一种无环的带权有向图:
1.顶点表示事件,有向边表示活动,边上的权值通常表示活动持续时间;
2.图中一个顶点表示的事件,也就是它的入边所表示的活动都已经完成,它的出边所表示的活动都可以开始的那个状态,把这一情况看做事件。
完成整个工程的最短事件,就是从开始顶点到完成顶点的最长路径的长度(即,路径上各条边的权值之和)。这种最长路径称为AOE网的关键路径。
关键路径算法
变量定义
:事件最早可能发生的时间,对每个只需要考虑以为终点的入边集。
:时间最迟允许发生的时间,对每个只需要考虑以为始点的出边集。
网络中活动的最早可能开始时间,以及它的最迟允许开始时间.
关键活动:活动集合中的所有活动成为这个AOE网里的关键活动。
关键路径:所有完全由关键活动构成的从初始点到终点的路径就是关键路径。
算法
1.生成AOE网的一个拓扑序列;
2.生成表的值,应该按拓扑序列的顺序计算;
3.生成数组的值,应该按照拓扑序列的逆序计算;
4.数据组和可以一起计算,直接求出关键活动。
def critical_paths(graph):
def events_earliest_time(vnum, graph, toposeq):
ee = [0] * vnum
for i in toposeq:
for j, w in graph.out_edges(i):
if ee[i] + w > ee[j]: # 事件j更晚结束?
ee[j] = ee[i] + w
return ee
def event_latest_time(vnum, graph, toposeq, eelast):
le = [eelast] * vnum
for k in range(vnum-2, -1, -1): # 逆拓扑顺序
i = toposeq[k]
for j, w in graph.out_deges(i):
if le[j] - w < le[i]:
le[i] = le[j] - w
return le
def crt_paths(vnum, graph, ee, le):
crt_actions = []
for i in range(vnum):
for j, w in graph.out_deges(i):
if ee[i] == le[j] - w: # 关键活动
crt_actions.append((i, j, ee[i]))
return crt_actions
toposeq = toposort(graph)
if not toposeq: # 不存在拓扑序列,失败结束
return False
vnum = graph.vertex_num()
ee = events_earliest_time(vnum, graph, toposeq)
le = event_latest_time(vnum, graph, toposeq, ee[vnum-1])
return crt_paths(vnum, graph, ee, le)