第六章 图

第6章 图

【学习重点】

① 图的基本术语;

② 图的邻接矩阵存储和邻接表存储;

③ 图的遍历操作及算法实现;

④ 最小生成树算法、最短路径算法、拓扑排序算法和关键路径算法基于的存储结构以及算法的执行过程。

【学习难点】

① 运用图的遍历算法解决图的其他相关问题;

② 最小生成树算法;

③ 最短路径算法;

④ 拓扑排序算法;

⑤ 关键路径算法。

 

6.1 图的逻辑结构

6.1.1 图的定义和基本术语

在图中常常将数据元素称为顶点。

1.图的定义

图是由顶点的有穷非空集合和顶点之间边的集合组成,通常表示为G=(V,E) 其中,G表示一个图,V是图G中顶点的集合,E是图G中顶点之间边的集合。若顶点i 和  v j 之间的边没有方向,则称这条边为无向边,用无序偶对(i ,j)来表示;若从顶点i 到j 的边有方向,则称这条边为有向边(也称为弧),用有序偶对< v i ,v j >来表示,i 称为弧尾,j 称为弧头。如果图的任意两个顶点之间的边都是无向边,则称该图为无向图,否则称该图为有向图。

2.图的基本术语

简单图

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

邻接、依附

在无向图中,对于任意两个顶点i 和  v j ,若存在边(i ,j ),则称顶点i 和  v j  互为邻接点,同时称边    (i ,j )依附于顶点i 和  v j 。

在有向图中,对于任意两个顶点i 和  v j ,若存在弧i ,j >,则称顶点i 邻接到 v j 邻接自i ,同时称弧    i ,j >依附于顶点i 和  v j 。在不致混淆的情况下,通常称i 是j 的邻接点。

无向完全图、有向完全图

在无向图中,如果任意两个顶点之间都存在边,则称该图为无向完全图。含有n个顶点的无向完全图有n*(n-1)条边。

在有向图中,如果任意两顶点之间都存在方向互为相反的两条弧,则称该图为有向完全图。含有n个顶点的有向完全图有n*(n-1)

显然,在完全图中,边(或弧)的数目达到最多。

稠密图、稀疏图

    称边数很少的图为稀疏图,反之,称为稠密图。稀疏和稠密本身就是模糊的概念,稀疏图和稠密图常常是相对而言的。

顶点的度、入度、出度

在无向图中,顶点v的度是指依附于该顶点的边的个数,记为TDv)。在具有n个顶点e条边的无向图中,一共有2e条边。

在有向图中,顶点v的入度是指以该顶点为弧头的弧的个数,记为IDv);顶点v的出度是指以该顶点为弧尾的弧的个数,记为ODv)。在具有n个顶点e条边的有向图中,一共有e条边。

权、网

    在图中,权通常是指对边赋予的有意义的数量值。在实际应用中,权可以有具体的含义。比如,对于城市交通线路图,边上的权表示该条线路的长度或者等级;对于电子线路图,边上的权表示两个端点之间的电阻、电流或电压值;对于工程进度图,边上的权表示从前一个活动到后一个活动所需的时间等。

    边上带权的图称为网或网图。

路径、路径长度、回路

在无向图G=(V,E)中,顶点p 到q 之间的路径是一个顶点序列p =v q ,i1 ,…,v im = v q,其中,(ij-1,v ij )属于E1<=j<=m;如果G是有向图,则ij-1,v ij >属于E1<=j<=m)。路径上边的数目称为路径长度。第一个顶点和最后一个顶点相同的路径称为回路或环。

显然,在图中路径可能不唯一,回路也可能不唯一。

简单路径、简单回路

    在路径序列中,顶点不重复出现的路径称为简单路径。除了第一个顶点和最后一个顶点之外,其余顶点不重复出现的回路称为简单回路。

子图

    对于图G=V,E)和g=(v,e),如果vV中且eE中,则称图gG的子图。一个图可以有多个子图。

