@图

  1. 第六章 图
  2. 本章的主要内容是: 图的逻辑结构图的存储结构及实现图的连通性最小生成树最短路径 AOV网与拓扑排序 AOE网与关键路径 图论——欧拉
    欧拉1707年出生在瑞士的巴塞尔城,19岁开始发表论文,直到76岁。
    几乎每一个数学领域都可以看到欧拉的名字,从初等几何的欧拉线,多面体的欧拉定理,立体解析几何的欧拉变换公式,四次方程的欧拉解法到数论中的欧拉函数,微分方程的欧拉方程,级数论的欧拉常数,变分学的欧拉方程,复变函数的欧拉公式等等。
    据统计,他一生共写下了886本书籍和论文,其中:分析、代数、数论占40%,几何占
    18%,物理和力学占28%,天文学占11%,弹道学、航海学、建筑学等占3%。
    1733年,26岁的欧拉担任了彼得堡科学院数学教授。1741年到柏林担任科学院物理数学所所长,直到1766年,重回彼得堡,没有多久,完全失明。欧拉对著名的哥尼斯堡七桥问题的解答开创了图论的研究。
    哥尼斯堡七桥问题
  3. 能否从某个地方出发,穿过所有的桥仅一次后再回到出发点? 哥尼斯堡七桥问题 七桥问题的图模型欧拉回路的判定规则:
    1.如果通奇数桥的地方多于两个,则不存在欧拉回路; 2.如果只有两个地方通奇数桥,可以从这两个地方之一出发,找到欧拉回路;
    3.如果没有一个地方是通奇数桥的,则无论从哪里出发,都能找到欧拉回路。
    6.1 图的逻辑结构 图的定义 图是由顶点的有穷非空集合和顶点之间边的集合组成,通常表示为:

G=(V,E) (Graph Vertex Edge)

  1. 其中:G表示一个图,V是图G中顶点的集合,E是图G中顶点之间边的集合。
    在线性表中,元素个数可以为零,称为空表;在树中,结点个数可以为零,称为空树;在图中,顶点个数不能为零,但可以没有边。

  2. 若顶点vi和vj之间的边没有方向,则称这条边为无向边,表示为(vi,vj)。如果图的任意两个顶点之间的边都是无向边,则称该图为无向图。
    若从顶点vi到vj的边有方向,则称这条边为有向边,表示为。 如果图的任意两个顶点之间的边都是有向边,则称该图为有向图。

**

  1. 图的基本术语

简单图中,若不存在顶点到其自身的边,且同一条边不重复出现。

  1. 图的基本术语
  2. 邻接、依附

无向图中,对于任意两个顶点vi和顶点vj,若存在边
(vi,vj),则称顶点vi和顶点vj互为邻接点,同时称边
(vi,vj)依附于顶点vi和顶点vj。
V1的邻接点: V2 、V4
V2的邻接点: V1 、V3 、V5

  1. 6.1 图的逻辑结构
  2. 图的基本术语

邻接、依附
有向图中,对于任意两个顶点vi和顶点vj,若存在弧 ,则称顶点vi邻接到顶点vj,顶点vj邻接自顶点vi,同时称弧依附于顶点vi和顶点vj 。
V1的邻接点: V2 、V3
V3的邻接点: V4
不同结构中逻辑关系的对比

  1. 线性结构

    树结构 图结构在线性结构中,数据元素之间仅具有线性关系;在树结构中,结点之间具有层次关系;在图结构中,任意两个顶点之间都可能有关系。
    不同结构中逻辑关系的对比
    树结构 图结构在线性结构中,元素之间的关系为前驱和后继;在树结构中,结点之间的关系为双亲和孩子;在图结构中,顶点之间的关系为邻接。

  2. 图的基本术语
    无向完全图:在无向图中,如果任意两个顶点之间都存在边,则称该图为无向完全图。
    有向完全图:在有向图中,如果任意两个顶点之间都存在方向相反的两条弧,则称该图为有向完全图。
    含有n个顶点的无向完全图有多少条边?含有n个顶点的有向完全图有多少条弧?

  3. 含有n个顶点的无向完全图有n×(n-1)/2条边。 含有n个顶点的有向完全图有n×(n-1)条边。
    . 图的基本术语

稀疏图:称边数很少的图为稀疏图;稠密图:称边数很多的图为稠密图。
顶点的度:在无向图中,顶点v的度是指依附于该顶点的边数,通常记为TD (v)。
顶点的入度:在有向图中,顶点v的入度是指以该顶点为弧头的弧的数目,记为ID (v);
顶点的出度:在有向图中,顶点v的出度是指以该顶点为弧尾的弧的数目,记为OD (v)。
图的基本术语

n
∑TD(vi ) = 2e
i=1

在具有n个顶点、e条边的无向图G中,各顶点的度之和与边数之和的关系?
图的基本术语

n n
∑ID(vi ) =∑OD(vi ) =e i=1 i=1

在具有n个顶点、e条边的有向图G中,各顶点的入度之和与各顶点的出度之和的关系?与边数之和的关系?
图的基本术语
权:是指对边赋予的有意义的数值量。
网:边上带权的图,也称网图。

  1. 图的基本术语

路径:在无向图G=(V, E)中,从顶点vp到顶点vq之间的路径是一个顶点序列(vp=vi0,vi1,vi2, …, vim=vq),其中, (vij-1,vij)∈E(1≤j≤m)。若G是有向图,则路径也是有方向的,顶点序列满足∈E。
V1 到V4的路径: V1 V4
V1 V2 V3 V4
V1 V2 V5V3 V4
一般情况下,图中的路径不惟一。

. 图的基本术语

