第六章 图
6.1
6.1.1 图的定义和基本术语
1.图的定义
图是由顶点的有穷非空集合和顶点之间边的集合组成,通常表示为:
G=(V,E)
其中G表示一个图,V是图G中顶点的集合,E是图G顶点之间边的集合。
如果图的任意两个顶点之间的边都是没有方向的,则称该图为无向图,否则为有向图。
2.图的基本术语
简单图
在图中,若不存在顶点到其自身的边,且同一条边部重复出现。
邻接、依附
在无向图中,任意两个顶点有边相连接,则互为邻接点,同时称边依附于顶点。
无向完全图、有向完全图
在无向完全图中,任意两个顶点都有边;
含n个节点的无向完全图有n*(n-1)/2条边。
有向图中,任意两顶点都有方向互相相反的两条弧;
含有n*(n-1)条边。
稠密图、稀疏图
称边少的为稀疏图,反之多的就是稠密图。(相对而言)
权、网
权通常是指对边赋予有意义的数量值。
边上带权的图称为网。
路径、路径长度、回路
路径长度=结点到根的边的个数;
第一个顶点和最后一个顶点有相同的路径称为回路;
简单路径、简单回路
简单路径:在路径序列中,顶点部重复出现。
简单回路:除了第一个顶点和最后一个顶点之外,其余顶点部重复出现。
子图
图G=(V,E)和G’=(V’,E’),如果V’属于V且E’属于E,则称G’是G的子图。
连通图、连通分量
连通图:在无向图中,若任意顶点之间有路径;
连通分量:非连通图的极大连通子图
强连通图、强连通分量
强连通图:在有向图中,对任意顶点i、j,若从顶点i到j均有路径;
强连通分量:非连通图的极大强连通子图。
生成树、生成森林
具有n个顶点的连通图G的生成树是包含G中全部顶点的一个极小连通子图。生成树可以任意指定一顶点为数的根。在生成树添加一条原图中的边,则必会产生回路,若减少一条边,则会成为非连通。所以一棵具有n个顶点的生成树有且仅有n-1条边。
6.1.2 图的抽象数据类型定义
图是一种与具体应用密切相关的数据结构,它的基本操作往往随应用不同而有很大差别。下面给出一个图的抽象数据类型定义的例子,简单起见,基本操作仅包含图的遍历,针对具体应用,需要重新定义其基本操作。
ADT Graph
Data
顶点的有穷非空集合和边的集合
Operation
InitGraph
前置条件:图不存在
输入:无
功能:图的初始化
输出:无
后置条件:构造一个空的图
DestroyGraph
前置条件:图已存在
输入:无
功能:销毁图
输出:无
后置条件:释放图所占用的存储空间
DFSTraverse
前置条件:图已存在
输入:遍历的起始顶点v
功能:从顶点v出发深度优先遍历图
输出:图中顶点的一个线性排列
后置条件:图保持不变
BFSTraverse
前置条件:图已存在
输入:遍历的起始顶点v
功能:从顶点v出发广度优先遍历图
输出:图中顶点的一个线性排列
后置条件:图保持不变
endADT
6.1.3 图的遍历操作
图的遍历操作是图中最基本的操作。是指从图中某一顶点出发,对图中所有顶点访问一次且仅访问一次。
图遍历中关键问题:
(1)在图中没有一个确定的开始结点任一个都可以作为开始结点;
解决方案:从编号小的顶点开始 。
(2)从某个顶点出发可能到达不了所有其他顶点;
解决方案:多次调用从某顶点出发遍历图的算法。
(3)由于可能存在回路,某些顶点可能被重复访问;
解决方案:附设访问标志数组visited[n]
(4)在图中,一个顶点可以与其它多个顶点相邻接,当这样的顶点访问过后,如何选取下一个要访问的结点?解决方案:深度优先遍历和广度优先遍历。
1.深度优先遍历
类似于树的前序遍历
基本思想:
(1) 访问顶点v;
(2) 从v的未被访问的邻接点中选取一个顶点w,从w出发进行深度优先遍历;
(3) 重复以上两步,直到所有路径都被访问到。
2.广度优先遍历
类似于层序遍历
(1) 访问顶点v;
(2) 依次访问v的各个未被访问的邻接点v1,v2,……vk;
(3) 分别从v1,v2,……vk出发依次访问他们未被访问的邻接点,并使“先被访问顶点的邻接点”先于“后被访问结点的邻接点”被访问。直至图中所有与顶点v有路径相通的顶点都被访问到。
6.2 图的存储结构及实现
一个图包括两部分信息:信息以及描述顶点之间关系的信息。
6.2.1邻接矩阵(数组表示法)
基本思想:用一个一维数组存储图中顶点的信息,用一个二维数组(称为邻接矩阵)存储图中各顶点之间的邻接关系。
假设图G=(V,E)有n个顶点,则邻接矩阵是一个n×n的方阵,定义为:
arc[i][j]={1 若(vi, vj)∈E(或<vi, vj>∈E)
0 其它
1.邻接矩阵构造函数算法
template<class dt>
MGraph<dt>::MGraph(dt a[],int n,int e)
{
vertexnum=n;arcnum=e;
for(i=0;i<vertexnum;i++)
vertex[i]=a[i];
for(i=0;i<vertexnum;i++)
for(j=0;j<vertexnum;j++)
arc[i][j]=0;
for(k=0;k<arcnum;k++)
{
cin>>i>>j;
arc[i][j]=1;
}
}
2.深度优先遍历算法
template<class dt>
void MGraph<dt>::DFSTraverse(int v)
{
cout<<vertex[v];visited=1;
for(j=0;j<vertexnum;j++)
if(arc[v][j]==1&&visited[j]==0) DFSTraverse(j);
}
3.广度优先遍历算法
template<class dt>
void MGraph<dt>::BFSTraverse(int v)
{
front=rear=-1;
cout<<vertex[v];visited[v]=1;Q[++rear]=v;
while(front!=rear)
{
v=Q[++front];
for(j=0;j<vertexnum;j++)
if(arc[v][j]==1&&visited[j]==0)
{
cout<<vertex[j];visited[j]=1;
Q[++rear]=j;
}
}
}
6.2.2 邻接表
1.邻接表:一种顺序存储与链接存储相结合的存储方法,类似于树的孩子链表表示法。
2.邻接表构造函数算法:
template<class dt>
ALGraph<dt>::ALGraph(dt a[],int n,int e)
{
vertexnum=n;arcnum=e;
for(i=0;i<vertexnum;i++)
{
adjlist[i].vertex=a[i];
adjlist[i].firstedge=NULL;
}
for(k=0;k<arcnum;k++)
{
cin>>i>>j;
s=new Arcnode;s->adjvex=j;
s->next=adjlist[i].firstedge;
adjlist[i].firstedge=s;
}
}
3.广度优先遍历算法
template<class dt>
void ALGraph<dt>::BFSTraverse(int v)
{
front=rear=-1;
cout<<adjlist[v].vertex;visited[v]=1;Q[++rear]=v;
while(front!=rear)
v=Q[++front];
p=adjlist[v].firstarc;
while(p!=NULL)
{
j=p->adjlist;
if(visited[j]==0)
{
cout<<adjlist[j].vertex;visited[j]=1;Q[++rear]=j;
}
p=p->next;
}
}
4.深度优先遍历算法
template<class dt>
void ALGraph<dt>::DFSTraverse(int v)
{
cout<<adjlist[v].vertex;visited=1;
p=adjlist[v].firstedge;
while(p!=NULL)
{
j=p->adjvex;
if(visited[j]==0) DFSTraverse(j);
p=p->next;
}
}
6.3 最小生成树
6.3.1 MST 性质
假设G=(V,E)是一个无向连通网,U是顶点集V的一个非空子集。若(u,v)是一条具有最小权值的边,其中u∈U,v∈V-U,则必存在一棵包含边(u,v)的最小生成树。
6.3.2 Prim 算法
void Prim(MGraph G)
{
for(i=1;i<G.vertexnum;i++)
{
shortedge[i].lowcost=G.arc[0][i];
shortedge.adjvex=0;
}
shortedge[0].lowcost=0;
for(i=1;i<G.vertexnum;i++)
{
k=minedge(shortedge,G.vertexnum)
cout<<"("<<k<<shortedge[k].adjvex<<")"<<shortedge[k].lowcost;
shortedge[k].lowcost=0;
for(j=1;j<G.vertexnum;j++)
if G.arc[k][j]<shortedge[j].lowcost
{
shortedge[j].lowcost=G.arc[k][j];
shortedge[j].adjvex=k;
}
}
}
2.最小生成树算法 Kruskal
void Kruskal(EdgeGraph)
{
for(i=0;i<G.vertexnum;i++)
parent[i]=-1;
for(num=0,i=0;i<G.edgenum;i++)
{
vex1=Findroot(parent,G.edge[i].from);
vex2=Findroot(parent,G.edge[i].to);
if(vex1!=vex2)
{
cout<<"("<<G.edge[i].from<<G.edge[i].to<<")"<<endl;
parent[ver2]=ver1;
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最短路径
1.最短路径:两顶点之间经历的边数最少的路径(路径上的第一个顶点称为源点,最后一个顶点称为终点)。
2.Dijkstra 算法:
void Dijkstra(MGraph G,int v)
{
for(i=0;i<G.vertexnum;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<G.vertexnum)
{
for(k=0,i=0;i<G.vertexnum;i++)
if((dist[i]!=0)&&(dist[i]<dist[k])) k=i;
cout<<dist[k]<<path[k];
s[num++]=k;
for(i=0;i<G.vertexum;i++)
if(dist[i]>dist[k]+G.arc[k][i])
{
dist[i]=dist[k]+G.arc[k][j];
path[i]=path[k]+G.vertex[i];
}
dist[k]=0;
}
}