连通图、连通分量

    在无向图中,若任意顶点v i和j(i!=j)之间有路径,则称该图是连通图。非连通图的极大连通图子图称为连通分量,“极大”的含义是指包括所有连通的顶点以及和这些顶点相关联的所有边。 

强连通图、强连通分量

在有向图中,对任意顶点v i和j,若从顶点v i到j  均有路径,则称该有向图是强连通图。非强连通图的极大强连通图子图称为强连通分量。

 

生成树、生成森林

具有n个顶点的连通图G的生成树是包含G中全部顶点的一个极小连通子图。连通图的生成树是一个自由树,可以在生成树中任意指定一个顶点为数的根结点。在生成树中添加任意一条属于原图中的边必定会产生回路,因为新添加的边使其所依附的两个顶点之间有了第二条路径;在生成树中减少任意一条边,则必然成为非连通。所以一棵具有n个顶点的生成树有且仅有n-1条边。

具有n个顶点的有向图G的生成树是包含G中全部顶点的一个子图,且子图中只有一个入度为零的顶点,其他顶点的入度均为1

在非连通图中,由每个连通分量都可以得到一棵生成树,这些连通分量的生成树构成了非连通图的生成森林。

6.1.2图的抽象数据类型定义

ADT  Graph

Data

定点的有穷非空集合和边的集合

前置条件:图不存在

输入:n个顶点e条边

功能:图的初始化

输出:无

后置条件:构造一个含有n个顶点e条边的图

DestroyGraph

前置条件:图已存在

输入:无

功能:销毁图

输出:无

后置条件:释放图所占用的存储空间

DFSTraverse

前置条件:图已存在

输入:遍历的起始顶点v

功能:从顶点v出发深度优先遍历图

输出:图的深度优先遍历序列

后置条件:图保持不变

BFSTraverse

前置条件:图已存在

输入:遍历的起始顶点v

功能:从顶点v出发的广度优先遍历图

输出:图的广度优先遍历序列

后置条件:图保持不变

endADT

6.1.3图的遍历操作

图的遍历是指从图中某一顶点出发,对图中所有顶点访问一次且仅访问一次。

(1) 图中没有确定的开始顶点,所以可从图中任意顶点出发,不妨将顶点进行编号,先从编号小的顶点开始。

(2) 要遍历图中所有顶点,只只许多次重复从某一顶点出发进行图的遍历。

(3) 为了在便利过程中便于区分顶点是否已被访问,设置一个访问标志数组visited[n],其初值为未被访问标志“0”。

(4) 图的遍历通常有深度优先遍历和广度优先遍历两种,这两种遍历次序对无向图和有向图都适用。

1. 深度优先遍历

深度优先遍历类似于树的前序遍历

从图中某顶点v出发进行深度优先遍历的基本思想是:

(1) 访问顶点v

(2) 从v的未被访问的邻接点中选取一个顶点w,从w出发进行深度优先遍历;

(3) 重复上述两步,直至图中所有和v有路径相同的顶点都被访问到。

伪代码:

1. 访问顶点vvisited[v]=1

2. w=顶点v的第一个邻接点;

3. whilew存在)

3.1 ifw未被访问)从顶点w出发递归执行该算法;

3.2 w=顶点v的下一个邻接点;

2. 广度优先遍历

广度优先遍历类似于树的层序遍历。

从图中某顶点v出发进行广度优先遍历的基本思想是:

(1) 访问顶点v

(2) 一次访问v的各个未被访问的邻接点v1v2.....vk;

(3) 分别从v1,v2,.….vk出发依次访问它们未被访问的邻接点,兵士“先被访问顶点的邻接点”先于“后被访问顶点的邻接点”被访问。

伪代码:

1. 初始化队列Q

2. 访问顶点vvisited[v]=1;顶点v入队列Q

3. while(队列Q非空)

3.1 v=队列 Q的队头元素出队;

3.2 w=顶点v的第一个邻接点;

3.3 while(w存在)

3.3.1 如果w未被访问,则访问顶点w;visited[w]=1;顶点w入队列Q

