浅谈数据结构之图的邻接表深度和广度优先遍历(九)
邻接矩阵是一种不错的图存储结构,但是我们发现,对于边数相对较少的图,这种结构是存在对存储空间的极大浪费的。我们知道,顺序存储结构存在预先分配内存可能造成空间浪费的问题,于是引出了链式存储的结构。同样的,我们也可以考虑对边或弧使用链式存储的方式来避免空间浪费的问题。因此,对于图的存储结构,我们同样引入了一种数组与链表相组合的存储方法,我们一般称之为邻接表。
邻接表的处理方法是这样的:(1).图中顶点用一个一维数组存储,当然,顶点也可以用单链表来存储,不过数组可以较容易的读取顶点的信息,更加方便;另外,每个数据元素还需要存储指向第一个邻接点的指针,以便于查找该顶点的边信息;(2).图中每个顶点Vi的所有邻接点构成一个线性表,由于邻接点的个数不定,所以用单链表存储,无向图称为顶点Vi的边表,有向图则称为顶点Vi作为弧尾的出边表。
对比图的深度优先遍历与广度优先遍历,我们发现,它们在时间复杂度上是一样的,不同之处仅仅在于对顶点的访问顺序不同,可见两者在全图遍历上是没有优劣之分的,只是视不同的情况选择不同的方式而已。不过深度优先遍历更适合于目标比较明确,以找到目标为主要目的的情况,而广度优先遍历更适合在不断扩大遍历范围时找到相对最优解的情况。
图的创建及邻接表的深度和广度优先遍历源程序代码如下所示:
1 #include <stdio.h> 2 #include <stdlib.h> 3 4 #define OK 1 5 #define ERROR 0 6 #define TRUE 1 7 #define FALSE 0 8 9 #define MAXSIZE 9 /* 存储空间初始分配量 */ 10 #define MAXEDGE 15 11 #define MAXVEX 9 12 #define INFINITY 65535 13 14 typedef int Status; /* Status是函数的类型,其值是函数结果状态代码,如OK等 */ 15 typedef int Boolean; /* Boolean是布尔类型,其值是TRUE或FALSE */ 16 17 typedef char VertexType; /* 顶点类型应由用户定义 */ 18 typedef int EdgeType; /* 边上的权值类型应由用户定义 */ 19 20 /* 邻接矩阵结构 */ 21 typedef struct 22 { 23 VertexType vexs[MAXVEX]; /* 顶点表 */ 24 EdgeType arc[MAXVEX][MAXVEX]; /* 邻接矩阵,可看作边表 */ 25 int numVertexes, numEdges; /* 图中当前的顶点数和边数 */ 26 }MGraph; 27 28 /* 邻接表结构 */ 29 typedef struct EdgeNode /* 边表结点 */ 30 { 31 int adjvex; /* 邻接点域,存储该顶点对应的下标 */ 32 int weight; /* 用于存储权值,对于非网图可以不需要 */ 33 struct EdgeNode *next; /* 链域,指向下一个邻接点 */ 34 }EdgeNode; 35 36 typedef struct VertexNode /* 顶点表结点 */ 37 { 38 int in; /* 顶点入度 */ 39 char data; /* 顶点域,存储顶点信息 */ 40 EdgeNode *firstedge; /* 边表头指针 */ 41 }VertexNode, AdjList[MAXVEX]; 42 43 typedef struct 44 { 45 AdjList adjList; 46 int numVertexes,numEdges; /* 图中当前顶点数和边数 */ 47 }graphAdjList,*GraphAdjList; 48 49 /* 用到的队列结构与函数 */ 50 /* 循环队列的顺序存储结构 */ 51 typedef struct 52 { 53 int data[MAXSIZE]; 54 int front; /* 头指针 */ 55 int rear; /* 尾指针,若队列不空,指向队列尾元素的下一个位置 */ 56 }Queue; 57 58 /* 初始化一个空队列Q */ 59 Status InitQueue(Queue *Q) 60 { 61 Q->front=0; 62 Q->rear=0; 63 return OK; 64 } 65 66 /* 若队列Q为空队列,则返回TRUE,否则返回FALSE */ 67 Status QueueEmpty(Queue Q) 68 { 69 if(Q.front==Q.rear) /* 队列空的标志 */ 70 return TRUE; 71 else 72 return FALSE; 73 } 74 75 /* 若队列未满,则插入元素e为Q新的队尾元素 */ 76 Status EnQueue(Queue *Q,int e) 77 { 78 if ((Q->rear+1)%MAXSIZE == Q->front) /* 队列满的判断 */ 79 return ERROR; 80 Q->data[Q->rear]=e; /* 将元素e赋值给队尾 */ 81 Q->rear=(Q->rear+1)%MAXSIZE; /* rear指针向后移一位置, */ 82 /* 若到最后则转到数组头部 */ 83 return OK; 84 } 85 86 /* 若队列不空,则删除Q中队头元素,用e返回其值 */ 87 Status DeQueue(Queue *Q,int *e) 88 { 89 if (Q->front == Q->rear) /* 队列空的判断 */ 90 return ERROR; 91 *e=Q->data[Q->front]; /* 将队头元素赋值给e */ 92 Q->front=(Q->front+1)%MAXSIZE; /* front指针向后移一位置, */ 93 /* 若到最后则转到数组头部 */ 94 return OK; 95 } 96 97 void CreateMGraph(MGraph *G) 98 { 99 int i, j; 100 101 G->numEdges=15; 102 G->numVertexes=9; 103 104 /* 读入顶点信息,建立顶点表 */ 105 G->vexs[0]='A'; 106 G->vexs[1]='B'; 107 G->vexs[2]='C'; 108 G->vexs[3]='D'; 109 G->vexs[4]='E'; 110 G->vexs[5]='F'; 111 G->vexs[6]='G'; 112 G->vexs[7]='H'; 113 G->vexs[8]='I'; 114 115 for (i = 0; i < G->numVertexes; i++) /* 初始化图 */ 116 { 117 for ( j = 0; j < G->numVertexes; j++) 118 { 119 G->arc[i][j]=0; 120 } 121 } 122 123 G->arc[0][1]=1; 124 G->arc[0][5]=1; 125 126 G->arc[1][2]=1; 127 G->arc[1][8]=1; 128 G->arc[1][6]=1; 129 130 G->arc[2][3]=1; 131 G->arc[2][8]=1; 132 133 G->arc[3][4]=1; 134 G->arc[3][7]=1; 135 G->arc[3][6]=1; 136 G->arc[3][8]=1; 137 138 G->arc[4][5]=1; 139 G->arc[4][7]=1; 140 141 G->arc[5][6]=1; 142 143 G->arc[6][7]=1; 144 145 for(i = 0; i < G->numVertexes; i++) 146 { 147 for(j = i; j < G->numVertexes; j++) 148 { 149 G->arc[j][i] =G->arc[i][j]; 150 } 151 } 152 } 153 154 /* 利用邻接矩阵构建邻接表 */ 155 void CreateALGraph(MGraph G,GraphAdjList *GL) 156 { 157 int i,j; 158 EdgeNode *e; 159 160 *GL = (GraphAdjList)malloc(sizeof(graphAdjList)); 161 162 (*GL)->numVertexes=G.numVertexes; 163 (*GL)->numEdges=G.numEdges; 164 for(i= 0;i <G.numVertexes;i++) /* 读入顶点信息,建立顶点表 */ 165 { 166 (*GL)->adjList[i].in=0; 167 (*GL)->adjList[i].data=G.vexs[i]; 168 (*GL)->adjList[i].firstedge=NULL; /* 将边表置为空表 */ 169 } 170 171 for(i=0;i<G.numVertexes;i++) /* 建立边表 */ 172 { 173 for(j=0;j<G.numVertexes;j++) 174 { 175 if (G.arc[i][j]==1) 176 { 177 e=(EdgeNode *)malloc(sizeof(EdgeNode)); 178 e->adjvex=j; /* 邻接序号为j */ 179 e->next=(*GL)->adjList[i].firstedge; /* 将当前顶点上的指向的结点指针赋值给e */ 180 (*GL)->adjList[i].firstedge=e; /* 将当前顶点的指针指向e */ 181 (*GL)->adjList[j].in++; 182 183 } 184 } 185 } 186 } 187 188 Boolean visited[MAXSIZE]; /* 访问标志的数组 */ 189 190 /* 邻接表的深度优先递归算法 */ 191 void DFS(GraphAdjList GL, int i) 192 { 193 EdgeNode *p; 194 visited[i] = TRUE; 195 printf("%c ",GL->adjList[i].data); /* 打印顶点,也可以其它操作 */ 196 p = GL->adjList[i].firstedge; 197 while(p) 198 { 199 if(!visited[p->adjvex]) 200 DFS(GL, p->adjvex); /* 对为访问的邻接顶点递归调用 */ 201 p = p->next; 202 } 203 } 204 205 /* 邻接表的深度遍历操作 */ 206 void DFSTraverse(GraphAdjList GL) 207 { 208 int i; 209 for(i = 0; i < GL->numVertexes; i++) 210 visited[i] = FALSE; /* 初始所有顶点状态都是未访问过状态 */ 211 for(i = 0; i < GL->numVertexes; i++) 212 if(!visited[i]) /* 对未访问过的顶点调用DFS,若是连通图,只会执行一次 */ 213 DFS(GL, i); 214 } 215 216 /* 邻接表的广度遍历算法 */ 217 void BFSTraverse(GraphAdjList GL) 218 { 219 int i; 220 EdgeNode *p; 221 Queue Q; 222 for(i = 0; i < GL->numVertexes; i++) 223 visited[i] = FALSE; 224 InitQueue(&Q); 225 for(i = 0; i < GL->numVertexes; i++) 226 { 227 if (!visited[i]) 228 { 229 visited[i]=TRUE; 230 printf("%c ",GL->adjList[i].data); /* 打印顶点,也可以其它操作 */ 231 EnQueue(&Q,i); 232 while(!QueueEmpty(Q)) 233 { 234 DeQueue(&Q,&i); 235 p = GL->adjList[i].firstedge; /* 找到当前顶点的边表链表头指针 */ 236 while(p) 237 { 238 if(!visited[p->adjvex]) /* 若此顶点未被访问 */ 239 { 240 visited[p->adjvex]=TRUE; 241 printf("%c ",GL->adjList[p->adjvex].data); 242 EnQueue(&Q,p->adjvex); /* 将此顶点入队列 */ 243 } 244 p = p->next; /* 指针指向下一个邻接点 */ 245 } 246 } 247 } 248 } 249 } 250 251 252 int main(void) 253 { 254 MGraph G; 255 GraphAdjList GL; 256 CreateMGraph(&G); 257 CreateALGraph(G,&GL); 258 259 printf("\n1.图的邻接表的深度优先遍历为:"); 260 DFSTraverse(GL); 261 262 printf("\n2.图的邻接表的广度优先遍历为:"); 263 BFSTraverse(GL); 264 265 return 0; 266 }