非带权图——路径上边的个数路径长度:带权图——路径上各边的权之和
V1 V4:长度为1
V1 V2 V3 V4 :长度为3
V1 V2 V5V3 V4 :长度为4
图的基本术语
非带权图——路径上边的个数路径长度:带权图——路径上各边的权之和
V1 V4:长度为8
V1 V2 V3 V4 :长度为7
V1 V2 V5V3 V4 :长度为15
图的基本术语
回路(环):第一个顶点和最后一个顶点相同的路径。
简单路径:序列中顶点不重复出现的路径。
简单回路(简单环):除了第一个顶点和最后一个顶点外,其余顶点不重复出现的回路。

图的基本术语
子图:若图G=(V,E),G’=(V’,E’),如果V’⊆V 且E’ ⊆ E ,则称图G’是G的子图。

  1. 图的基本术语

连通图:在无向图中,如果从一个顶点vi到另一个顶点vj(i≠j)有路径,则称顶点vi和vj是连通的。如果图中
任意两个顶点都是连通的,则称该图是连通图。
连通分量:非连通图的极大连通子图称为连通分量。
1.含有极大顶点数;

  1. 依附于这些顶点的所有边。
    如何求得一个非连通图的连通分量?
    图的基本术语

连通分量是对无向图的一种划分。
图的基本术语

强连通图:在有向图中,对图中任意一对顶点vi和vj (i≠j),若从顶点vi到顶点vj和从顶点vj到顶点vi均有路径,则称该有向图是强连通图。
强连通分量:非强连通图的极大强连通子图。

如何求得一个非连通图的连通分量?
图的基本术语
V2
图的基本术语
生成树:n个顶点的连通图G的生成树是包含G中全部顶点的一个极小连通子图。
多——构成回路含有n-1条边
少——不连通 如何理解极小连通子图?
生成森林:在非连通图中,由每个连通分量都可以得到一棵生成树,这些连通分量的生成树就组成了一个非连通图的生成森林。
生成树

图的抽象数据类型定义
ADT Graph
Data
顶点的有穷非空集合和边的集合
Operation
InitGraph
前置条件:图不存在输入:无功能:图的初始化输出:无后置条件:构造一个空的图 DFSTraverse
前置条件:图已存在输入:遍历的起始顶点v 功能:从顶点v出发深度优先遍历图输出:图中顶点的一个线性排列后置条件:图保持不变
BFSTraverse
前置条件:图已存在输入:遍历的起始顶点v 功能:从顶点v出发广度优先遍历图输出:图中顶点的一个线性排列后置条件:图保持不变 DestroyGraph
前置条件:图已存在输入:无功能:销毁图输出:无
后置条件:释放图所占用的存储空间
GetVex
前置条件:图已存在输入:顶点v 功能:在图中查找顶点v的数据信息输出:顶点v的数据信息后置条件:图保持不变 endADT
图的遍历操作
图的遍历是在从图中某一顶点出发,对图中所有顶点访问一次且仅访问一次。
抽象操作,可以是对结点进行的各种处理,这里简化为输出结点的数据。图的遍历操作要解决的关键问题
①在图中,如何选取遍历的起始顶点?
解决方案:从编号小的顶点开始 。
在线性表中,数据元素在表中的编号就是元素在序列中的位置,因而其编号是唯一的;
在树中,将结点按层序编号,由于树具有层次性,因而其层序编号也是唯一的;
在图中,任何两个顶点之间都可能存在边,顶点是没有确定的先后次序的,所以,顶点的编号不唯一。
为了定义操作的方便,将图中的顶点按任意顺序排列起来,比如,按顶点的存储顺序。图的遍历操作要解决的关键问题
②从某个起点始可能到达不了所有其它顶点,怎么办?
解决方案:多次调用从某顶点出发遍历图的算法。
下面仅讨论从某顶点出发遍历图的算法。图的遍历操作要解决的关键问题
③ 因图中可能存在回路,某些顶点可能会被重复访问,那么如何避免遍历不会因回路而陷入死循环。解决方案:附设访问标志数组visited[n] 。
④ 在图中,一个顶点可以和其它多个顶点相连,当这样的顶点访问过后,如何选取下一个要访问的顶点?
解决方案:深度优先遍历和广度优先遍历。

  1. 深度优先遍历 (depth-first traverse) 基本思想:
    ⑴访问顶点v;
    ⑵ 从v的未被访问的邻接点中选取一个顶点w,从w出发进行深度优先遍历;
    ⑶重复上述两步,直至图中所有和v有路径相通的顶点都被访问到。
  2. 深度优先遍历 (depth-first traverse)
    例 例
    深度优先遍历:V1⇒ V2 ⇒V4 ⇒ V8 ⇒V5 ⇒V6 ⇒V3 ⇒V7 深度优先遍历:V1⇒ V2 ⇒V4 ⇒ V8 ⇒V5 ⇒V6 ⇒V3 ⇒V7
  3. 深度优先遍历 (depth-first traverse)

深度优先遍历:V1⇒ V2 ⇒V4 ⇒ V8 ⇒V3 ⇒V6 ⇒V7 ⇒V5