W=顶点v的下一个邻接点;

6.1.3  图的遍历操作

   图的遍历是指从图中某一顶点出发,对图中所有定点访问一次且仅访问一次。

在图的遍历中要解决的关键问题是:

(1)没有一个确定的开始顶点,如何选取遍历的起始顶点?答:一般选编号比较小的顶点。

2)从某个顶点出发可能到达不了所有其他顶点。答:多次重复从某个顶点出发进行图的遍历。

3) 如何避免遍历不会因回路而陷入死循环?答:设置一个访问标志数组

4) 一个定点于多个顶点相邻接,当这样的顶点访问过后,如何选取下一个要访问的顶点?答:利用遍历次序

深度优先遍历

(1) 访问顶点v

(2) 从v的未被访问的邻接点中选取一个顶点w,从w出发进行深度优先遍历;

(3) 重复上述两步,直至图中所有和v有路径相通的顶点都被访问。

伪代码1.访问顶点v visited[v]=1;

2.w=顶点v的第一个邻接点;

3.while (w存在)

3.1 ifw未被访问)从顶点w出发递归执行该算法;

3.2 w=顶点v的下一个邻接点;

广度优先遍历

(1) 访问顶点v

(2) 依次访问v的各个未被访问的邻接点v1v2…vk

(3) 分别从v1v2…vk出发访问他们未被访问的邻接点,并使“先被访问顶点的邻接点”先于“后被访问顶点的邻接点”被被访问。直至图中所有与顶点v有路径相通的顶点都被访问到。

伪代码

1. 初始化队列Q

2. 访问顶点vvisited[v]=1;顶点入队列Q

3. while( 队列Q非空)

3.1 v=队列Q的对头元素出队;

3.2 w=顶点v的第一个邻接点;

3.3 whilew存在)

3.3.1 如果w未被访问,则访问顶点w; visited[w]=1;顶点w入队列Q;

3.3.2 w=顶点v的下一个邻接点。

 

 

6.2图的存储结构及实现

6.2.1邻接矩阵

图的邻接矩阵存储也称数组表示法,其方法是用用一个以为数组存储图中顶点的信息,用一个二维数组存储图中边的信息,存储顶点之间的邻接关系的二维数组称为邻接矩阵。

无向图的邻接矩阵一定是对称矩阵,而有向图的邻接矩阵则不一定对称。

在图的邻接矩阵存储中容易解决下列问题:

(1) 对于无向图,定点i的度等于邻接矩阵的第i行非零元素的个数。

对于有向图,顶点i的初度等于邻接矩阵低i行非零元素的个数;顶点i的入度等于邻接矩阵第i行非零元素的个数。

(2) 要判断顶点ij之间是否存在边,只需测试临街矩阵中相应位置的元素arc[i][j],若其值为1,则有边;否则,顶点ij之间不存在边。

(3) 找顶点i的所有临界点,可依次判别顶点 i与其他顶点之间是否有边或顶点i到其它顶点是否有弧。

邻邻接矩阵存储结构的抽象数据类型定义:

const int MaxSize=10;

template

class MGraph

{

Public :

   MGraph(DataType a[],int n,int e);

   ~MGraph(){}

   void DFSTraverse(int v);

   void BFSTraverse(int v);

 private:

 DataType vertex[MaxSize];

 int arc[MaxSize][MaxSize];

 int vertexNUM,arcNUM;

} ;         

邻接矩阵构造函数算法 MGraph  

template 

MGraph::MGraph(DataType a[],int n,int e)

