广度优先搜索遍历图
从图中的某个顶点V0出发,并在访问此顶点之后依次访问V0的所有未被访问的邻接点,之后按这些顶点被访问的先后次序依次访问它们的邻接点,直至图中所有和V0有路径相通的顶点都被访问到。若此时图中尚有顶点未被访问,则另选图中一个未曾被访问的顶点作为起始点,重复上述过程,直至图中所有顶点都被访问到为止。
26_002 |
void BFSTraverse(Graph G, Status(* Visit)(int v)) { for(v = 0; v < G.vexnum; ++ v) { visited[v] = FALSE; } InitQueue(Q); //置空的辅助队列Q for(v = 0; v < G.vexnum; ++ v) { if(! visited[v]) { //v尚未访问 EnQueue(Q, v); //v入队列 visited[u] = TRUE; Visti(u); //访问u while(! QueueEmpty(Q)) { DeQueue(Q, u); //对头元素的出队并置u //访问u for(w = FirstAdjVex(G, u); w != 0; w = NextAdjVex(G, u, w)) { if(! visited[w]) { EnQueue(Q, w); visited[w] = TRUE; Visit(w); } //u的尚未访问的邻接顶点w入队列Q } }; } } }//BFSTraverse |
遍历的简单应用
1. 求一条从顶点i到顶点s的简单路径
27_001 |
void DFSearch(int v, int s, char *PATH) { //从第v个顶点出发递归地深度优先遍历图G //求得一条从v到s的简单路径,并记录在PATH中 visited[v] = TRUE; //访问第v个顶点 Append(PATH, getVertex(v)); for(w = FirstAdjVex(v); w != 0 && !found; w = NextAdjVex(v)) { if(w = s) { found = TRUE; Append(PATH, W); } else { if(! visited[w]) { DFSearch(w, LP); } } if(! found) { Delete(PATH); } } } |
2. 求两个顶点之间的一条路径长度最短的路径
基于广度优先搜索遍历,并修改链队列的结点结构及其入队列和出队列的算法。
1) 将链队列的结点改为“双链”结点,即结点中包含next和priou两个指针;
2) 修改入队列的操作,插入新的的队尾结点时,令其priou域的指针指向刚刚出队列的结点,即当前的对头指针所指结点;
3) 修改出队列的操作,出队列时,仅移动对头指针,而不将对头结点从链表中删除。
27_003 |
typedef DuLinkList QueuePtr; void InitQueue(LinkQueue &Q) { Q.front = Q.rear = (QueuePtr) malloc (sizeof(QNode)); Q.front->next = Q.rear->next = NULL; }
void EnQueue(LinkQueue &Q, QelemType e) { p = (QueuePtr) malloc(sizeof(QNode)); p->data = e; p->next = NULL; p->priou = Q.front; Q.rear->next = p; Q.rear = p; }
void DeQueue(LinkQueue &Q, QelemType &e) { Q.front = Q.front->next; e = Q.front->data; } |
7.4 最小生成树
问题:假设要在n个城市之间建立通讯网络,则连同n个城市只需要修n-1条线路,如何在最节省经费的前提下建立这个通讯网?
该问题等价于:构造网的一棵最小生成树,即:在e条带权的边中选取n-1条(不构成回路),使“权值之和“为最小。
算法一:(普里姆算法)
可取图中任意一个顶点v作为生成树的根,之后若要网生成树上添加顶点w,则在顶点v和顶点w之间必定存在一条边,并且该边的权值在所有连同顶点v和w之间的边中取最小值。
一般情况下,假设n个顶点分成两个集合:U(包含已落在生成树上的结点)和V-U(尚未落在生成树上的顶点),则在所有连通U中顶点和V-U中顶点的边中选取权值最小的边。
记录从顶点集U到V-U的代价最小的边的辅助数组:
27_004 |
struct { VertexType adjvex; VRTyped lowcost; }closedge[MAX_VERTEX_NUM]; k = LocateVex(G, u); for(j = 0; j < G.vexnum; ++ j) { //辅助数组初始化 if(j != k) { closedge[j] = (u, G.arcs[k][j].adj); } closeedge[k].lowcoset = 0; //初始,U-(u) for(j = 0; i < G.vexnum; ++ i) { //在其余顶点中选择 k = minimum(closedge); //求出T的一个小节点(k) printf(colsedge[k].adjvex, G.vexs[k]); closedge[k].lowcost = 0; //第k顶点并入U集 for(j = 0; j < G.vexnum; ++ j) { if(G.arcs[k][j].adj < closedge[j].lowcost) { closedge[j] = (G.vexs[k], G.arcs[k][j].adj); } } } } |
算法二:(克鲁斯卡尔算法)
为使生成树上边的权值之和最小,显然,其中每一台边的权值应该尽可能地小。克鲁斯卡尔算法的做法吉局势:先构造一个只含有n个顶点的子图SG,然后从权值最小的边喀什,若它的添加不使SG中产生回路,则在SG上加上这条边,如此重复,直至加上n-1条边为止。
算法:
构造非连通图ST=(V,{});k=i=0; while(k<n-1){++I; 从边集E中选取第i条权值最小的边(u,v);若(u,v)加入ST后不会使ST中产生回路,则输出边(u,v);且k++;}
由于普里姆算法的时间复杂度为O(n2),则适用于稠密图;而克鲁斯卡尔算法则需对e条边按权值进行排序,其时间复杂度为O(eloge),则适用域稀疏图。
7.5重(双)连通图和关节点
问题:若从一个连通图中删去任何一个顶点及其相关联的边,它仍为一个连通图的话,则该连通图被称为重(双)连通图。
若连通图中的某个顶点和其相关联的边被删去之后,该连通图被分割成两个或两个以上的连同分量,则称此顶点为关节点。
没有关节点的连通图为双连通图。
关节点的特征:
假设从某个顶点V0出发对连通图进行深度优先搜索遍历,则可得到一棵深度优先生成树,树上包含图的所有顶点。
若生成树的根结点,有两个或两个以上的分支,则此顶点(生成树的根)必为关节点;
对生成树上的任意一个“顶点”,若其某棵子树的跟或子树中的其它“顶点”没有和其祖先相通的回边,则该“顶点”必为关节点。
1) 设V0为深度优先遍历的出发点
28_001 |
p = G.vertices[0].firstarc; v = p->adjvex; DFSArticul(G, v); //从第v顶点出发深度优先搜素 if(count < G.vexnum) { //生成树的根至少有两棵子树 printf(0, G.vertices[0].data);//根是关节点 } |
2) 对生成树上的顶点定义一个函数
29_002 |
low(v)=Min{visited[v], low[w], visted[k]} |
对顶点v,若(在生成树上)存在一个子树根w,且low[w]>=visited[v]则顶点v为关节点。
对深度优先遍历算法作如下修改:
1. visited[v]的值改为遍历过程中顶点的访问次序count值
2. 遍历过程中求得low[v]=Min{visited[v],low[w],visited[k]}
3. 从子树遍历返回时,判别low[w]>=visited[v]?
29_003 |
min = visited[v0] = ++count; //设定low[v0]的初始值count计顶点访问次序 for(p = G.vertices[v0].firstarc; p; p=p->nextarc) { w = p->adjvex; //w为v0的邻接顶点 if(visited[w] == 0) { //w未曾访问 DFSArticul(G, w); //返回前求得low[w] if(low[w] < min) { min = low[w]; if(low[w] >= visited[v0]) { printf(v0, G.vertices[v0].data); //输出关节点 } } } else { //w是回边上的顶点 if(visited[w] < min) { min = visited[w]; } } low[v0] = min; } |