深度优先遍历序列?入栈序列?出栈序列?
深一层递归
递归返回
深度优先遍历序列?入栈序列?出栈序列?
深一层递归
递归返回
深度优先遍历序列?入栈序列?出栈序列?
深一层递归
递归返回
深度优先遍历序列?入栈序列?出栈序列?
深一层递归
递归返回

  1. 广度优先遍历 (breadth-first traverse) 基本思想:
    ⑴访问顶点v;
    ⑵依次访问v的各个未被访问的邻接点v1, v2, …, vk;
    ⑶ 分别从v1,v2,…,vk出发依次访问它们未被访问的邻接点,并使“先被访问顶点的邻接点”先于“后被访问顶点的邻接点”被访问。直至图中所有与顶点v 有路径相通的顶点都被访问到。
  2. 广度优先遍历 (breadth-first traverse)
    例 例
    广度优先遍历:V1⇒ V2 ⇒V3 ⇒ V4 ⇒V5 ⇒V6 ⇒V7 ⇒V8 广度优先遍历:V1⇒ V2 ⇒V3 ⇒ V4 ⇒V5 ⇒V6 ⇒V7 ⇒V8
  3. 广度优先遍历 (breadth-first traverse)

广度优先遍历:V1⇒ V2 ⇒V3 ⇒ V4 ⇒V6 ⇒V7 ⇒V8 ⇒V5

V1
广度优先遍历序列?入队序列?出队序列?  
V2 V3
广度优先遍历序列?入队序列?出队序列?  
V3 V4 V5
广度优先遍历序列?入队序列?出队序列?

V4 V5 V6 V7
广度优先遍历序列?入队序列?出队序列?  
V5 V6 V7 V8
广度优先遍历序列?入队序列?出队序列?  
是否可以采用顺序存储结构存储图?
图的特点:顶点之间的关系是m:n,即任何两个顶点之间都可能存在关系(边),无法通过存储位置表示这种任意的逻辑关系,所以,图无法采用顺序存储结构。
如何存储图?
考虑图的定义,图是由顶点和边组成的,分别考虑如何存储顶点、如何存储边。

邻接矩阵(Adjacency Matrix 数组表示法)
基本思想:用一个一维数组存储图中顶点的信息,用一个二维数组(称为邻接矩阵)存储图中各顶点之间的邻接关系。
假设图G=(V,E)有n个顶点,则邻接矩阵是一个 n×n的方阵,定义为:
1 若(vi, vj)∈E(或∈E)
arc[i][j]=
0 其它
无向图的邻接矩阵
vertex= V1 V2 V3 V4
V1 V2 V3 V4
0 1 0 1 V1 arc=1 0 1 1 V2 0 1 0 0 V3 1 1 0 0 V4
无向图的邻接矩阵的特点?主对角线为 0 且一定是对称矩阵。
无向图的邻接矩阵
vertex= V1 V2 V3 V4
V1 V2 V3 V4
0 1 0 1 V1 arc= 1 0 1 1 V2 0 1 0 0 V3 1 1 0 0 V4
如何求顶点i的度?邻接矩阵的第i行(或第i列)非零元素的个数。
无向图的邻接矩阵
vertex= V1 V2 V3 V4
V1 V2 V3 V4
0 1 0 1 V1 arc=1 0 1 1 V2 0 1 0 0 V3 1 1 0 0 V4
如何判断顶点 i 和 j 之间是否存在边?测试邻接矩阵中相应位置的元素arc[i][j]是否为1。

6.2 图的存储结构及实现
无向图的邻接矩阵
vertex= V1 V2 V3 V4
V1 V2 V3 V4
0 1 0 1 V1 arc= 1 0 1 1 V2 0 1 0 0 V3 1 1 0 0 V4
如何求顶点 i 的所有邻接点?
将数组中第 i 行元素扫描一遍,若arc[i][j]为1,则顶点 j 为顶点 i 的邻接点。
有向图的邻接矩阵
vertex= V1 V2 V3 V4
V1 V2 V3 V4
0 1 1 0 V1 arc=0 0 0 0 V2 0 0 0 1 V3 1 0 0 0 V4
有向图的邻接矩阵一定不对称吗?不一定,例如有向完全图。
有向图的邻接矩阵
vertex= V1 V2 V3 V4
V1 V2 V3 V4
0 1 1 0 V1 arc= 0 0 0 0 V2 0 0 0 1 V3 1 0 0 0 V4
如何求顶点 i 的出度?邻接矩阵的第 i 行元素之和。
有向图的邻接矩阵
7. vertex= V1 V2 V3 V4 V1 V2 V3 V4 0 1 1 0 V1
arc=0 0 0 0 V2 0 0 0 1 V3 1 0 0 0 V4

如何求顶点 i 的入度?邻接矩阵的第 i 列元素之和。