{

vertexNUM=n;arcNUM=e;

for(i=0;i

vertex[i]=a[i];

for(i=0;i

for(j=0;j

arc[i][j]=0;

for(k=0;k

{

cin>>i>>j;

arc[i][j]=1;

arc[j][i]=1;

}

}

深度优先遍历DFSTraverse

                   

template 

void MGraph::DFSTraverse (int v)

{

cout<

for(j=0;j

if(arc[v][j]==1&&visited[j]==0)DFSTraverse(j);

}

广度优先遍历算法BFSTraverse

template 

void MGraph::BFSTraverse(int v)

{

front=rear=-1;

cout<

while(front!=rear)

{

v=Q[++front];

    for(j=0;j

ife(arc[v][j]==1&&visited[j]==0)

{

cout<

}

}

}

 

 

 

6.3 最小生成树

G=VE)是一个无向连通图,生成数上各边的权值之和称为该生成树的代价,在G的所有生成树中,代价最小的生成树称为最小生成树。

6.3.1 MST性质

最小生成树具有MST性质:假设G=VE)是一个无向连通网U是顶点集V的一个非空子集。若(u,v)是一条具有最小权值的边,其中u属于Uv属于V-U,则必存在一颗包含边(u,v)的最小生成树。

6.3.2 Prim算法

最小生成树算法 Prim

void Prim(MGrap G)

{

for(i=1;i

{

shortEdge[i].lowcost=G.arc[0][i];

shortEdge[i].adjvex=0;

}

shortEdge[0].lowcost=0;

for (i=1;i

{

k=MinEdge(shortEdge,G.vertexNum)

cout<<"("<

shortEdge[k].lowcost=0;

for(j=1;j

if G.arc[k][j]

shortEdge[j].lowcost=G.arc[k][j];

shortEdge[j].adjvex=k;

}

}

}

6.3.3 Kruskal算法

最小生成树算法 Kruskal

void Kruskal(EdgeGraph G)

{

for(i=0;i

parent[i]=-1;

for(num=0,i=0;i

{

vex1=FindRoot(parent,G.edge[i].from);

vex2=FindRoot(parent,G.edge[i].to);

if(vex1!=vex2)

{

cout<<"("<

parent[vex2]=vex1;

num++;

if (num==n-1) return;

}

}

}

int FindRoot(int parent[],int v)

{

t=v;

if(parent[t]>-1) t=parent[t];

return t;

}

 

6.4 最短路径

在非网图中,最短路径是指两顶点之间经历的边数最少的路径,路径上的第一个顶点称为源点,最后一个顶点成为终点。

Dijkstra 算法

void Dijkstra(MGraph G,int v)

{

for(i=0;i

{

dist[i]=G.arc[v][i];

if(dist[i]!=)path[i]=G.vertex[v]+G.vertex[i];

else path[i]=" ";

}

s[0]=v;

dist[v]=0;

num=1;

while (num

{

for(k=0,i=0;i

if((dist[i]!=0)&&(dist[i]

cout<

s[num++]=k;

for(i=0;i

if(dist[i]>dist[k]+G.arc[k][i]){

dist[i]=dist[k]+G.arc[k][i];

path[i]=path[k]+G.vertex[i];

}

dist[k]=0;

}

}

Floyd 算法

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[j];

else path[i][j]=" ";

}

for(k=0;d

for(i<0;i

for(j=0;j

if(dist[i][k]+dist[k][j]

dist[i][j]=dist[i][k]+dist[k][j];

path[i][j]=path[i][k]+path[k][j];

}

 

6.5 有向无环图及其应用

6.5.1 AOV网与拓扑排序

     在一个表示工程的有向图中,用顶点表示活动,用弧表示活动之间的优先关系,称这样的有向图为顶点表示活动的网,简称AOV

   对一个有向图构造拓扑序列的过程称为拓扑排序。

AOV网进行拓扑排序的基本思想是:

(1) 从AOV网中选择一个没有前驱的顶点并且输出它;

(2) 从AOV网中删去该顶点,并且删去所有以该顶点为尾的弧;

(3) 重复上述两步,直到全部顶点都被输出,或AOV网中不存在没有前驱的顶点。

显然,拓扑排序的结果有两种:AOV网中全部顶点都被输出,这说明AOV网中不存在回路;AOV网中顶点未被全部输出,剩余的顶点均不存在没有前驱的顶点,这说明AOV网中存在回路。

 

 

 

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(第六章 图)