图论算法小结

//邻接矩阵存储结构定义例如以下:
//邻接矩阵包括两种数组:顶点表和边表
#define MaxVertexNum 100				//顶点数目的最大值
typedef char VertexType;				//顶点的数据类型
typedef int EdgeType;					//带权图中边上权值的数据类型
typedef struct{
	VertexType Vex[MaxVertexNum];				//顶点表
	EdgeType Edge[MaxVertexNum][MaxVertexNum];	//邻接矩阵,边表
	int vexnum,arcnum;					//图的当前顶点数和弧数
}AGraph;
//邻接表法:
//邻接表中存在两种结点:顶点表结点和边表结点。

当中。顶点表包括:顶点域和边表头指针。边表结点包括:邻接点域和指针域 #define MaxVertexNum 100 //图中顶点数目的最大值 typedef struct ArcNode{ //边表结点 int adjvex; //该弧所指向的顶点的位置 struct ArcNode *next; //指向下一条弧的指针 //InfoType ifo; //网的边权值 }ArcNode; typedef struct VNode{ //顶点表结点 VertexType data; //顶点信息 ArcNode *first; //指向第一条依附该顶点的弧的指针 }VNode,AdjList[MaxVertexNum]; typedef struct{ AdjList vertices; //邻接表 int vexnum,arcnum; //图的顶点数和弧数 }ALGraph; //ALGraph是以邻接表存储的图类型 /* 说明: ALGraph G; G.vertices[i]:邻接表中顶点表数组中的元素(是一个结构体)。 p=G.vertices[i].first:邻接表中顶点表数组中下标为i的元素指向的第一个边表结点的地址(也即指向的第一个边表结点)。 p->adjvex:边表结点的结点号。 p->next:下一个边表结点的地址(也即指向的下一个边表结点)。 "v=FirstNeighbor(G,i);"等价于:"v=G.vertices[i].first->adjvex;" FirstNeighbor(G,x):求图中顶点x的第一个邻接点,若有则返回顶点号。

