其他分享
首页 > 其他分享> > 数据结构---图的遍历和最小生成树

数据结构---图的遍历和最小生成树

作者:互联网

目录

图的遍历

由于图的任一顶点都可能与其他的顶点相邻接,所以访问某个顶点后可能沿着某条路径搜索又回到改顶点,为了避免同一个顶点被访问多次,在遍历过程中记下每个已访问过的顶点,这里就用到了辅助数组visited[n] , 其初始值置为"false"或者0, 一旦访问了顶点 Vi, 便置visited[i]为"true"或者1。

深度优先搜索

类似于树的先序遍历,先访问顶点

算法实现
bool visited [MVNum] ;//访问标志数组, 其初值为 "false"
void DFS(Graph G,int v) 
{
cout<<v;visited[v]=true; //访问第v个顶点, 并置访问标志数组相应分量值为true
for(w=FirstAdjVex(G,v);w>=O;w=NextAdjVex(G,v,w)) 
//依次检查v的所有邻接点w,FirstAdjVex(G, v)表示v的第一个邻接点
//NextAdjVex(G,v,w)表示v相对于w的下一个邻接点w匀表示存在邻接点
if(!visited[w]) DFS(G,w);//对v的尚未访问的邻接顶点w递归调用DFS
}
void DFSTraverse(Graph G) 
{//对非连通图G做深度优先遍历
for(v=0;v<G.vexnum;++v) visited[v]=false;//将标志数组初始化
for(v=0;v<G.vexnum;++v)//
if(!visited[v]) DFS(G,v);
}
邻接矩阵表示图的深度优先搜索遍历
void DFS_AM(AMGraph G,int v) 
{//图G为邻接矩阵类型,从第v个顶点出发深度优先搜索遍历图G
cout<<v;visited[v]=true; //访问第v个顶点,并置访问标志数组相应分址值为true
for(w=0;w<G.vexnum;w++) //依次检查邻接矩阵v所在的行
if((G.arcs[v][w]!=0)&&(!visited[w])) DFS(G,w); //G.arcs[v][w]!=0表示w是v的邻接点,如果w未访问,则递归调用DFS
 }

这里先访问顶点(共有n个)对顶点所在邻接矩阵的行再依次检查(共有n列)所以时间复杂度为 O(n*n)

邻接表表示图的深度优先搜索遍历
void DFS_AL (ALGraph G,int v) 
{//图G为邻接表类型, 从第v个顶点出发深度优先搜索遍历图G
cout<<v;visited[v]=true; //访问第v个顶点,并置访问标志数组相应分量值为true
p=G.vertices[v] .firstarc;//p指向v的边链表的第一个边结点
while(p!=NULL) //边结点非空
{
w=p->adjvex;//表示w是v的邻接点
if(!visited[w]) DFS(G,w); //如果w访问, 则递归调用DFS
p=p->nextarc; //p指向下一个边结点
}
}

这里先访问顶点(共n个)对顶点所在的边链表进行检查,全都访问完后对下一个边结点进行访问(除开始结点外,由该顶点出发的边链表的结点数和边数相同,所以e条边,也就访问了e次)

时间复杂度为O(n+e)

广度优先搜索

类似于树的按层次遍历,按照从上到下从左到右访问每一层的每一个结点

在遍历时,先访问的顶点其邻接点也先于后访问的顶点的邻接点,这里引入有先进先出特点的队列来保存已经访问过的顶点

算法实现
void BFS{Graph G,int v) 
{//按广度优先非递归遍历连通图G
cout<<v;visited[v]=true;//访问第v个顶点,并置访问标志数组相应分址值为true
InitQueue(Q); //辅助队列Q初始化, 置空
EnQueue(Q,v); //v进队
while (!QueueEmpty (Q))//当队列非空
{
DeQueue (Q, u);//队头元素出队并置为u
for(w=FirstAdjVex(G,u);w>=0;w=NextAdjVex(G,u,w))
//依次检查u的所有邻接点w, FirstAdjVex(G,u)表示u的第一个邻接点
//NextAdjVex(G,u,w)表示u相对于w的下一个邻接点,w>=0表示存在邻接点
if (!visited [w])//w为u的尚未访问的邻接顶点
{
cout<<w; visited[w]=true//访问 w, 并置访问标志数组相应分量值为true
EnQueue (Q, w) ;//w进队
}
}
}
总结

访问起始顶点---起始顶点进队---队头元素出队---依次访问队头元素所有未被访问过的邻接点---访问一个邻接点就入队一个直到邻接点访问完毕也就入队完毕---再将队头元素出队重复上两步

时间复杂度和深度优先搜索遍历相同

图的应用---最小生产树

生成树的特点

最小生成树是权值之和最小的生成树

MST性质

假设 N= (V, E)是一个连通网,U是顶点集 V的一个非空子集。若(u, v)是一条具有最小权值(代价)的边,其中u属于U, v属于V-U, 则必存在一棵包含边(u, v)的最小生成树。

解释

普里姆算法

假设 N=(V,E)是连通网, TE 是 N上最小生成树中边的集合

  1. U = {u0}(u0属于V), TE = {}
  2. 在所有 u属于U, v属于V-U的边(u,v)属于E 中找一条权值最小的边(u0,v0)并入集TE, 同时 v0并入U
  3. 重复2., 直至 U = V为止

在图中任取一个顶点u作为起始点,再在除u外的其余点中找到一条和邻接的权值最小的边,将边的另一点和u并在一起,重复寻找权值最小的的边,直到图中所有顶点都和u并上,这里是不断将点并入U,也叫加点法

