数据结构与算法——图(mi6236)
二、图的遍历
给定一个无向连通图,从图的任意指定顶点出发,依照某种规则去访问图中所有顶点,且每个顶点仅被访问一次,这一访问过程叫作图的遍历(graph traversal)数的遍历是利用树求解各类问题的基础,是树的一个最基本的运算。同样,图的遍历算法是求解图的连通性、拓扑排序和求关键路径等算法的基础。图的遍历比树的遍历复杂,由于图的任一点都有可能和其余个顶点相邻接,故在访问了某个顶点之后,沿着某条边再次访问同一顶点,出现了重复访问某一顶点的问题。为了避免同一顶点被多次访问,在图的遍历过程中,必须记住每个已被访问过的顶点,为此可设置一个表示顶点是否已被访问过的辅助数组visited[1…n],它的初值为0或假,一旦访问了Vi,便置visited[i]为1或真。图的遍历按照深度优先和广度优先规则去实施,通常有深度优先搜索法(depth_firstsearch)和广度优先搜索法( breadth_frist search)两种.
1、深度优先搜索
深度优先搜索是在访问某一顶点Vo之后,由Vo出发,选取一个与Vo邻接且没有被访问的任一点W1进行访问。再由W1出发,选取一个与W1邻接且没有被访问的任一点W2进行访问,再由W2出发,……,依次类推,重复上述过程,直至某顶点Wi已无未被访问过的邻接点时,则退回一步找到一个顶点Wi-1的其他尚未被访问的邻接点。如果存在尚未被访问过的邻接点,则访问此邻接点,然后再从该顶点出发,按深度优先进行访问;如果退回一步后还没有尚未被访问的邻接点,则再回退一步再进行搜索,重复上述过程,一直到所有顶点都被访问过为止。所谓“访问”究竟是进行什么运算,视不同应用而定且访问的顺序不是唯一的。
#include <stdio.h>
#define MAX 5 /*定义最大顶点个数5*/
typedef struct EdgeNode{
intadjnum; /*邻接点域,存储位置序号,*/
intinfo;
structEdgeNode *nextarc; /*链域,指向下一条边(弧)的指针*/
}EdgeNode;
typedef struct VNode{ /*表头结点的类型VNode*/
intvexdata; /*表头结点的数据域*/
structEdageNode *firstarc; /*表头结点的指针域*/
}VNode;
typedef VNode AdGraph[MAX+1]; /*定义图的类型AdGraph,为一个一维数组*/
typedef struct{
AdGraphadgraph; /*邻接表*/
inte,h; /*边和顶点的数目*/
}Adlist;
采用邻接表结构图的深度优先遍历的递归算法:
intvisited[MAX+1]; /*结点是否访问过标记*/
voidDFSTraverse(Adlist G)
{
for(v=0;v<G.n;v++)
{
visited[v]=0; /*初始化所有均未被访问过*/
}
for(v=0;v<G.n;v++)
{
if(!visited[v])
{
DFS(G,V); /*以某一顶点为起始点确保所有结点均被访问到*/
}
}
}
voidDFS(Adlist G,int v) /*从顶点v开始递归,深度优先便利图G*/
{
Vistted[v]=1;
printf(“%d”,v); /*打印顶点信息*/
for(w=First_Adjv(G,v);w;w=Next_Adjv(G,v,w))
{
if(!visited[w])
{
DFS(G,w); /*对v的未访问的邻接顶点w递归调用DFS*/
}
}
}
图的深度优先遍历的非递归遍历算法:
intvisited[MAX+1]; /*标记结点是否访问过*/
voidNonRecDFSTraverse(Adlist G)
{
int stack1[2*MAX+1],v,top1=0; /*申请栈1*/
int stack2[2*MAX+1],top2=0; /*申请栈2*/
for(v=0;v<G.n;v++)
{
Visited[v]=0; /*初始化,所有结点均未被访问过*/
}
for(v=0;v<G.n;v++) /*确保所有结点均被访问到*/
{
if(!visited[v])
{
stack1[++top1]=v;
while(top1>0)
{
v=stack1[top1--];
if(!visited[v])
{
printf(“%d”,v); /*打印顶点的信息*/
}
for(w=First_Adjv(G,v);w;w=Next_Adjv(G,v,w))
{
if(!visited[w])
{
stack2[++top2]=w;
}
}
while(top2>0)
{
stack1[++top1]=stack2[top2--];
}/*while*/
}/*while*/
}/*if*/
}/*for*/
}/*NonRecDFSTraverse*/
2、广度优先搜索
无向图的广度优先搜索过程:从图G中某一顶点Vo出发,在访问了Vo之后,首先依次访问与Vo邻接的全部顶点W1,W2,….,Wd。然后再依次访问W1,W2,….,Wd邻接的尚未被访问过的全部顶点,再从这些被访问过的顶点出发,逐一访问与它们邻接的尚未被访问过的全部顶点。依次类推,直到所有的顶点全被访问完为止,这一搜索过程称为广度优先搜索。在上述搜索过程中,若W1在W2之前访问过,则W1的邻接点也将在W2的邻接点之前访问。因此,对于广度优先算法,无需记录所有走过的路径,但却需记录与一个顶点相邻接的全部顶点,由于访问过这些顶点之后,还将按照先被访问的顶点就先访问它的邻接点的方式进行广度优先搜索。为此我们用一个先进先出的队列来记录这些顶点。广度优先搜索的序列也不是唯一的。
广度优先算法:
3、图的生成树和连通分量
在对无向图进行遍历时,对于连通图,仅需依次调用搜索过程(dfs或bfs)。即从图的任意一点出发,便可遍历到图中每个顶点,按照图的生成树的定义,图中的全部顶点和搜索过程所经过的边集,即构成了该两通图的生成树。求图的连通分量实际是图的遍历的一种应用。当无向图是连通图时,从图中某点Vo出发遍历图,不能访问到图的所有顶点,而只能访问到包含Vo所在的最大连通子图(连通分量)中的所有顶点。若从非连通图中每个连通分量中的一个顶点出发遍历图,则可求出无向图的所有连通分量。
为了求得非连通图的所有的连通分量,只需调用dfs或bfs,并对图中每个顶点进行检测。若某顶点已被访问,则该顶点落在图中已求得的连通分量上;若某顶点未被访问,则从该顶点出发遍历图,便可求得图的另一个两通分量其算法描述如下:
Void component(graph g) /*g为用邻接表或邻接矩阵表示的有n个顶点的无向图,求该图的所有连通分量*/
{
for(i=1,i<n,i++)
Visited[i]=0;
for(i=1;i<n;i++)
{
if (Vistted[i]=0)
{
dfs(g,Vi);
printf(“输出dfs过程中访问到的顶点及所有依附于顶点的边”);
}
}
}
有向图的连通性及其将连通分量,均可用dfs和bfs遍历方式求得。
数据结构与算法——图
三、最小生成树
一棵生成树的代价即树上各边的代价之和,如果该生成树的代价最小,则称该书为最小生成树(也称最小代价生成树)。
求图的最小生成树有着广泛的应用价值,例如,如何在n个城市之间建立通信网络图,并且连通n个城市只需要n-1条线路,而且通信代价最小。
构造最小生成树的算法主要有普里姆(prim)算法和克鲁斯卡尔(kruskal)算法两种。
(1)prim算法
普里姆于1975年提出了构造最小生成树的一种方法,该算法的要点是:按照将顶点逐一连通的步骤,把已连通的顶点加入到集合V中,这个集合V开始时为空集。首先任选一顶点加入V,然后,从依附与该顶点的边中选取权值最小的边作为生成树的一条边,并将依附于该边且在集合V外的另一个顶点加入到V。表示这两个顶点已通过权值最小的边两通了。以后,每次从一个顶点在集合V中而另一个顶点在V外的各条边中选取权值最小的一条,作为生成树的一条边,并把依附与该边且在集合V外的顶点并入V,依次类推,直到全部顶点都已连通(全部顶点加入到V),即构成所要求的最小生成树。
若N=(V, E)是连通网,V={V1,V2,…,Vn}是网的顶点集合,E是N上最小生成树中边的集合。引入顶点集合U和边的集合TE,U的初始状态为{V1},它存放的是当前所得到(还未完成的)最小代价生成树上的所有顶点,TE的初始状态为φ。在prim算法的每一步,都从所有的边{(u,v)|v∈V-U,u∈U}中找出所有代价最小的边(u,v),同时将v并入U,(u,v)并入集合TE,直到U=V为止。此时TE中必有n-1条边,则T=(V,TE)为N的最小生成树。
Prim算法的时间复杂度为O(n*n),它与网上的边的数目无关,因此它适合稠密图。
算法描述:
void minispantree-PRIM(adjmatrix gn, vexptr u0)
{
For(int v=1;v<vexnumber;v++)
{
IF(V!=u0)
{
While(closedge[v])
{
Vex=u0;
Lowcost=gn[u0,v];/*辅助数组初始化*/
}
}
Closedge[u0].lowcost=0;
For (i=1;i<vexnumber-1;i++)
{
K=minimum(closedge); /*求代价最小的顶点k∈V-U}
printf(“%,%”,closedge[k].vex,k);/*输出生成树的边*/
closedge[k].lowcost=o;/*顶点k并入u集*/
for(i=1;i<vexnum;i++)
{
If(gn[k,v]<closedge[v].lowcost)
{
Closedge[v].lowcost=gn[k,v];
Closedge[v].vex=k;
}
}