若x没有邻接点或图中不存在x,则返回-1。 NextNeightor(G,x,y):假设图G中顶点y是顶点x的一个邻接点,返回除y之外顶点x的下一个邻接点的顶点号,若y是x的最后一个邻接点,则返回-1。 对于图的不同的存储结构留有同样的函数。比如: //以邻接矩阵作存储结构 int NextNeighbor(AGraph G,int x,int y){ if(x!=-1 && y!=-1){ for(int col=y+1;col<G.vexnum;col++) if(G.Edge[x][col]>0 && G.Edge[x][col]<maxWeight) //maxWeight代表无穷 return col; } return -1; } //以邻接表作存储结构 int NextNeighbor(ALGraph G,int x,int y){ if(x!=-1){ ArcNode *p=G.vertices[x].first; while(p!=NULL && p->data!=y) p=p->next; if(p!=NULL && p->next!=NULL) return p->next->data; } return -1; } */ //从图的邻接表表示转换成邻接矩阵表示的算法: void Convert(ALGraph &G,int arcs[N][N]){ int i=0,t=0; for(i=0;i<N;i++){ for(t=FirstNeighbor(A,i);t>=0;t=NextNeighbor(A,i,t)) arcs[i][t]=1; } } //或者: void Convert(ALGraph &G,int arcs[N][N]){ int i=0; ArcNode p; for(i=0;i<N;i++){ p=G.vertices[i].first; //或者:p=((&G)->vertices[i]).first; while(p!=NULL){ arcs[i][p->data]=1; p=p->nextarc; } } } //广度优先搜索(Breadth-First-Search,BFS) bool visited[MAX_VERTEX_NUM); //訪问标记数组 void BFSTraverse(Graph G){ //对图G进行广度优先遍历,设訪问函数为visit() for(i=0;i<G.vexnum;i++) visited[i]=FALSE; //訪问标志数组初始化 InitQueue(Q); //初始化辅助队列Q for(i=0;i<G.vexnum;i++) //从0号顶点開始遍历 if(!visited[i]) //对每一个连通分量開始遍历 BFS(G,i); //v[i]未訪问过,从v[i]開始BFS } void BFS(Graph G,int v){ //从顶点v出发,广度优先遍历图G,算法借助一个辅助队列Q visit(v); //訪问初始顶点v visited[v]=TRUE; //对v做已訪问标志 Enqueue(Q,v); //顶点v入队列 while(!isEmpty(Q)){ DeQueue(Q,v); //顶点v出队列 for(w=FirstNeighbor(G,v);w>=0;w=NextNeighbor(G,v,w)) //检測v全部邻接点 if(!visited[w]){ //w为v的尚未訪问的邻接顶点 visit(w); //訪问顶点w visited[w]=TRUE; //对w做已訪问标记 EnQueue(Q,w); //顶点w入队列 } } } //BFS算法求解单源最短路径问题的算法: void BFS_MIN_Distance(Graph G,int u){ //d[i]表示从u到i结点的最短路径 for(i=0;i<G.vexnum;i++) d[i]=INF //初始化路径长度为无穷 visited[u]=TRUE; d[u]=0; EnQueue(Q,u); while(!isEmpty(Q)){ //BFS算法主过程 DeQueue(Q,u); for(w=FirstNeighbor(G,u);w>=0;w=NextNeighbor(G,u,w)) if(!visited[w]){ visited[w]=TRUE; d[w]=d[u]+1; //路径长度加1 EnQueue(Q,w); } } } //深度优先搜索(Depth-First-Search,DFS) bool visited[MAX_VERTEX_NUM]; //訪问标记数组 void DFSTraverse(Graph G){ //对图G进行尝试优先遍历。訪问函数为visit() for(v=0;v<G.vexnum;v++) visited[v]=FALSE; //訪问标志数组初始化 for(v=0;v<G.vexnum;v++) //本代码中是从v=0開始遍历 if(!visited[v]) DFS(G,v); } void DFS(Graph G,int v){ //从顶点v出发。採用递归思想。深度优先遍历图G visit(v); //訪问顶点v visited[v]=TRUE; //设已訪问标记 for(w=FirstNeighbor(G,v);w>=0;w=NextNeighbor(G,v,w)) if(!visited[w]){ //w为u的尚未訪问的邻接顶点 DFS(G,w); } } //推断一个无向图G是否为一棵树 //思路:一个无向图G是一棵树的条件是:G必须是无回路的连通图或者是有n-1条边的连通图(这里採用后一种方法) //法一:广度优先搜索 bool visited[MAX_VERTEX_NUM]; int Vnum=0,Enum=0; bool IsTree1(Graph G){ int v=0,w; for(v=0;v<G.vexnum;v++) visited[v]=FALSE; v=0; visited[v]=TRUE; EnQueue(Q,v); Vnum=1; while(!IsEmpty(Q)){ DeQueue(Q,v); for(w=FirstNeighbor(Q,v);w>=0;w=NextNeighbor(G,v,w)){ Enum++; if(!visited[w]){ visited[w]=TRUE; EnQueue(Q,w); Vnum++; } } } if(Vnum==G.vexnum && Enum==2*(G.vernum-1)) return TRUE; else return FALSE; } //法二:深度优先搜索: bool visited[MAX_VERTEX_NUM]。 int Vnum=0,Enum=0; bool IsTree2(Graph G){ for(i=1;i<=G.vexnum;i++) visited[i]=FALSE; DFS(G,1); if(Vnum==G.vexnum && Enum==2*(G.vexnum-1)) return TRUE; else return FALSE; } void DFS(Graph G,int v){ visited[v]=TRUE; Vnum++; int w=FirstNeighbor(G,v); while(w!=-1){ //能够写成for的形式 Enum++; if(!visited[w]) DFS(G,w); w=NextNeighbor(G,v,w); } } //附:推断一个有向图是否为一棵树的算法: //这个好像有点问题??? int Vnum=0; bool visited[MAX_VERTEX_NUM]; bool IsTree(Graph G){ for(v=0;v<G.vexnum;v++) visited[v]=FALSE; if(!DFS(G,v)) return FALSE; if(Vnum==G.vexnum) return TRUE; else return FALSE; } bool DFS(Graph G,int v){ visited[v]=true; Vnum++; for(w=FirstNeighbor(G,v);w>=0;w=NextNeighbor(G,v,w)){ if(visited[w]) return FALSE; if(!DFS(G,w)) //假设訪问到了已訪问过的结点,就一直返回FALSE。直到退出,说明不是一个棵。