有向图的邻接矩阵
vertex= V1 V2 V3 V4
V1 V2 V3 V4
0 1 1 0 V1 arc=0 0 0 0 V2 0 0 0 1 V3 1 0 0 0 V4
如何判断从顶点 i 到顶点 j 是否存在边?测试邻接矩阵中相应位置的元素arc[i][j]是否为1。
网图的邻接矩阵
网图的邻接矩阵可定义为:
wij 若(vi, vj)∈E(或∈E)
arc[i][j]= 0 若i=j
∞ 其他
0 2 5 ∞
∞ 0 ∞ ∞
arc=
∞∞ 0 8 7 ∞∞ 0
邻接矩阵存储
无向图的类
const int MaxSize=10; template
class MGraph // Adjacency Matrix
{ public:
MGraph(T a[ ], int n, int e ); ~MGraph( )
void DFSTraverse(int v); void BFSTraverse(int v);
private:
T vertex [MaxSize]; int arc[MaxSize][MaxSize];
int vertexNum, arcNum;
};
邻接矩阵中图的基本操作——构造函数
8. 确定图的顶点个数和边的个数;
9. 输入顶点信息存储在一维数组vertex中;
10. 初始化邻接矩阵;
11. 依次输入每条边存储在邻接矩阵arc中;
4.1 输入边依附的两个顶点的序号i, j;
4.2 将邻接矩阵的第i行第j列的元素值置为1;
4.3 将邻接矩阵的第j行第i列的元素值置为1;
邻接矩阵中图的基本操作——构造函数 template
MGraph::MGraph(T a[ ], int n, int e)
{
vertexNum=n; arcNum=e; for (i=0; i for (i=0; i for (j=0; j for (k=0; k {
cin>>i>>j; //边依附的两个顶点的序号
arc[i][j]=1; arc[j][i]=1; //置有边标志 }
}
邻接矩阵中图的基本操作——深度优先遍历
template
void MGraph::DFSTraverse(int v)
{ cout< DFSTraverse( j );
}
邻接矩阵中图的基本操作——广度优先遍历 template
void MGraph::BFSTraverse(int v)
{
front=rear=-1; //假设采用顺序队列且不会发生溢出
cout< { v=Q[++front]; for (j=0; j if (arc[v][j]==1 && visited[j]==0 ) { cout< }
}

  1. 邻接表(Adjacency List )

  2. 图的邻接矩阵存储结构的空间复杂度?如果为稀疏图则会出现什么现象? 假设图G有n个顶点e条边,则存储该图需要O(n2) 。
    邻接表存储的基本思想:对于图的每个顶点vi,将所有邻接于vi的顶点链成一个单链表,称为顶点vi的边表(对于有向图则称为出边表),所有边表的头指针和存储顶点信息的一维数组构成了顶点表。邻接表有两种结点结构:顶点表结点和边表结点。

vertex firstedge adjvex next
顶点表 边 表 vertex:数据域,存放顶点信息。
firstedge:指针域,指向边表中第一个结点。
adjvex:邻接点域,边的终点在顶点表中的下标。
next:指针域,指向边表中的下一个结点。定义邻接表的结点 struct ArcNode
{ int adjvex;
ArcNode *next; adjvex next
};
template
struct VertexNode
{
T vertex;
ArcNode *firstedge; vertex firstedge
};

无向图的邻接表
边表中的结点表示什么?
每个结点对应图中的一条边,
邻接表的空间复杂度为O(n+e)。
无向图的邻接表
如何求顶点 i 的度?
顶点i的边表中结点的个数。 
无向图的邻接表
如何判断顶点 i 和顶点 j 之间是否存在边? 测试顶点 i 的边表中是否存在终点为 j 的结点。

有向图的邻接表
如何求顶点 i 的出度?
顶点 i 的出边表中结点的个数。
vertex firstedge
0
1
2
有向图的邻接表
1
2
有向图的邻接表
V1 V2 如何求顶点 i 的所有邻接点?遍历顶点 i 的边表,该边表中的所有终点都是顶点 i 的邻接点。
V3 V4 vertex firstedge
0
1
2

网图的邻接表

邻接表存储
有向图的类
const int MaxSize=10; //图的最大顶点数 template class ALGraph // Adjacency List struct ArcNode
{ { int adjvex; public: ArcNode *next;
ALGraph(T a[ ], int n, int e); };
~ALGraph; template void DFSTraverse(int v); struct{ VertexNode void BFSTraverse(int v); T vertex;
private: ArcNode *firstedge;
VertexNode adjlist[MaxSize]; };
int vertexNum, arcNum; };
邻接表中图的基本操作——构造函数

  1. 确定图的顶点个数和边的个数;
  2. 输入顶点信息,初始化该顶点的边表;
  3. 依次输入边的信息并存储在边表中;
    3.1 输入边所依附的两个顶点的序号i和j;
    3.2 生成邻接点序号为j的边表结点s;
    3.3 将结点s插入到第i个边表的头部;
    邻接表中图的基本操作——构造函数
    template
    ALGraph::ALGraph(T a[ ], int n, int e)

{
vertexNum=n; arcNum=e; struct ArcNode{ int adjvex;
for (i=0; i //输入顶点信息,初始化边表 };
{ template struct VertexNode adjlist[i].vertex=a[i]; {
adjlist[i].firstedge=NULL; T vertex;ArcNode *firstedge;
} };
struct ArcNode
for (k=0; k //输入边的信息存储在边表中 };
template
{ struct VertexNode cin>>i>>j; {
T vertex; s=new ArcNode; s->adjvex=j; ArcNode *firstedge; s
s->next=adjlist[i].firstedge; adjlist[i].firstedge=s;
} 0 } 1
2
3
邻接表中图的基本操作——深度优先遍历
template struct ArcNode
void ALGraph::DFSTraverse(int v) { int adjvex; ArcNode *next;
{ };
cout< { }; j=p->adjvex; if (visited[j]==0) DFSTraverse(j); p=p->next;
}
}
邻接表中图的基本操作——广度优先遍历 template
void ALGraph::BFSTraverse(int v)
{ front=rear=-1;
cout< { v=Q[++front]; p=adjlist[v].firstedge; while (p!=NULL)
{ j= p->adjvex; if (visited[j]==0) {
cout< }
p=p->next;
}
}
}
2 ∧
逆邻接表 0
1 ∧
3 ∧
3 ∧
0 ∧
2 ∧
逆邻接表 2
3
邻接表:有向图中对每个结点建立以Vi为尾的弧的单链表,
即:出边表逆邻接表:有向图中对每个结点建立以Vi为头的弧的单链表
十字链表
2 ∧
0
1 ∧
3 ∧
3 ∧
0 ∧
2 ∧
逆邻接表 2
3
将邻接表与逆邻接表合二为一?为什么要合并?十字链表的结点结构
vertex firstin firstout tailvex headvex headlink taillink
顶点表结点 边表结点
vertex:数据域,存放顶点信息; firstin:入边表头指针;
firstout:出边表头指针;
tailvex:弧的起点在顶点表中的下标; headvex:弧的终点在顶点表中的下标; headlink:入边表指针域,指向弧头相同的下一条弧; taillink:出边表指针域,指向弧尾相同的下一条弧。
十字链表存储有向图
v1v2 v1v3 
图的存储结构的比较——邻接矩阵和邻接表
空间性能
时间性能
适用范围

邻接矩阵 O(n2) O(n2) 稠密图 唯一
O(n+e) O(n+e) 稀疏图 不唯一
(边的排列)

无向图的连通性
要想判定一个无向图是否为连通图,或有几个连通分量,通过对无向图遍历即可得到结果。
连通图:仅需从图中任一顶点出发,进行深度优先搜索(或广度优先搜索),便可访问到图中所有顶点。
非连通图:需从多个顶点出发进行搜索,而每一次从一个新的起始点出发进行搜索过程中得到的顶点访问序列恰为其各个连通分量中的顶点集。
图G3的邻接表
优先搜索遍历,三次调用DFS过程:
这三个顶点集分别加上所有依附于这
个连通分量。
求无向图的连通分量

  1. count=0; //遍历算法调用的次数
  2. for (图中每个顶点v)
    2.1 if (v尚未被访问过)
    2.1.1 count++;
    2.1.2 从v出发遍历该图;
  3. if (count== 1) cout<<“图是连通的”; else cout<<“图中有”<

有向图的连通性
⑴ 从某顶点出发进行深度优先遍历,并按其所有邻接点都访问(即出栈)的顺序将顶点排列起来。
⑵ 从最后完成访问的顶点出发,沿着以该顶点为头的弧作逆向的深度优先遍历。若不能访问到所有顶点,则从余下的顶点中最后访问的那个顶点出发,继续作逆向的深度优先遍历,直至有向图中所有顶点都被访问到为止。
⑶ 每一次逆向深度优先遍历所访问到的顶点集便是该有向图的一个强连通分量的顶点集。
生成树
设E(G)为连通子图G中所有边的集合,
•则从图中任一顶点出发遍历图时,必定将E(G)分成两个集合:T(G)和B(G),其中T(G)是遍历过程中历经的边的集合。 •T(G)和图G中所有顶点一起构成连通图G的极小连通子图,按照定义(生成树:n个顶点的连通图G的生成树是包含G中全部顶点的一个极小连通子图。),它是连通图的一棵生成树,并且称
– 由深度优先搜索得到的为深度优先生成树;
– 由广度优先搜索得到的为广度优先生成树
(生成树不唯一)
生成树

(a)深度优先生成树 (b)广度优先生成树
例深度遍历:V1⇒ V2 ⇒V4 ⇒ V8 ⇒V5 ⇒V3 ⇒V6 ⇒V7
广度遍历:V1⇒ V2 ⇒V3 ⇒ V4 ⇒V5 ⇒V6 ⇒V7 ⇒V8
深度优先生成树 广度优先生成树
说明:一个连通图
可以有许多棵不同的生成树
所有生成树具有以下共同特点:
♣生成树的顶点个数与图的顶点个数相同
♣生成树是图的极小连通子图
♣一个有n个顶点的连通图的生成树有n-1条边
♣生成树中任意两个顶点间的路径是唯一的
♣在生成树中再加一条边必然形成回路
含n个顶点n-1条边的图不一定是生成树
对于非连通图
每个连通分量中的顶点集和遍历时走过的边一起构成若干棵生成树,这些连通分量的生成树组成非连通图的生成森林。例如,图示为G3的深度优先生成森林,它由三棵深度优先生成树组成。
G3
G3的深度优先生成森林

最小生成树(Minimum Spanning Tree)
生成树的代价:设G=(V,E)是一个无向连通网,生成树上各边的权值之和称为该生成树的代价。
最小生成树:在图G所有生成树中,代价最小的生成树称为最小生成树。
最小生成树的概念可以应用到许多实际问题中。
例:在n个城市之间建造通信网络,至少要架设n-1条通信线路,而每两个城市之间架设通信线路的造价是不一样的,那么如何设计才能使得总造价最小?
MST性质
假设G=(V, E)是一个无向连通网,U是顶点集V的一个非空子集。若(u, v)是一条具有最小权值的边,其中u∈U,v∈V-U,则必存在一棵包含边(u, v)的最小生成树。

MST性质 用反证法证明:
假设网N的任何一棵最小生成树都不包含(u ,v)。
设T是连通网上的一棵最小生成树,
当将边(u,v)加入到T中时,由生成树的定义,T中必存在一条包含(u,v)的回路。
另一方面,由于T是生成树,则在T上必存在另一条边(u’,v’),其中u’∈U,v’∈V-U,且u和u’之间,v和v’之间均有路径相通。
删去边(u’, v’),便可消除上述回路,同时得到另一棵生成树T,。
因为(u,v)的代价不高于(u’,v’),则T’的代价亦不高于T,T’是包含(u,v)的一棵最小生成树。由此和假设矛盾。

两个利用MST性质构造最小生成树的算法:普里姆(Prim)算法克鲁斯卡尔(Kruskal)算法 
普里姆(Prim)算法
基本思想:设G=(V, E)是具有n个顶点的连通网, T=(U, TE)是G的最小生成树, T的初始状态为 U={u0}(u0∈V),TE={ },重复执行下述操作:在所有u∈U,v∈V-U的边中找一条代价最小的边
(u, v)并入集合TE,同时v并入U,直至U=V。
关键:是如何找到连接U和V-U的最短边。
利用MST性质,可以用下述方法构造候选最短边集:对应V-U中的每个顶点,保留从该顶点到U中的各顶点的最短边。
U={A}
V-U={B, C, D, E, F} cost={(A, B)34, (A, C)46, (A, D)∞,(A, E)∞,(A, F)19} U={A, F} V-U={B, C, D, E} cost={(A, B)34,(F, C)25, (F, D)25,(F, E)26} U={A, F, C} V-U={B, D, E} cost={(A, B)34, (C, D)17,
(F, E)26}
U={A, F, C, D} V-U={B, E} cost={(A, B)34, (F, E)26} U={A, F, C, D, E}
V-U={B}
cost={(E, B)12}

数据结构设计
数组lowcost[n]:用来保存集合V-U中各顶点与集合U 中顶点最短边的权值,lowcost[v]=0表示顶点v已加入最小生成树中;
数组adjvex[n]:用来保存依附于该边(集合V-U中各顶点与集合U中顶点的最短边)在集合U中的顶点。
如何用数组lowcost和adjvex表示候选最短边集?
lowcost[i]=w 表示顶点vi和顶点vk之间的权值为w, adjvex[i]=k 其中:vi∈ V-U 且vk ∈U

普里姆算法构造最小生成树的过程

普里姆算法构造最小生成树的过程(续)
Prim算法——伪代码

  1. 初始化两个辅助数组lowcost和adjvex;
  2. 输出顶点u0,将顶点u0加入集合U中;
  3. 重复执行下列操作n-1次
    3.1 在lowcost中选取最短边,取adjvex中对应的顶点序号k;
    3.2 输出顶点k和对应的权值;
    3.3 将顶点k加入集合U中;
    3.4 调整数组lowcost和adjvex;
    克鲁斯卡尔(Kruskal)算法
    基本思想:设无向连通网为G=(V, E),令G的最小生成树为T=(U, TE),其初态为U=V,TE={ },图G中每个顶点自成一个连通分量。按照边的权值由小到大的顺序,在G的边集E中选择代价最小的边,
    •若该边依附的顶点属于T中不同的连通分量上,则将此边作为最小生成树的边加入到T中,
    •否则舍去此边而选择下一条代价最小的边。
    •依此类推,直至所有顶点都在同一连通分量上。
    此连通分量便为G的一棵最小生成树。
    B
    A E
    F
    C D
    连通分量={A}, {B}, {C}, {D}, {E}, {F}

连通分量={A}, {B}, {C}, {D}, {E}, {F}
{A}, {B, E}, {C}, {D}, {F}
C D
连通分量={A}, {B, E}, {C}, {D}, {F}
{A, F}, {B, E}, {C}, {D}
17
连通分量={A, F}, {B, E}, {C}, {D}
{A, F}, {B, E}, {C, D}
17
连通分量={A, F}, {B, E}, {C, D}
{A, F, C, D}, {B, E}
17
连通分量={A, F, C, D}, {B, E}
{A, F, C, D, B, E}

Kruskal算法构造最小生成树

Kruskal算法——伪代码

  1. 初始化:U=V; TE={ };
  2. 循环直到T中的连通分量个数为1
    2.1 在E中寻找最短边(u,v);
    2.2 如果顶点u、v位于T的两个不同连通分量,则
    2.2.1 将边(u,v)并入TE;
    2.2.2 将这两个连通分量合为一个;
    2.3 在E中标记边(u,v),使得(u,v)不参加后续最短边的选取;
    比较两种算法
    算法名 普里姆算法 克鲁斯卡尔算法时间复杂度 O(n2) O(eloge) 适应范围 稠密图 稀疏图

最短路径
在非网图中,最短路径是指两顶点之间经历的边数最少的路径。
AE:1
ADE:2
ADCE:3 ABCE:3 最短路径
在网图中,最短路径是指两顶点之间经历的边上权值之和最短的路径。
AE:100 ADE:90
ADCE:60
ABCE:70
单源点最短路径问题
问题描述:给定带权有向图G=(V, E)和源点v∈V,求从v到G中其余各顶点的最短路径。
应用实例——计算机网络传输的问题:怎样找到一种最经济的方式,从一台计算机向网上所有其它计算机发送一条消息。
迪杰斯特拉(Dijkstra)提出了一个按路径长度递增的次序产生最短路径的算法——Dijkstra算法。
Dijkstra算法
基本思想:设置一个集合S存放已经找到最短路径的顶点,S的初始状态只包含源点v 。
•初使时令 S={v},T={其余顶点},T中顶点对应的距离值:
若存在< v, vi >,为< v, vi >弧上的权值
若不存在< v, vi>,为∝
•从T中选取一个其距离值为最小的顶点vk ,加入S
•对T中顶点的距离值进行修改:若加进vk作中间顶点,从v到vi 的距离值比不加vk 的路径要短,则修改此距离值
•重复上述步骤,直到S中包含所有顶点,即S=V为止。
Dijkstra算法的基本思想

迪杰斯特拉算法示例:
终点 从v0到各终点的距离值和最短路径的求解过程
v1 ∞ ∞ ∞ ∞ ∞
v2 10, (v0,v2)
v3 ∞ 60, (v0,v2,v3) 50, (v0,v4,v3)
v4 30, (v0,v4) 30, (v0,v4)
v5 100,(v0,v5) 100,(v0,v5) 90, (v0,v4,v5) 60, (v0,v4,v3,v5)
vk v2 v4 v3 v5
S {v0,v2} {v0,v2,v4} {v0,v2,v3,v4} {v0,v2,v3,v4,v5}
设计数据结构 :
图的存储结构:带权的邻接矩阵存储结构
数组dist[n]:每个分量dist[i]表示当前所找到的从始点v到终点vi的最短路径的长度。初态为:若从v到vi 有弧,则dist[i]为弧上权值;否则置dist[i]为∞。
数组path[n]:path[i]是一个字符串,表示当前所找到的从始点v到终点vi的最短路径。初态为:若从v到 vi有弧,则path[i]为vvi;否则置path[i]空串。
数组s[n]:存放源点和已经生成的终点,其初态为只有一个源点v。
Dijkstra算法——伪代码

  1. 初始化数组dist、path和s;
  2. while (s中的元素个数 2.1 在dist[n]中求最小值,其下标为k;
    2.2 输出dist[j]和path[j];
    2.3 修改数组dist和path;
    2.4 将顶点vk添加到数组s中;时间复杂度为O(n2)
    每一对顶点之间的最短路径
    问题描述:给定带权有向图G=(V, E),对任意顶点 vi,vj∈V(i≠j),求顶点vi到顶点vj的最短路径。
    解决办法1:每次以一个顶点为源点,调用Dijkstra 算法n次。显然,时间复杂度为O(n3)。
    解决办法2:弗洛伊德提出的求每一对顶点之间的最短路径算法——Floyd算法,其时间复杂度也是
    O(n3),但形式上要简单些。
    Floyd算法基本思想:假设求从顶点vi 到vj 的最短路径。
    如果从vi 到vj 有弧,则从vi 到vj 存在一条长度为arcs[i][j]的路径,该路径不一定是最短路径,尚需进行n次试探:
    在路径上增加一个顶点v0 ,考虑路径(vi , v0, vj )是否存在(即判别弧(vi , v0 )和
    (v0 , vj )是否存在)。
    •如果存在,则比较(vi,vj )和(vi,v0,vj )的路径长度,取长度较短者为从vi到vj 的中间顶点的序号不大于0的最短路径。
    在路径上增加一个顶点v1,如果(vi,…, v1)和(v1 ,…,vj )分别是当前找到的中间顶点的序号不大于0的最短路径,那么(vi ,…, v1 ,…, vj )就有可能是从vi到vj 的中间顶点的序号不大于1的最短路径。
    将它和已经得到的从vi到vj 中间顶点序号不大于0的最短路径相比较,从中选出中间顶点的序号不大于1的最短路径在路径上再增加一个顶点v2,继续进行试探…… 依次类推,在经过n次比较后,最后求得的必是从顶点vi到顶点vj的最短路径。
    AB AC
    BA BC
    CA
    AB AC
    BA BC
    CA CAB
    AB ABC
    BA BC
    CA CAB
    Floyd算法例 初始:06 0 4 112路径:
    3 ∝ 0
    0 4 11
    加入A:6 0 2路径:
    3 7 0
    0 4 6
    加入B:6 0 2路径:
    3 7 0
    AB ABC
    BCA BC
    CA CAB
    0 4 6
    加入C:5 0 2路径:
    3 7 0
    设计数据结构图的存储结构:带权的邻接矩阵存储结构
    数组dist[n][n]:存放在迭代过程中求得的最短路径长度。迭代公式:
    dist-1[i][j]=arc[i][j]
    dist k[i][j]=min{distk-1[i][j], distk-1[i][k]+distk-1[k][j]}
    0≤k ≤n-1
    数组path[n][n]:存放从vi到vj的最短路径,初始为 path[i][j]=“vivj”。
    Floyd算法——C++描述
    void Floyd(MGraph G)
    { for (i=0; i for (j=0; j { dist[i][j]=G.arc[i][j]; if (dist[i][j]!=∞) path[i][j]=G.vertex[i]+G.vertex[j];
    else path[i][j]="";
    }
    Floyd算法——C++描述
    for (k=0; k if (dist[i][k]+dist[k][j] }
    }

AOV网(Activity On Vertex)
什么是工程?工程有什么共性?
AOV网:在一个表示工程的有向图中:
用顶点表示活动,
用弧表示活动之间的优先关系,称这样的有向图为顶点表示活动的网,简称AOV网。
AOV网中出现回路意味着什么?
AOV网
什么是工程?工程有什么共性?
AOV网:在一个表示工程的有向图中:
用顶点表示活动,
用弧表示活动之间的优先关系,
称这样的有向图为顶点表示活动的网,简称AOV网。
AOV网特点:
1.AOV网中的弧表示活动之间存在的某种制约关系。
2.AOV网中不能出现回路。
编号 课程名称 先修课
C1 高等数学 无
C2 计算机导论 无
C3 离散数学 C1
C4 程序设计 C1, C2
C5 数据结构 C3,C4
C6 计算机原理 C2,C4
C7 数据库原理 C4,C5,C6
AOV网 
拓扑排序
拓扑序列:设G=(V,E)是一个具有n个顶点的有向图,V中的顶点序列v1, v2, …, vn称为一个拓扑序列,当且仅当满足下列条件:若从顶点vi到vj有一条路径,则在顶点序列中顶点vi必在顶点vj之前。拓扑排序:对一个有向图构造拓扑序列的过程称为拓扑排序。
拓扑序列使得AOV网中所有应存在的前驱和后继关系都能得到满足。
拓扑排序
基本思想:
⑴从AOV网中选择一个没有前驱的顶点并且输出;
⑵ 从AOV网中删去该顶点,并且删去所有以该顶点为尾的弧;
⑶重复上述两步,直到全部顶点都被输出,或AOV 网中不存在没有前驱的顶点。
拓扑排序的结果?
拓扑排序

拓扑序列: C1, C2, C3, C4, C5, C6, C7
拓扑排序
说明AOV网中存在回路。
拓扑序列: C1, C2, C3,
设计数据结构

  1. 图的存储结构:采用邻接表存储,在顶点表中增加一个入度域。
    in vertex firstedge 顶点表结点
  2. 栈S:存储所有无前驱的顶点。
    拓扑排序

(a) 一个AOV网 (b) AOV网的邻接表存储
拓扑排序

拓扑排序

拓扑排序

拓扑排序

拓扑排序

拓扑排序
A in vertex firstedge 
拓扑排序算法——伪代码

  1. 栈S初始化;累加器count初始化;
  2. 扫描顶点表,将没有前驱的顶点压栈;
  3. 当栈S非空时循环
    3.1 vj=退出栈顶元素;输出vj;累加器加1;
    3.2 将顶点vj的各个邻接点的入度减1;
    3.3 将新的入度为0的顶点入栈;
  4. if (count

AOE网(Activity On Edge) ——边表示活动的网。边表示活动的网。
问题提出把工程计划表示为有向图,用顶点表示事件,弧表示活动;
每个事件表示在它之前的活动已完成,在它之后的活动可以开始,例 设一个工程有11项活动,9个事件事件 v1——表示整个工程开始事件v9——表示整个工程结束
问题:(1)完成整项工程至少需要多少时间?
(2)哪些活动是影响工程进度的关键?
AOE网(Activity On Edge) ——边表示活动的网。边表示活动的网。
在一个表示工程的带权有向图中:
用顶点表示事件(Event) ,
用有向边表示活动,
边上的权值表示活动的持续时间,称这样的有向图叫做边表示活动的网,简称AOE网。 AOE网中没有入边的顶点称为始点(或源点),没有出边的顶点称为终点(或汇点)。
AOE网的性质:
⑴只有在某顶点所代表的事件发生后,从该顶点出发的各活动才能开始;
⑵只有在进入某顶点的各活动都结束,该顶点所代表的事件才能发生。
事件 事件含义
v1 开工

v2 活动a1完成,活动a4可以开始
v3 a2完成,活动a5可以开始活动
… ………
v9 a10 和a11完成,整个工程完成活动

从始点到终点的路径可能不止一条,只有各条路径上所有活动都完成了,整个工程才算完成。
完成整个工程所需的最短时间取决于从始点到终点的最长路径长度,即这条路径上所有活动的持续时间之和。这条路径长度最长的路径就叫做关键路径。
关键路径
关键路径:在AOE网中,从始点到终点具有最大路径长度(该路径上的各个活动所持续的时间之和)的路径称为关键路径。
关键活动:关键路径上的活动称为关键活动。
由于AOE网中的某些活动能够同时进行,故完成整个工程所必须花费的时间应该为始点到终点的最大路径长度。关键路径长度是整个工程所需的最短工期。
关键路径
要找出关键路径,必须找出关键活动, 即不按期完成就会影响整个工程完成的活动。
首先计算以下与关键活动有关的量:
⑴事件的最早发生时间ve[k]
⑵事件的最迟发生时间vl[k]
⑶活动的最早开始时间e[i]
⑷活动的最晚开始时间l[i]
最后计算各个活动的时间余量 l[k] - e[k],时间余量为
0者即为关键活动。
⑴事件的最早发生时间ve[k]
ve[k]是指从始点开始到顶点vk的最大路径长度。这个长度决定了所有从顶点vk发出的活动能够开工的最早时间。
ve[1]=0
ve[k]=max{ve[j]+len} (∈p[k]) p[k]表示所有到达vk的有向边的集合 ve[k]=max{ve[j]+len} 
⑵事件的最迟发生时间vl[k]
vl[k]是指在不推迟整个工期的前提下,事件vk允许的最晚发生时间。

vl[n]=ve[n]
vl[k]=min{vl[j]-len}(∈s[k])
s[k]为所有从vk发出的有向边的集合 vl[k]=min{vl[j]-len} 
⑶活动的最早开始时间e[i]
若活动ai是由弧表示,则活动ai的最早开始时间应等于事件vk的最早发生时间。因此,有:
e[i]=ve[k]
e[i]=ve[k]
活动 e
a1 0
a2 0
a3 0
a4 6
a5 4
a6 5
a7 7
a8 7
a9 7
a10 16
a11 14
⑷活动的最晚开始时间l[i]
活动ai的最晚开始时间是指,在不推迟整个工期的前提下, ai必须开始的最晚时间。
若ai由弧表示,则ai的最晚开始时间要保证事件vj的最迟发生时间不拖后。因此,有: l[i]=vl[j]-len
l[i]=vl[j]-len
活动 e l l-e
a1 0 0 0 
a2 0 2 2
a3 0 3 3
a4 6 6 0 
a5 4 6 2
a6 5 8 3
a7 7 7 0 
a8 7 7 0 
a9 7 10 3
a10 16 16 0 
a11 14 14 0 

活动 e l l-e
a1 0 0 
a2 0 2 2
a3 0 3 3
a4 6 6 
a5 4 6 2
a6 5 8 3
a7 7 7 
a8 7 7 
a9 7 10 3
a10 16 16 
a11 14 14 
求关键路径步骤 
应用举例——关键路径
课堂练习:求u0到其他各顶点的最短路径。

应用举例——关键路径
S T l(a) l(b) l© l(d) l(e) l(f) l(g)
1 u0 abcdefg 8 4 ∞ ∞ 2 1 7
2 u0 f abcdeg 8 4 ∞ 4 2 7
3 u0 fe abcdg 8 4 ∞ 3 7
4 u0 fed abcg 8 4 9 6
5 u0 fedb acg 6 9 6
6 u0 fedba cg 9 6
7 u0 fedbag c 9
8 u0 fedbagc

你可能感兴趣的:(@图)