# 原创 : 第七章 图 # 第七章 图 # 一、图的存储结构 > ## 1.邻接矩阵 > ## 2.邻接表 > ## 3.邻接多重表(有点看不懂…) > # 二、图的遍历 ## 1.深度优先搜索遍历 > ``` //以邻接表存储结构为例 int visit[maxSize]; //visit[]作为顶点的访问标记的全局数组,初始化为0 void DFS(AGraph *G,int v) { ArcNode *p; visit[v]=1; //置已访问标记 Visit(v); p=G->adjlist[v].firstarc; //p指向顶点第一条边 while(p!=NULL) { if(visit[p->adjvex]==0) //若顶点未访问,则递归访问它 { DFS(G,p->adjvex); //(1) p=p->nextarc; //p指向顶点v的下一条边的终点 } } } //与二叉树对比 void preorder(BTNode *p) { if(!p=NULL) { visit(p); preorder(p->left); //(2) preorder(p->right); //(3) } } ``` > ## 2.广度优先搜索遍历 > ``` //以邻接表为例 void BFS(AGraph *G, int v, int visit[maxSize]) //visit[]数组全部初始化为0 { ArcNode *p; int que[maxSize],front=rear=0; int j; Visit(v); visit[v]=1; rear=(rear+1)%maxSize; //当前顶点v入队 que[rear]=v; while(front!=rear) //队空遍历完成 { front = (front+1)%maxSize; //顶点出队 j=que[front]; p=G->adjlist[j]firstarc; //p指向出队顶点的第一条边 while(p!=NULL) //将p的所有邻接顶点中未被访问的入队 { if(visit[p->adjvex]==0) { Visit(p->adjvex); visit[p->adjvex]=1; rear=(rear+1)%maxSize; //该顶点进队 que[rear]=p->adjvex; } p=p->nextarc; //p指向j的下一条边 } } } ``` ``` //上述是针对连通图的,对于非连通图,只需用一个循环检测图中每个顶点即可 //1.深度优先搜索遍历 void dfs(AGraph *g) { int i; for(i=0;i<g->n;++i) //n是顶点数 { if(visit[i]==0)DFS(g,i); } } //2.广度优先搜索遍历 void bfs(AGraph *g) { int i; for(i=0;i<g->n;++i) { if(visit[i]==0)BFS(g,i,visit); } } ``` ## 3.例程 ``` //1.求不带权无向连通图G距离顶点v最远的一个顶点 //采用广度优先搜索遍历,返回最后一个结点即可 int BFS(AGraph *G,int v) { ArcNode *p; int que[maxSize],front=rear=0; int visit[maxSize]; int i,j; for(i=0;i<G->n;++i)visit[i]=0; //初始化visit rear=(rear+1)%maxSize; //顶点v入队 que[rear]=v; visit[v]=1; while(front!=rear) { front=(front+1)%maxSize; //出队 j=que[front]; p=G->adjlist[j].firstarc; //p指向出队结点p的第一条边 while(p!=NULL) { front=(front+1)%maxSize; j=que[front]; p=G->adjlist[j].firstarc; while(p!=NULL) { if(visit[p->adjvex]==0) { visit[p->adjvex]=1; rear=(rear+1)%maxSzie; que[rear]=p->adjvex; } p=p->nextarc; } } } return j; //队空时保存了遍历过程中的最后一个顶点 } //2.判断无向图G是否是一棵树 //满足树的条件是有n-1条边的连通图,n为图中顶点的个数 void DFS2(AGraph *G,int v,int &vn,int &en) { ArcNode *p; visit[v]=1; ++vn; p=G->adjvex[v].firstarc; while(p!=NULL) { ++en; if(visit[p->adjvex]==0)DFS2(G,p->adjvex); p=p->nextarc; } } int GisTree(AGraph *G) { int vn=0,en=0,1; for(i=0;i<G->n;++i)visit[i]=0; DFS2(G,1,vn,en); if(vn==G->n&&(G->n-1)==en/2)return 1; //遍历过程中访问过的顶点数和图中的顶点数相等,且边数等于顶点数减1,则是树 //注意en,每个顶点都算了一次,最后总和相当于边数的两倍,所以要除以2 else return 0; } //3.图采用邻接表存储,判断顶点i和顶点j(i!=j)之间是否有路径 //分析:从顶点i开始进行一次深度搜索遍历,遍历过程中遇到j说明i,j有路径 int DFSTrave(AGraph *G, int i, int j) { int k; for(k=0;k<G->n;++n)visit[k]=0; //初始化visit[]数组 DFS(G,i); if(visit[j]==1)return 1; else return 0; } ``` # 三、最小(代价)生成树 > ## 1.普里姆算法 > ``` //形参写成MGraph会使得参数传入因复制了一个较大的变量而变得低效 //用引用型避免函数参数复制 void Prim(MGraph g,int v0,int &sum) { int lowcost[maxSize],vset[maxSize],v; int i,j,min; v=v0; for(i=0;i<g.n;++i) { lowcost[i]=g.edges[v0][i];//顶点v0的边的权值 vset[i]=0; } vset[v0]=1; //将v0并入树中 sum=0; //sum清零用来累计树的权值 for(i=0;i<g.n-1;++i) { min=INF; //INF是一个已经定义的比图中所有边权值都大的常量 for(j=0;j<g.n;++j) { if(vset[j]==0&&lowcost[j]<min) { min=lowcost[j]; k=j; } } vset[k]=1; v=k; sum+=min;//sum即是最小生成树的权值 //循环以刚并入的顶点v为媒介更新候选边 for(j=0;j<g.n;++j) { if(vset[j]==0&&g.edges[v][j]<lowcost[j]) lowcost[j]=g.edges[v][j]; } } } //时间复杂度:O(n^2),普里姆算法适用于稠密图 ``` ## 2.克鲁斯卡尔算法 > ``` //假设road[]数组中存放了图中各边及其所连接的两个顶点的信息,且排序函数已经存在 typedef struct { int a,b; //a,b为一条边的两个顶点 int w; //边的权值 }Road; Road road[maxSize]; int v[maxSize]; //并查集数组 int getroot(int a) { while(a!=v[a])a=v[a]; return a; } void Kruskal(MGraph g,int &sum,Road road[]) { int i; int N,E,a,b; N=g.n; //顶点数 E=g.e; //边数 sum=0; for(i=0;i<N;++i)v[i]=i; sort(road,E); //对并查集数组进行从小到大权值排序 for(i=0;i<E;++i) { a=getRoot(road[i].a); b=getRoot(road[i].b); //不构成回路,则可以并入 if(a!=b) { v[a]=b; sum+=road[i].w; //此处生成树的权值可以改为其他写法 } } } //克鲁斯卡尔算法时间复杂度主要花费在选取的排序算法上 //排序算法的规模由图的边数e决定 //克鲁斯卡尔算法适用于稀疏矩阵 ``` # 四、最短路径 ## 1.迪杰斯特拉算法 > ``` void printfPath(int path[],int a) { int stack[maxSize],top=-1; //这个栈以由叶子结点到根结点的顺序将其入栈 while(path[a]!=-1) { stack[++top]=a;//先将叶子顶点入栈 a=path[a]; } stack[++top]=a; //源点 while(top!=-1) cout<<stack[top--]<<" ";//出栈并打印出栈元素,实现顶点逆序打印 cout<<endl; } ``` ``` //迪杰斯特拉算法 void Dijkstra(MGraph g,int v,int dist[],int path[]) { int set[maxSize]; int min,i,j,u; //对各数组初始化 for(i=0;i<g.edges[v][i]) { dist[i]=g.edges[v][i]; set[i]=0; if(g.edges[v][i]<INF)path[i]=v; else path[i]=-1; } set[v]=1;path[v]=-1; //初始化结束 //关键操作 for(i=0;i<g.n-1;++i) { min=INF; //从剩余顶点选取一个顶点,通往这个顶点的路径在通往所有剩余结点中路径最短 for(j=0;j<g.n;++j) { if(set[j]==0&&dist[j]<min) { u=j; min=dist[j]; } } set[u]=1; //将选出的顶点并入最短路径 //将选出的顶点作为中间点,对所有通往剩余顶点的路径进行检测 for(j=0;j<g.n;++j) { //判断顶点u的加入是否会出现通往顶点j的更短的路径 //如果出现,则改变路径长度,否则什么都不做 if(set[j]==0&&dist[u]+g.edges[u][j]<dist[j]) { dist[j]=dist[u]+g.edges[u][j]; path[j]=u; } } } //关键操作结束 } //函数结束,dist[]数组存放了v点到其余顶点的最短路径长度 //path[]中存放v点到其余各顶点的最短路径 ``` > ## 2.费洛伊德算法 > ``` void Floyd(MGraph g,int Path[][maxSize]) { int i,j,k; int A[maxSize][maxSize]; //对A[][]和Path[][]进行初始化 for(i=0;i<g.n;++j) { for(j=0;j<g.n;++j) { A[i][j]=g.edges[i][j]; Path[i][j]=-1; } } //完成以k为中间点对所有顶点对{i,j}进行检测和修改 for(k=0;k<g.n;++k) { for(i=0;i<g.n;++i) { for(j=0;j<g.n;++j) { if(A[i][j]>A[i][k]+A[k][j]) { A[i][j]=A[i][k]+A[k][j]; Path[i][j]=k; } } } } } ``` # 五、拓扑排序 > ``` //拓扑排序 int TopSort(AGraph *G) { int i,j,n=0; int stack[maxSize],top=-1; ArcNode *p; //将图中入度为0的顶点入栈 for(i=0;i<G->n;++i) { if(G->adjlist[i].count==0) stack[++top]=i; } //关键操作 while(top!=-1) { i=stack[top--]; //顶点出栈 ++n; //计数器加1,统计当前顶点 cout<<i<<" "; //输出当前顶点 p=G->adjlist[i].firstarc; //将所有由此结点引出的边所指的顶点的入度减小1,将这个过程中入度为0的顶点入栈 while(p!=NULL) { j=p->adjvex; //该边所指向的结点位置 --(G->adjlist[j].count); if(G->adjlist[j].count==0) stack[++top]=j; p=p->nextarc; //指向下一条边的顶点 } } //关键操作结束 if(n==G->n) return 1; else return 0; } ``` > # 六、关键路径 > # 七、例程 ``` //1.判断以邻接表方式存储的有向图中是否存在由顶点vi到顶点vj的路径 //思路:广度优先搜索遍历BFS,起点为vi,BFS退出之前遇到vj,则证明有路径 int BFS(AGraph *G,int vi,int vj) { ArcNode *p; int que[maxSize],front=rear=0; int visit[maxSize]; int i,j; for(i=0;i<G->n;++i)visit[i]=0; rear=(rear+1)%maxSize; que[rear]=vi; visit[vi]=1; while(front!=rear) { front=(front+1)%maxSize;//出队 j=que[front]; if(j==vj)return 1;//找到了顶点vj p=G->adjlist[j].firstarc; //指向出队顶点的第一条边 while(p!=NULL) { if(visit[p->adjvex]==0) { rear=(rear+1)%maxSize; que[rear]=p->adjvex; visit[p->adjvex]=1; } p=p->nextarc; //p指向下一条边 } } return 0; } ``` ``` //2.有向图G中,如果r到G中的每个结点都有路径可达,则称结点r为G的根结点 //判断有向图是否有根 //深度优先搜索遍历DFS,以r为起点进行DFS遍历,若在函数退出时已经访问所有顶点,则r为根 int visit[maxSize],sum; //假设常量maxSize已经定义 void DFS(AGraph *G,int v) { ArcNode *p; visit[v]=1; ++sum;//每访问一个顶点,加1 p=G->adjlist[v].firstarc; while(p!=NULL) { if(visit[p->adjvex]==0) { DFS(G,p->adjvex); p=p->nextarc; } } } void print(AGraph *G) { int i,j; for(i=0;i<G->n;++i) { sum=0; //每次选取一个新起点计数器清零 for(j=0;j<G->n;++j)visit[j]=0; //每次进行DFS时访问标记数组清零 DFS(G,i); if(sum==G->n)cout<<i<<endl; //当图中所有顶点全部被访问时则判断为根,输出 } } ```