image-20220209171406733

注意:可能存在多条权值相同的边可选,这也说明最小生成树可能不唯一

算法实现

为了记录以记录从 U到 V-U 具有最小权值的边,需附设一个辅助数组 closedge,在辅助数组中存在一个相应分量 closedge[i-1],它包括两个域: lowcost 和 adjvex, 其中 Iowcost 存储最小边上的权值, adjvex 存储最小边在 U 中的那个顶点

struct //辅助数组的定义
{
VerTexType adjvex;//最小边在U中的那个顶点
ArcType lowcost; //最小边上的权值
}closedge [MVNum];
void MiniSpanTree_Prim(AMGraph G,VerTexType u) 
{//无向网G以邻接矩阵形式存储, 从顶点u出发构造G的最小生成树T, 输出T的各条边
k=LocateVex(G,u); //k为顶点u的下标
for(j=0;j<G.vexnum;++j) //对V-U的每一个顶点 Vj, 初始化closedge[j]
if(j!=k) closedge[j]={u,G.arcs[k][j]}; //{adjvex, lowcost} 
closedge[k].lowcost=0; //初始, U={u}
for(i=1;i<G.vexnum;++i) 
{//选择其余 n-1 个顶点,生成 n-1 条边(n=G.vexnum)
k=Min(closedge); 
//求出 T 的下一个结点:第 K 个顶点, closedge[k]中存有当前最小边
u0=closedge[k] .adjvex; // u0为最小边的一个顶点,u0属于U
v0=G.vexs[k]; // v0为最小边的另一个顶点, v0属于V-U
cout<<u0<<v0;//输出当前的最小边(uO, vO)
closedge[k] .lowcost=0;//第k个顶点并入U集 ,权值为零表示并入U
for(j=0;j<G.vexnum;++j) 
if(G.arcs[k] [j]<closedge[j] .lowcost) //新顶点并入U后重新选择最小边
closedge [j]={G.vexs [k] ,G.arcs [k][j]};
}
}

分析:初始化时间复杂度的O(n)第二个for中内有两个内循环其一是在 closedge[v] .lowcost 中求最小值, 其频度为 n-1; 其二是重新选择具有最小权值的边, 其频度为 n,所以时间复杂度为O(n*n)

可以看出只和顶点有关,所以适合边多的稠密网

克鲁斯卡尔算法

假设连通网N=(V,E), 将N中的边按权值从小到大的顺序排列

  1. 初始状态为只有n个顶点而无边的非连通图 T= (V, {}), 图中每个顶点自成一个连通分量
  2. 在E中选择权值最小的边, 若该边依附的顶点落在T中不同的连通分鼠上 (即不形成回路),则将此边加入到T中,否则舍去此边而选择下一条权值最小的边
  3. 重复2.,直至T中所有顶点都在同一连通分量上为止

将图中的所有顶点看成不同的连通分量,将所有边按照权值从小到大排序,依次选择最小的边加入生成树中并保证不形成回路,这里是不断将边加入生成树中,所以也叫加边法

image-20220209180112589

实现该算法也是要要引入以下辅助的数据结构1.结构体数组Edge: 存储边的信息,包括边的两个顶点信息和权值2.Vexset[i]: 标识各个顶点所属的连通分量。对每个顶点V;EV, 在辅助数组中存在一个相应元素Vexset[i]表示该顶点所在的连通分量。初始时Vexset[i] = i, 表示各顶点自成一个连通分量

算法实现
struct //辅助数组Edges 的定义
{
VerTexType Head; //边的始点
VerTexType Tail; //边的终点
ArcType lowcost; //边上的权值
} Edge [arcnum] ;
//辅助数组 Vexset 的定义
int Vexset[MVNum];
void MiniSpanTree_ Kruskal(AMGraph G) 
{//无向网G以邻接矩阵形式存储,构造G的最小生成树T, 输出T的各条边
Sort (Edge); //将数组 Edge 中的元素按权值从小到大排序
for(i=0;i<G.vexnum;++i) //辅助数组,表示各顶点自成一个连通分量
Vexset[i]=i; 
for(i=0;i<G.arcnum;++i) //依次查看数组 Edge 中的边,这里已经按照从小到大排列了
{
vl=LocateVex(G,Edge[i] .Head); //v1为边的始点 Head 的下标
v2=LocateVex(G,Edge[i] .Tail); //v2为边的终点 Tail的下标
vsl=Vexset[vl];//获取边 Edge[i]的始点所在的连通分量 vsl
vs2=Vexset[v2]; //获取边 Edge[i]的终点所在的连通分量 vs2
if(vsl!=vs2) //边的两个顶点分属不同的连通分量
{
cout<<Edge[i] .Head<<Edge[i] .Tail;//输出此边
for(j=O;j<G.vexnurn;++j) //合并 VS1 和 VS2 两个分益, 即两个集合统一编号
if(Vexset[ j] ==vs2) Vexset [ j] =vsl; //集合编号为 vs2 的都改为 vsl
}
}
}

分析:克鲁斯卡尔算法的时间复杂度为 O(elog2e),与网中的边数有关,与普里姆算法相比,克鲁斯卡尔算法更适合于求稀疏网的最小生成树

标签:遍历,最小,---,访问,邻接,权值,顶点,数据结构
来源: https://www.cnblogs.com/Aurora-hu/p/15876248.html