return FSLSE; } return TRUE; } //DFS的非递归算法: //法一:此算法栈叶元素是v到i的路径,可是不过一条路径,无法找到全部路径。(以下有一个算法能够打印全部简单路径。

) bool visited[MAX_VEXNUM_NUM]; void DFS_Non_RC(ALGraph G,int v){ for(v=0;v<G.vexnum;v++) visited[v]=FALSE; InitStack(S); for(v=0;v<G.vexnum;v++) if(!visited[v]){ //DFS while(v>=0 || !IsEmpty(S)){ while(v>=0){ if(!visited[v]){ visit(v); visited[v]=TRUE; Push(S,v); v=FirstNeighbor(G,v); //或v=G.vertices[v].first->adjvex; } else break; } Pop(S,w); GetTop(S,v); w=NextNeighbor(G,v,w); while(w>=0 && visited[w]) //退出循环时。w可能为-1或者是w>=0可是没有被訪问过 w=NextNeighbor(G,v,w); v=w; } } } //法二:此算法是从右端到左端的顺序进行的。栈中的元素不是v到i的路径。

bool visited[MAX_VEXNUM_NUM]; void DFS_Non_RC(ALGraph G,int v){ int w; InitStack(S); for(i=0;i<G.vexnum;i++) visited[i]=FALSE; Push(S,v); visited[v]=TRUE; while(!IsEmpty(S){ k=Pop(S); visit(k); for(w=FirstNeighbor(G,k);w>=0;w=NextNeighbor(G,k,w)) if(!visited[w]){ Push(S,w); visited[w]=TRUE; } } } //推断以邻接表方式存储的有向图中是否存在同顶点i到顶点j的路径(i!=j)。 //法一:BFS bool visited[MAX_VEXNUM_NUM]; bool BFSTest(ALGraph G,int i,int j){ int v=0; for(v=0;v<G.vexnum;v++) visited[v]=FALSE; InitQueue(Q); EnQueue(Q,i); visited[i]=TRUE; while(!IsEmpty(Q)){ DeQueue(Q,v); for(w=FirstNeighbor(G,v);w>=0;w=NextNeighbor(G,v,w)){ if(w==j) return TRUE; if(!visited[w]){ EnQueue(Q,w); visited[w]=TRUE; } } } return FALSE; } //法二:DFS bool visited[MAX_VEXNUM_NUM]; bool DFSTest(ALGraph G,int i,int j){ int v=0; for(v=0;v<G.vexnum;v++) visited[v]=FALSE; //可採用非递归DFS。这里採用递归DFS return DFS(G,i,j); } bool DFS(ALGraph G,int i,int j){ visited[i]=TRUE; for(w=FirstNeighbor(G,i);w>=0;w=NextNeighbor(G,i,w)){ if(w==j) return TRUE; if(!visited[w]) if(DFS(G,w,j)) //假设找到。一直返回TRUE,直到退出 return TRUE; } return FALSE; } //输出图中从顶点i到顶点j的全部简单路径 //法一:非递归DFS bool visited[MAX_VEXNUM_NUM]; void DFS_Non_RC_Path(AGraph G,int i,int j){ int s[MAX_VEXNUM_NUM]; //栈 int top=-1,v=0; for(v=0;v<G.vexnum;v++) visited[v]=FALSE; v=i; while(v>=0 || top!=-1){ while(v>=0){ if(!visited[v]){ visited[v]=TRUE; s[++top]=v; //Push if(v==j){ //打印路径 for(int k=0;k<=top;k++) printf("%d ",s[k]); printf("\n"); break; } v=FirstNeighbor(G,v); while(v>=0 && visited[v]) v=NextNeighbor(G,s[top],v); } else break; } w=s[top--]; //Pop visited[w]=FALSE //退栈时清除訪问标记 if(top==-1) break; v=s[top]; //GetTop w=NextNeighbor(G,v,w); while(w>=0 && visited[w]) w=NextNeighbor(G,v,w); v=w; } } //法二:递归DFS bool visited[MAX_VEXNUM_NUM]; int d=-1; int path[MAX_VEXNUM_NUM]; void FindPath(AGraph G,int i,int j){ int v=0; for(v=0;v<G.vexnum;v++) visited[v]=FALSE; DFS_Path(G,i,j); } void DFS_Path(ALGraph G,int u,int v){ int w; ArcNode *p; d++; path[d]=u; visited[u]=TRUE; if(u==v) print(path,d+1); p=G.vertices[u].first; //*能够改为:for(w=FirstNeighbor(G,u);w>=0;w=NextNeighbor(G,u,w)) while(p!=NULL){ //* if(!visited[w]) w=p->adjvex; //* DFS_Path(G,w,v); if(!visited[w]) //* DFS_Path(G,w,v);//* p=p->next; //* } //* visited[u]=FALSE; d--; } ------------------------------------------------------------------------------------------------------------------- ************************************************** 图的应用 ***************************************************** ------------------------------------------------------------------------------------------------------------------- //最小生成树(Minimum-Spanning-Tree,MST) //Prim(对照最短路径中的Dijkstra算法) #define INFINITY 10000 #define MAX_VERTICES 5 typedef int Graph[MAX_VERTICES][MAX_VERTICES]; void prim(Graph G,int vcount,int father[]){ int i,j,k; int lowcost[MAX_VERTICES],closeset[MAX_VERTICES],used[MAX_VERTICES]; for(i=0;i<vcount;i++){ lowcost[i]=G[0][i]; //有边的点相应的是边的权值。没有边的点相应的是INFINITY closeset[i]=0; //最小权值边在已增加点集中的点 used[i]=0; father[i]=-1; } used[0]=1; for(i=1;i<vcount;i++){ j=0; while(used[j]) j++; for(k=0;k<vcount;k++) if(!used[k] && lowcost[k]<lowcost[j]) j=k; father[i]=closeset[j]; //这个father[]能够不要,由于。已增加到已增加点集中的点的closeset[]值不会变,终于程序退出时,closeset[]中就会有相应的信息 used[j]=1; //每增加一个点。就更新未增加点到已增加点集中的点的最短路径信息 for(k=0;k<vcount;k++) if(!used[k] && G[j][k]<lowcost[k]){ //更新最短路径信息 lowcost[k]=G[j][k]; closeset[k]=j; } } } //Kruskal typedef struct { int a; int b; int pri; } Node; void kruskal(Graph G,int Vcount,int Ecount,int father[]){ Node p[Ecount],temp; int t=0,i,j; for(i=0;i<Vcount;i++) for(j=0;j<Vcount;j++) if(i!=j){ temp.a=i; temp.b=j; temp.pri=G[i][j]; for(k=t-1;k>=0;k--) //这个事实上是将边按权值进行升序排序 if(p[k].pri > temp.pri) p[K+1]=p[k]; else break; p[k+1]=temp; t++; } //生成树 memset(used,0,sizeof(used)); memset(father,-1,sizeof(father)); for(i=0;i<t;i++) if(!used[p[i].a] || !used[p[i].b]){ //只要此边有一个点不在used中,就增加此边 used[p[i].a]=used[p[i].b]=1; father[p[i].b]=p[i].a; } //假设图一定能生成一棵树。这里就不用推断了。 } //最短路径 //Dijkstra求单源最短路径 void Dijkstra(Graph G,int Vcount,int s,int path[],int dist[]){ //Vcount:顶点个数。s:開始结点;path[]用于返回由開始结点到相应结点的路径指示 int i,j,w,minc,dist[Vcount],mark[Vcount]; memset(mark,0,Vcount); for(i=0;i<Vcount;i++){ dist[i]=G[s][i]; path[i]=s; } mark[s]=1;path[s]=0;dist[s]=0; for(i=1;i<Vcount;i++){ minc=INFINITY; w=0; for(j=0;j<Vcount;j++) if(!mark[j] && minc>=dist[j]){ minc=dist[j]; w=j; } mark[w]=1; for(j=0;j<Vcount;j++) if(!mark[j] && G[w][j]!=INFINITY && dist[j]>dist[w]+G[w][j]){ dist[j]=dist[w]+G[w][j]; path[j]=w; } } } //注意:输入的图的边的权值必须非负(这是Dijkstra的局限性所在). //顶点号从0開始,用例如以下方法打印路径: void printpath(int path[],int s,int t){ //t:目标结点 int i=t; while(i!=s){ printf("%d<--",i+1); i=path[i]; } printf("%d\n",s+1); } //Floyd_Warshall算法(简称为:Floyd算法)用于求各顶点之间最短路径问题 void Floyd_Warshall(Graph G,int Vcount,Graph D,Graph P){ //D:D[i][j]表示从i到j的最短距离;P:P[i][j]表示从i到j的最短路径上的结点 int i,j,k; for(i=0;i<Vcount;i++) for(j=0;j<Vcount;j++){ D[i][j]=G[i][j]; P[i][j]=i; } for(i=0;i<Vcount;i++){ D[i][i]=0; P[i][i]=0; } for(k=0;k<Vcount;k++) for(i=0;i<Vcount;i++) for(j=0;j<Vcount;j++) if(D[i][j]>D[i][k]+D[k][j]){ D[i][j]=D[i][k]+D[k][j]; P[i][j]=P[k][j]; } } //顶点号从0開始,用例如以下方法打印路径: void printpath(int P[],int s,int t){ //s:出发结点。t:目标结点 int i=t; while(i!=s){ printf("%d<--",i+1); i=P[s][i]; } printf("%d\n",s+1); } //Bellman_Ford:求单源最短路径 int Bellman_Ford(Graph G,int Vcount,int s,int path[],int &success){ int i,j,k,dist[Vcount]; for(i=0;i<n;i++){ dist[i]=INFINITY; path[i]=0; } dist[s]=0; for(i=0;i<Vcount;i++) for(j=0;j<Vcount;j++) if(dist[j]>dist[i]+G[i][j]){ dist[j]=dist[i]+G[i][j]; path[j]=i; } for(j=0;j<Vcount;j++) if(dist[j]>dist[i]+G[i][j]) return 0;//有负权回路 return 1;//找路成功 } //注意:输入的图的边的权能够为负(这也正是这个算法的用处所在),假设存在一个从源点可达的权为负的回路则success为0。 //顶点号从0開始。用例如以下方法打印路径: void printpath(int path[],int s,int t){ //s:出发结点。t:目标结点 int i=t; while(i!=s){ printf("%d<--",i+1); i=path[i]; } printf("%d\n",s+1); } //改进Bellman_Ford int Bellman_Ford(Graph G,int Vcount,int s,int path[],int &success){ int i,j,k,dist[Vcount]; for(i=0;i<n;i++){ dist[i]=INFINITY; path[i]=0; } dist[s]=0; for(i=0;i<Vcount;i++){ int relaxed=0; for(j=0;j<Vcount;j++) if(dist[j]>dist[i]+G[i][j]){ dist[j]=dist[i]+G[i][j]; path[j]=i; relaxed=1; } if(relaxed==0) break; } for(j=0;j<Vcount;j++) if(dist[j]>dist[i]+G[i][j]) return 0;//有负权回路 return 1;//找路成功 } //拓扑排序 //法一: bool TopologicalSort(Graph G){ int i=0,count=0; InitStack(S); for(i=0;i<G.vexnum;i++) if(indegree[i]==0) Push(S,i); while(!IsEmpty(S)){ Pop(S,i); print[count++]=i; for(p=G.vertices[i].first;p;p=p->next){ v=p->adjvex; if(!(--indegree[v])) Push(S,v) } } if(count<G.vexnum) return FALSE; else return TRUE; } //法二:利用DFS求各结点的訪问结束时间。将结束时间从大到小排序就可以得到一条拓扑序列。 bool visited[MAX_VERTEX_NUM]; int finishTime[MAX_VERTEX_NUM]; int time=0; void DFS_Traverse(Graph G){ memset(visited,0,sizeof(visited)); memset(finishTime,0,sizeof(finishTime)); for(v=0;v<G.vexnum;v++) if(!visited[v]) DFS(G,v); //对finishTime[]按时间值从大到小输出即得到一条拓扑序列 print(finishTime); } void DFS(Graph G,int v){ visited[v]=TRUE; for(w=FirstNeighbor(G,v);w>=0;w=NextNeighbor(G,v,w)) if(!visited[w]) DFS(G,w); time+=1; finishTime[v]=time; } 假设在使用过程中。出现段错误。可能须要将一些变量设置为全局的。 上面的代码体现了算法的思想,可能一些详细的细节未考虑进去。


posted on 2017-04-19 12:52  ljbguanli  阅读(559)  评论(0编辑  收藏  举报