图的算法
真正的勇士,敢于直面惨淡的人生,敢于正视淋漓的鲜血
——鲁迅
这篇博文总结一下图的有关算法。转载请注明出处http://www.cnblogs.com/tianji,谢谢!
图的表示
要表示一个图G=(V,E),有两种标准的表示方法,邻接矩阵和邻接表。这两种方法都可用于有向图和无向图。对于稀疏图,常用邻接表表示,因为这样表示图更为紧凑(图中|E|要远小于|V|*|V|);当遇到稠密图或者必须很快判别两个给定顶点是否存在连接边时,一般采用邻接矩阵表示。
图G=(V,E)的邻接表表示由一个包含|V|个列表的数组Adj所组成,其中每个列表对应于V中的一个顶点,对于V中的每一个顶点u,Adj[u]包含所有和它相邻的顶点(或者指向这些顶点的指针)。如下两图分别问(b)为(a)无向图和有向图邻接表表示。
在图G=(V,E)的邻接矩阵表示中,假定各顶点按某种任意的方式编号为1,2,3,...,|V|,那么G的一个邻接矩阵为一个|V|*|V|的矩阵A=aij,它满足:
如上两图所示,(c)为(a)无向图、有向图的邻接矩阵表示。
广度优先搜索
在给定图G=(V,E)和一个特定的源顶点s的情况下,广度优先搜索系统的搜索G中的边以期发现可从s到达的所有顶点,并计算s的可达顶点之间的距离(即最少的边数)。该搜索算法同时还能生成一棵根为s,且包含所有s的可达顶点的广度优先树。对从s可达的任意顶点v,广度优先树中从s到v的路径对应于图G中从s到v的一条最短路,即包含最少边数的路径,该算法对有向图和无向图都适用。
总结一下:算法中将使用三个数组color,π,d分别,还用到队列Q,算法伪代码如下:
复杂度分析:
对此算法另外的一点分析:该算法得到一棵广度优先树,且求出了从 s到任意其他顶点的最短路径(无权),路径可通过π数组迭代求出,最短路径长度(最短的边数)即为d数组保存的值。
给出求最短路径的递归伪代码:
C语言代码如下:这里采用visited数组表示已访问,并没有伪代码中的其他数组,可以自己增加(参考严蔚敏C语言版《数据结构》)
全部代码如下:
#include<stdio.h> /* EOF(=^Z或F6),NULL */ #include<math.h> /* floor(),ceil(),abs() */ #define TRUE 1 #define FALSE 0 #define OK 1 #define ERROR 0 #define INFEASIBLE -1 typedef int Status; /* Status是函数的类型,其值是函数结果状态代码,如OK等 */ typedef int Boolean; /* Boolean是布尔类型,其值是TRUE或FALSE */ #define MAX_NAME 5 /* 顶点字符串的最大长度 */ typedef int InfoType; typedef char VertexType[MAX_NAME]; /* 字符串类型 */ #define MAX_VERTEX_NUM 20 typedef enum{DG,DN,AG,AN}GraphKind; /* {有向图,有向网,无向图,无向网} */ typedef struct ArcNode { int adjvex; /* 该弧所指向的顶点的位置 */ struct ArcNode *nextarc; /* 指向下一条弧的指针 */ InfoType *info; /* 网的权值指针) */ }ArcNode; /* 表结点 */ typedef struct { VertexType data; /* 顶点信息 */ ArcNode *firstarc; /* 第一个表结点的地址,指向第一条依附该顶点的弧的指针 */ }VNode,AdjList[MAX_VERTEX_NUM]; /* 头结点 */ typedef struct { AdjList vertices; int vexnum,arcnum; /* 图的当前顶点数和弧数 */ int kind; /* 图的种类标志 */ }ALGraph; int LocateVex(ALGraph G,VertexType u) { /* 初始条件: 图G存在,u和G中顶点有相同特征 */ /* 操作结果: 若G中存在顶点u,则返回该顶点在图中位置;否则返回-1 */ int i; for(i=0;i<G.vexnum;++i) if(strcmp(u,G.vertices[i].data)==0) return i; return -1; } Status CreateGraph(ALGraph *G) { /* 采用邻接表存储结构,构造没有相关信息的图G(用一个函数构造4种图) */ int i,j,k; int w; /* 权值 */ VertexType va,vb; ArcNode *p; printf("请输入图的类型(有向图:0,有向网:1,无向图:2,无向网:3): "); scanf("%d",&(*G).kind); printf("请输入图的顶点数,边数: "); scanf("%d,%d",&(*G).vexnum,&(*G).arcnum); printf("请输入%d个顶点的值(<%d个字符):\n",(*G).vexnum,MAX_NAME); for(i=0;i<(*G).vexnum;++i) /* 构造顶点向量 */ { scanf("%s",(*G).vertices[i].data); (*G).vertices[i].firstarc=NULL; } if((*G).kind==1||(*G).kind==3) /* 网 */ printf("请顺序输入每条弧(边)的权值、弧尾和弧头(以空格作为间隔):\n"); else /* 图 */ printf("请顺序输入每条弧(边)的弧尾和弧头(以空格作为间隔):\n"); for(k=0;k<(*G).arcnum;++k) /* 构造表结点链表 */ { if((*G).kind==1||(*G).kind==3) /* 网 */ scanf("%d%s%s",&w,va,vb); else /* 图 */ scanf("%s%s",va,vb); i=LocateVex(*G,va); /* 弧尾 */ j=LocateVex(*G,vb); /* 弧头 */ p=(ArcNode*)malloc(sizeof(ArcNode)); p->adjvex=j; if((*G).kind==1||(*G).kind==3) /* 网 */ { p->info=(int *)malloc(sizeof(int)); *(p->info)=w; } else p->info=NULL; /* 图 */ p->nextarc=(*G).vertices[i].firstarc; /* 插在表头 */ (*G).vertices[i].firstarc=p; if((*G).kind>=2) /* 无向图或网,产生第二个表结点 */ { p=(ArcNode*)malloc(sizeof(ArcNode)); p->adjvex=i; if((*G).kind==3) /* 无向网 */ { p->info=(int*)malloc(sizeof(int)); *(p->info)=w; } else p->info=NULL; /* 无向图 */ p->nextarc=(*G).vertices[j].firstarc; /* 插在表头 */ (*G).vertices[j].firstarc=p; } } return OK; } VertexType* GetVex(ALGraph G,int v) { /* 初始条件: 图G存在,v是G中某个顶点的序号。操作结果: 返回v的值 */ if(v>=G.vexnum||v<0) exit(ERROR); return &G.vertices[v].data; } int FirstAdjVex(ALGraph G,VertexType v) { /* 初始条件: 图G存在,v是G中某个顶点 */ /* 操作结果: 返回v的第一个邻接顶点的序号。若顶点在G中没有邻接顶点,则返回-1 */ ArcNode *p; int v1; v1=LocateVex(G,v); /* v1为顶点v在图G中的序号 */ p=G.vertices[v1].firstarc; if(p) return p->adjvex; else return -1; } int NextAdjVex(ALGraph G,VertexType v,VertexType w) { /* 初始条件: 图G存在,v是G中某个顶点,w是v的邻接顶点 */ /* 操作结果: 返回v的(相对于w的)下一个邻接顶点的序号。 */ /* 若w是v的最后一个邻接点,则返回-1 */ ArcNode *p; int v1,w1; v1=LocateVex(G,v); /* v1为顶点v在图G中的序号 */ w1=LocateVex(G,w); /* w1为顶点w在图G中的序号 */ p=G.vertices[v1].firstarc; while(p&&p->adjvex!=w1) /* 指针p不空且所指表结点不是w */ p=p->nextarc; if(!p||!p->nextarc) /* 没找到w或w是最后一个邻接点 */ return -1; else /* p->adjvex==w */ return p->nextarc->adjvex; /* 返回v的(相对于w的)下一个邻接顶点的序号 */ } Boolean visited[MAX_VERTEX_NUM]; /* 访问标志数组(全局量) */ void(*VisitFunc)(char* v); /* 函数变量(全局量) */ typedef int QElemType; /* 队列类型 */ typedef struct QNode { QElemType data; struct QNode *next; }QNode,*QueuePtr; typedef struct { QueuePtr front,rear; /* 队头、队尾指针 */ }LinkQueue; Status InitQueue(LinkQueue *Q) { /* 构造一个空队列Q */ (*Q).front=(*Q).rear=(QueuePtr)malloc(sizeof(QNode)); if(!(*Q).front) exit(OVERFLOW); (*Q).front->next=NULL; return OK; } Status QueueEmpty(LinkQueue Q) { /* 若Q为空队列,则返回TRUE,否则返回FALSE */ if(Q.front==Q.rear) return TRUE; else return FALSE; } Status EnQueue(LinkQueue *Q,QElemType e) { /* 插入元素e为Q的新的队尾元素 */ QueuePtr p=(QueuePtr)malloc(sizeof(QNode)); if(!p) /* 存储分配失败 */ exit(OVERFLOW); p->data=e; p->next=NULL; (*Q).rear->next=p; (*Q).rear=p; return OK; } Status DeQueue(LinkQueue *Q,QElemType *e) { /* 若队列不空,删除Q的队头元素,用e返回其值,并返回OK,否则返回ERROR */ QueuePtr p; if((*Q).front==(*Q).rear) return ERROR; p=(*Q).front->next; *e=p->data; (*Q).front->next=p->next; if((*Q).rear==p) (*Q).rear=(*Q).front; free(p); return OK; } void BFSTraverse(ALGraph G,void(*Visit)(char*)) {/*按广度优先非递归遍历图G。使用辅助队列Q和访问标志数组visited。算法7.6 */ int v,u,w; VertexType u1,w1; LinkQueue Q; for(v=0;v<G.vexnum;++v) visited[v]=FALSE; /* 置初值 */ InitQueue(&Q); /* 置空的辅助队列Q */ for(v=0;v<G.vexnum;v++) /* 如果是连通图,只v=0就遍历全图 */ if(!visited[v]) /* v尚未访问 */ { visited[v]=TRUE; Visit(G.vertices[v].data); EnQueue(&Q,v); /* v入队列 */ while(!QueueEmpty(Q)) /* 队列不空 */ { DeQueue(&Q,&u); /* 队头元素出队并置为u */ strcpy(u1,*GetVex(G,u)); for(w=FirstAdjVex(G,u1);w>=0;w=NextAdjVex(G,u1,strcpy(w1,*GetVex(G,w)))) if(!visited[w]) /* w为u的尚未访问的邻接顶点 */ { visited[w]=TRUE; Visit(G.vertices[w].data); EnQueue(&Q,w); /* w入队 */ } } } printf("\n"); } void main() { ALGraph f; printf("请选择有向图\n"); CreateGraph(&f); Display(f); BFSTraverse(f); }
主要代码如下:
void BFSTraverse(ALGraph G,void(*Visit)(char*)) {/*按广度优先非递归遍历图G。使用辅助队列Q和访问标志数组visited。算法7.6 */ int v,u,w; VertexType u1,w1; LinkQueue Q; for(v=0;v<G.vexnum;++v) visited[v]=FALSE; /* 置初值 */ InitQueue(&Q); /* 置空的辅助队列Q */ for(v=0;v<G.vexnum;v++) /* 如果是连通图,只v=0就遍历全图 */ if(!visited[v]) /* v尚未访问 */ { visited[v]=TRUE; Visit(G.vertices[v].data); EnQueue(&Q,v); /* v入队列 */ while(!QueueEmpty(Q)) /* 队列不空 */ { DeQueue(&Q,&u); /* 队头元素出队并置为u */ strcpy(u1,*GetVex(G,u)); for(w=FirstAdjVex(G,u1);w>=0;w=NextAdjVex(G,u1,strcpy(w1,*GetVex(G,w)))) if(!visited[w]) /* w为u的尚未访问的邻接顶点 */ { visited[w]=TRUE; Visit(G.vertices[w].data); EnQueue(&Q,w); /* w入队 */ } } } printf("\n"); }
深度优先搜索