7.1概念性质和实现
7.1.1 定义
图可以看做是二元组G=(V,E):,其中V是顶集合,E是边的集合;
图分为有向和无向,下面的讨论中以<>表示有向边,()表示无向边;
<a, b>:a叫做始点,b叫做终点;
邻接::b叫做a的邻接点,无向图中邻接是双向的;
关联::<a,b>叫做与a相关联的边,或者邻接边;
Note:不考虑顶点到自身的边,两个顶点之间的边唯一;
7.1.2 图的一些概念和性质
完全图:任何两个顶点之间都有边;n个顶点的图边数:
有向图: n*(n-1)
无向图: n*(n-1)/2
度::某顶点邻接边的数目,有向图有出度和入度;顶点、边、度之间有关系:
E=0.5*∑D(vi):边等于所有顶点度的一半(无向、有向)
路径与相关性质
从顶点Vi到Vj可达,则称存在i到j的路径,记做<i…j>;
路径长度:路径上边的数目;
回路:起点==终点
简单回路:除了起点和终点,其他点都不相同
如果两点之间存在非简单路径,那么两点之间就要无穷多的路径,取决于绕圈的次数;
有根图:存在某个点,该点可达图中任一点,该点成为一个根;
连通图
两点之间存在路径则称两点连通;
连通无向图:任意顶点连通,则成为连通图;但是不一定完全,就像韩战之前中国要通过印度向美方传递消息;
强连通有向图: 之所以加‘强’,是因为要求路径是双向的
但是,无论是有向还是无向,连通都不意味着完全,例如:
G5是强连通图,以为ca以及ab之间都存在路径。
连通图和边数的关系
n个顶点的最小无向连通图有n-1条边→无向树
n个顶点的最小有根图有n-1条边→有向树
子图、连通子图
子图就是原图的一部分;
极大连通子图:
带权图和网络
带权图:边赋予权重;
网络:带权的连通无向图
7.1.3 抽象数据类型
7.1.4 图的表示和实现
7.2 图的python实现
7.3 图的基本算法
7.3.1 图的遍历
7.3.2 生成树
前提:连通图:,例如连通无向图、或者强连通有向图(实际只要求有根有向图)
性质7.4(图中的路径和路径的边数):如果图G有n个顶点,必然可以找到包含n-1条边的集合,这些边包含从顶点(记做根节点)到所有节点的路径,以下等价:
n-1条边=子图=生成树
特点:由于边只比节点少1,所以不可能有回路
遍历和生成树:
在图遍历的过程就构成该图的生成树,按照深度优先还是广度优先,可以分为DFS & BFS
构造DFS生成树:
假设图中有vnum个结点,创建一个长度为vnum的元组列表,元组内容为(前一节点、边权重)
递归构造DFS生成树:
7.4 最小生成树
7.4.1 最小生成树问题
若干概念:
生成树的权:G的一棵生成树中各条边的权值之和称为该生成树的权。
最小生成树::生成树可能不唯一,其中权值最小的称为最小生成树(不唯一)
假定:各节点到自身的权值为0,两点之间无边对应的权值为inf
7.4.2 Kruskal算法-分布统一(小部落到大部落)
思路
设G=(V,E)是一个网络,其中|v|=n,E是边集合(端点、权值),构造过程如下:
1、 初始化n个顶点但是没有任何边的孤立子图T=(V, {}),接下来不断扩充T。
2、 将边集合按照权重递增排列,顺序的检查边集合,找到端点位于不同连通分量的权值最小的边e,将e加入到T中。
3、 随着子图的连通分量逐渐减少,最后就是最小生成树。
原图连通则获得最小生成树,否则得到最小连通森林
问题:
1、 最短边的选取:排序、优先队列
2、 如何判断两个点位于不同的连通分量?
方案一:检查两个端点之间是否存在路径;
方案二:每个连通分量设一个代表元,代表元不同的点位于不同的连通分量。
实现与code流程:
边表统计,优先队列,生成树
7.4.3 Prim算法-根据地扩展算法-立于一点逐步扩展
思路:-建立根据地逐步扩大
将所有的顶点划分为当前连通区包含的结点和连通区之外的结点结合,每次面向所有的通道,从中选择权重最小的作为选中的最优通道,逐渐生成最小生成树.→因为每次都会增加连通区,所以一定可以生成→聚集兵力攻敌弱点。
问题
实现
7.5 最短路径
问题背景::带权图(有向或无向)
7.5.1最短路径问题
分为单源点最短路径和所有结点的最短路径,如果计算指定的两个结点之间的最短路径,一般没有简单的方法,而是借助单源点最短路径的结果。
7.5.2 单源点最短路径-Dijkstra算法
要求:权值非负
基本思想类似于Prim,利用了最小生成树(mst)的性质。
思路:根据到v0的最短路径是否确定,将顶点划分到两个集合U、V-U,逐步扩大U,直到包括所有的结点。
问题:如何找到下一个能确定最短路径的点?
每一时刻,确定所有顶点到v0的已知最短路径cdis(v0,v),
解释:1.已经在U中的点,其最短路径固定不变为dis(v0, v);
2.有些点不在U中,但是通过U中的点间接和v0相连;那么这些点的距离从inf更新为间接距离,min可以通过每次加入新点时探查得出,而不用每次都考虑V中所有的点;
3.其余点的距离仍然保持为inf;
根据性质7.7 确定要加入的新点
疑问:会不会因为后面结点的加入,使得已确定最短路径的点的距离更小?
不会。不容易说清,总之距离非降。
7.6 AOV与AOE
背景:有向无环图DAG
7.6.1 AOV-activity on vertex network顶点活动网
顶点表示工程中的活动,边表示先后顺序关系
拓扑排序::把网络结构压缩为线型结构,并且保证所有节点的先后关系不变。具体指C3一定在C2后面,但不一定是直接后继。结果称作拓扑序列。
意义:原方案的串行完成。
7.6.2 拓扑排序算法与回路检测
思想:
1、选择入度为0的点作为下一顶点
2、从网络中删除所选顶点和所有出边
3、重复以上过程直到已经选出了所有的顶点,如果结束不了,说明有环。
问题:
入度表和零度表的维护:入度表直接生成,0度表构成链表。
实现与code流程:
生成入度表,生成0度表,开始拓扑排序。
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("Arguments for Graph.")
self._mat = mat
self._unconn = unconn
self._vnum = vnum
def vertex_num(self):
"""结点数目"""
return self._vnum
def is_invalid(self, v):
"""合法性检查"""
# print(v, self._vnum)
return v < 0 or v >= self._vnum
def add_vertex(self):
"""增加结点"""
raise GraphError("not support...")
def add_edge(self, vi, vj, val=1):
"""创建边"""
if self.is_invalid(vi) or self.is_invalid(vj):
raise GraphError('vi or vj is invalid')
self._mat[i][j] = val
def get_edge(self, vi, vj):
"""根据索引获取边的信息"""
if self.is_invalid(vi) or self.is_invalid(vj