图的遍历
概念:
从图中某一顶点出发访遍图中其余顶点,且使每一个顶点仅被访问一次(图的遍历算法是求解图的连通性问题、拓扑排序和求关键路径等算法的基础。)
深度优先搜索(DFS)
- 基本思想:
- (1)在访问图中某一起始顶点 v 后,由 v 出发,访问它的任一邻接顶点 w1;
- (2)再从 w1 出发,访问与 w1邻接但还未被访问过的顶点 w2;
- (3)然后再从 w2 出发,进行类似的访问,…
- (4)如此进行下去,直至到达所有的邻接顶点都被访问过为止。
- (5)接着,退回一步,退到前一次刚访问过的顶点,看是否还有其它没有被访问的邻接顶点。如果有,则访问此顶点,之后再从此顶点出发,进行与前述类似的访问;如果没有,就再退回一步进行搜索。重复上述过程,直到连通图中所有顶点都被访问过为止。
- 算法实现(明显是要用到(栈)递归):
Void DFSTraverse( Graph G, Status (*Visit) (int v)) { // 对图G做深度优先遍历 for (v=0; v<G.vexnum; ++v) visited[v] = FALSE; // 访问标志数组初始化 for (v=0; v<G.vexnum; ++v) //这个循环是防止图是非连通的 if (!visited[v]) DFS(G,v); // 对尚未访问的顶点调用DFS } void DFS (Graph G,int v)//从第v个顶点出发递归地深度优先遍历图G { visited[v]=TRUE ; Visit(v); //访问第v个顶点 for(w=FirstAdjVex(G,v)/*从图的第v个结点开始*/; w>=0; w=NextAdjVex(G,v,w)/*v结点开始的w结点的下一个结点*/) if (!visited[w]) DFS(G,w); //对v的尚未访问的邻接顶点w递归调用DFS }
- DFS时间复杂度分析:
- (1)如果用邻接矩阵来表示图,遍历图中每一个顶点都要从头扫描该顶点所在行,因此遍历全部顶点所需的时间为O(n2)。
- (2)如果用邻接表来表示图,虽然有 2e 个表结点,但只需扫描 e 个结点即可完成遍历,加上访问 n 个头结点的时间,因此遍历图的时间复杂度为O(n+e)。
广度优先搜索(BFS)
- 基本思想:
- (1)从图中某个顶点V0出发,并在访问此顶点后依次访问V0的所有未被访问过的邻接点,之后按这些顶点被访问的先后次序依次访问它们的邻接点,直至图中所有和V0 有 路径相通的顶点都被访问到;
- (2)若此时图中尚有顶点未被访问(非连通图),则另选图中一个未曾被访问的顶点作起始点;
- (3)重复上述过程,直至图中所有顶点都被访问到为止。
- 算法实现(明显是要用到队列)
void BFSTraverse(Graph G, Status (*Visit)(int v)){ //使用辅助队列Q和访问标志数组visited[v] for (v=0; v<G.vexnum; ++v) visited[v] = FALSE; InitQueue(Q); // 置空的辅助队列Q for ( v=0; v<G.vexnum; ++v )//外层for循环是防止有非连通图的情况 if ( !visited[v]) { // v尚未访问 visited[v] = TRUE; Visit(v); EnQueue(Q, v); // v入队列 while (!QueueEmpty(Q)) { DeQueue(Q, u); // 队头元素出队并置为u for (w=FirstAdjVex(G,u);w>=0;w=NextAdjVex(G,u,w)) if ( ! visited[w]){ //w为u的尚未访问的邻接顶点 visited[w] = TRUE; Visit(w); EnQueue(Q, w); } //if } //while }if } // BFSTraverse
- BFS时间复杂度分析:
- (1) 如果使用邻接表来表示图,则BFS循环的总时间代价为 d0 + d1 + … + dn-1 = 2e=O(e),其中的 di 是顶点 i 的度
- (2)如果使用邻接矩阵,则BFS对于每一个被访问到的顶点,都要循环检测矩阵中的整整一行( n 个元素),总的时间代价为O(n2)。
图的连通性问题:
相关术语:
- 连通分量的顶点集:即从该连通分量的某一顶点出发进行搜索所得到的顶点访问序列;
- 生成树:某连通分量的极小连通子图(深度优先搜索生成树和广度优先搜索生成树);
- 生成森林:非连通图的各个连通分量的极小连通子图构成的集合。
最小生成树:
Kruskal算法:
- 先构造一个只含n个顶点的森林,然后依权值从小到大从连通网中选择边加入到森林中去,并使森林中不产生回路,直至森林变成一棵树为止(详细代码见尾文)。
Prim算法(还是看上图理解):
- 假设原来所有节点集合为V,生成的最小生成树的结点集合为U,则首先把起始点V1加入到U中,然后看比较V1的所有相邻边,选择一条最小的V3结点加入到集合U中,
然后看剩下的v-U结点与U中结点的距离,同样选择最小的.........一直进行下去直到边数=n-1即可。
算法设计思路:
- 增设一辅助数组Closedge[ n ],每个数组分量都有两个域:
要求:求最小的Colsedge[ i ].lowcost
两种算法比较:
- 普里姆算法的时间复杂度为 O(n2),与网中的边数无关,适于稠密图;
- 克鲁斯卡尔算法需对 e 条边按权值进行排序,其时间复杂度为 O(eloge),e为网中的边数,适于稀疏图。
完整最小生成树两种算法实现:
#include<stdio.h>
#include<stdlib.h>
#include<iostream>
using namespace std;
#define MAX_VERTEX_NUM 20
#define OK 1
#define ERROR 0
#define MAX 1000
typedef struct Arcell
{
double adj;//顶点类型
}Arcell,AdjMatrix[MAX_VERTEX_NUM][MAX_VERTEX_NUM];
typedef struct
{
char vexs[MAX_VERTEX_NUM]; //节点数组,
AdjMatrix arcs; //邻接矩阵
int vexnum,arcnum; //图的当前节点数和弧数
}MGraph;
typedef struct Pnode //用于普利姆算法
{
char adjvex; //节点
double lowcost; //权值
}Pnode,Closedge[MAX_VERTEX_NUM]; //记录顶点集U到V-U的代价最小的边的辅助数组定义
typedef struct Knode //用于克鲁斯卡尔算法中存储一条边及其对应的2个节点
{
char ch1; //节点1
char ch2; //节点2
double value;//权值
}Knode,Dgevalue[MAX_VERTEX_NUM];
//-----------------------------------------------------------------------------------
int CreateUDG(MGraph & G,Dgevalue & dgevalue);
int LocateVex(MGraph G,char ch);
int Minimum(MGraph G,Closedge closedge);
void MiniSpanTree_PRIM(MGraph G,char u);
void Sortdge(Dgevalue & dgevalue,MGraph G);
//-----------------------------------------------------------------------------------
int CreateUDG(MGraph & G,Dgevalue & dgevalue) //构造无向加权图的邻接矩阵
{
int i,j,k;
cout<<"请输入图中节点个数和边/弧的条数:";
cin>>G.vexnum>>G.arcnum;
cout<<"请输入节点:";
for(i=0;i<G.vexnum;++i)
cin>>G.vexs[i];
for(i=0;i<G.vexnum;++i)//初始化数组
{
for(j=0;j<G.vexnum;++j)
{
G.arcs[i][j].adj=MAX;
}
}
cout<<"请输入一条边依附的定点及边的权值:"<<endl;
for(k=0;k<G.arcnum;++k)
{
cin >> dgevalue[k].ch1 >> dgevalue[k].ch2 >> dgevalue[k].value;
i = LocateVex(G,dgevalue[k].ch1);
j = LocateVex(G,dgevalue[k].ch2);
G.arcs[i][j].adj = dgevalue[k].value;
G.arcs[j][i].adj = G.arcs[i][j].adj;
}
return OK;
}
int LocateVex(MGraph G,char ch) //确定节点ch在图G.vexs中的位置
{
int a ;
for(int i=0; i<G.vexnum; i++)
{
if(G.vexs[i] == ch)
a=i;
}
return a;
}
void MiniSpanTree_PRIM(MGraph G,char u)//普利姆算法求最小生成树
{
int i,j,k;
Closedge closedge;
k = LocateVex(G,u);
for(j=0; j<G.vexnum; j++)
{
if(j != k)
{
closedge[j].adjvex = u;
closedge[j].lowcost = G.arcs[k][j].adj;
}
}
closedge[k].lowcost = 0;
for(i=1; i<G.vexnum; i++)
{
k = Minimum(G,closedge);
cout<<"("<<closedge[k].adjvex<<","<<G.vexs[k]<<","<<closedge[k].lowcost<<")"<<endl;
closedge[k].lowcost = 0;
for(j=0; j<G.vexnum; ++j)//新顶点并入U后重新选择最小边
{
if(G.arcs[k][j].adj < closedge[j].lowcost)
{
closedge[j].adjvex = G.vexs[k];
closedge[j].lowcost= G.arcs[k][j].adj;
}
}
}
}
int Minimum(MGraph G,Closedge closedge) //求closedge中权值最小的边,并返回其顶点在vexs中的位置
{
int i,j;
double k = 1000;
for(i=0; i<G.vexnum; i++)
{
if(closedge[i].lowcost != 0 && closedge[i].lowcost < k)
{
k = closedge[i].lowcost;
j = i;
}
}
return j;
}
void MiniSpanTree_KRSL(MGraph G,Dgevalue & dgevalue)//克鲁斯卡尔算法求最小生成树
{
int p1,p2,i,j;
int bj[MAX_VERTEX_NUM]; //标记数组
for(i=0; i<G.vexnum; i++) //标记数组初始化
bj[i]=i;
Sortdge(dgevalue,G);//将所有权值按从小到大排序
for(i=0; i<G.arcnum; i++)
{
p1 = bj[LocateVex(G,dgevalue[i].ch1)];
p2 = bj[LocateVex(G,dgevalue[i].ch2)];
if(p1!=p2)
{
cout<<"("<<dgevalue[i].ch1<<","<<dgevalue[i].ch2<<","
<<dgevalue[i].value<<")"<<endl;
for(j=0; j<G.vexnum; j++)
{
if(bj[j] == p2)
bj[j] = p1;
}
}
}
}
void Sortdge(Dgevalue & dgevalue,MGraph G)//对dgevalue中各元素按权值按从小到大排序
{
int i,j;
double temp;
char ch1,ch2;
for(i=0; i<G.arcnum; i++)
{
for(j=i; j<G.arcnum; j++)
{
if(dgevalue[i].value > dgevalue[j].value)
{
temp = dgevalue[i].value;
dgevalue[i].value = dgevalue[j].value;
dgevalue[j].value = temp;
ch1 = dgevalue[i].ch1;
dgevalue[i].ch1 = dgevalue[j].ch1;
dgevalue[j].ch1 = ch1;
ch2 = dgevalue[i].ch2;
dgevalue[i].ch2 = dgevalue[j].ch2;
dgevalue[j].ch2 = ch2;
}
}
}
}
void main()
{
int i,j;
MGraph G;
char u;
Dgevalue dgevalue;
CreateUDG(G,dgevalue);
cout<<"图的邻接矩阵为:"<<endl;
for(i=0; i<G.vexnum; i++)
{
for(j=0; j<G.vexnum; j++)
cout << G.arcs[i][j].adj<<" ";
cout<<endl;
}
cout<<"=============普利姆算法===============\n";
cout<<"请输入起始点:";
cin>>u;
cout<<"构成最小代价生成树的边集为:\n";
MiniSpanTree_PRIM(G,u);
cout<<"============克鲁斯科尔算法=============\n";
cout<<"构成最小代价生成树的边集为:\n";
MiniSpanTree_KRSL(G,dgevalue);
}
运行结果: