图的遍历
图的遍历主要有两种方法:广度遍历和深度遍历,它们也叫做广度优先搜索和深度优先搜索。由遍历所经过的路径可以形成一个树,分别叫做广度优先搜索生成树和深度优先搜索生成树。
深度优先搜索
深度优先搜索如树的先根遍历类似,如下图1:
图1
其搜索过程,如图2所示:
图1
假设从顶点v1 出发进行搜索,在访问了顶点v1 之后,选择邻接点v2。因为v2 未曾访问,则从v2 出发进行搜索。依次类推,接着从v4 、v8 、v5 出发进行搜索。在访问了v5 之后,由于v5 的邻接点都已被访问,则搜索回到v8。由于同样的理由,搜索继续回到v4,v2 直至v1,此时由于v1 的另一个邻接点未被访问,则搜索又从v1 到v3,再继续进行下去由此,得到的顶点访问序列为:
。
从上面的搜索过程,我们可以看出深度优先搜索是一个递归的过程。
邻接矩阵存储图的深度优先搜索算法

//邻接矩阵 struct MGraph{ int edges[MAX_VERTEX_NUM][MAX_VERTEX_NUM]; int n; int e; }; void MDfs(const MGraph& mGraph,int v0) { cout << v0 << ' '; visited[v0] = true; for (int i = 1; i <= mGraph.n; i++) { if (visited[i] == false && mGraph.edges[v0][i] != -1) { MDfs(mGraph, i); } } } int main() { int n, m; int v1, v2,c; MGraph mG; cin >> n >> m; //初始化 for (int i = 1; i < MAX_VERTEX_NUM; i++) visited[i] = false; for (int i = 1; i < MAX_VERTEX_NUM; i++) for (int j = 1; j < MAX_VERTEX_NUM; j++) mG.edges[i][j] = -1; mG.n = n; mG.e = m; for (int i = 1; i <= m; i++) { cin >> v1 >> v2 >> c; mG.edges[v1][v2] = mG.edges[v2][v1] = c; } MDfs(mG, 1); }
测试
邻接链表存储图的优先搜索算法

bool visited[MAX_VERTEX_NUM]; //邻接链表 struct ArcNode{ int vertex; int len; int cost; ArcNode *nextarc; }; typedef struct VertexNode{ ArcNode *firstArc; int size; }VNode[100]; //分配边结点空间 void allocateArcNode(ArcNode **arcNode) { *arcNode = (ArcNode *)malloc(sizeof(ArcNode)); (*arcNode)->nextarc = NULL; } //头插法 void insertArcNode(LGraph &lGraph, ArcNode *arcNode,int v) { arcNode->nextarc = lGraph.nodes[v].firstArc; lGraph.nodes[v].firstArc = arcNode; } //图的初始化 void initeGraph(LGraph &lGraph) { for (int i = 0; i < MAX_VERTEX_NUM; i++) lGraph.nodes[i].firstArc = NULL; lGraph.n = 0; lGraph.e = 0; } void LDfs(const LGraph& lGraph, int v0) { cout << v0 << ' '; visited[v0] = true; ArcNode* arcptr = lGraph.nodes[v0].firstArc; while (arcptr != NULL) { int v = arcptr->vertex; if (!visited[v]) LDfs(lGraph, v); arcptr = arcptr->nextarc; } } void freeLGraph(LGraph &lGraph) { int size = lGraph.n; ArcNode *arcPtr = NULL; for (int i = 1; i <= size; i++) { arcPtr = lGraph.nodes[i].firstArc; while (arcPtr != NULL) { ArcNode *t = arcPtr; arcPtr = arcPtr->nextarc; delete t; } } } void main() { int n, m; int v1, v2,c; LGraph lG; initeGraph(lG); cin >> n >> m; lG.n = n; lG.e = m; for (int i = 1; i < MAX_VERTEX_NUM; i++) visited[i] = false; ArcNode *arc1 = NULL; ArcNode *arc2 = NULL; for (int i = 1; i <= m; i++) { cin >> v1 >> v2 >> c; allocateArcNode(&arc1); allocateArcNode(&arc2); arc1->cost = c; arc1->vertex = v2; insertArcNode(lG, arc1, v1); arc2->cost = c; arc2->vertex = v1; insertArcNode(lG, arc2, v2); } LDfs(lG, 1); freeLGraph(lG); }
测试
算法分析
分析上述算法,在遍历时,对图中每个顶点至多调用一次DFS 函数,因为一旦某个顶点被标志成已被访问,就不再从它出发进行搜索。因此,遍历图的过程实质上是对每个顶点查找其邻接点的过程。其耗费的时间则取决于所采用的存储结构。当用二维数组表示邻接矩阵图的存储结构时,查找每个顶点的邻接点所需时间为O(n2) ,其中n 为图中顶点数。而当以邻接表作图的存储结构时,找邻接点所需时间为O(e),其中e 为无向图中边的数或有向图中弧的数。由此,当以邻接表作存储结构时,深度优先搜索遍历图的时间复杂度为O(n+e) 。
广度优先搜索
广度优先搜索类似与树的层次遍历,如上图1显示的无向图。广度搜索过程如下图3所示:
图3
首先访问v1 和v1 的邻接点v2 和v3,然后依次访问v2 的邻接点v4 和v5 及v3 的邻接点v6 和v7,最后访问v4 的邻接点v8。由于这些顶点的邻接点均已被访问,并且图中所有顶点都被访问,由些完成了图的遍历。得到的顶点访问序列为:v1→v2 →v3 →v4→ v5→ v6→ v7 →v8。
与深度优先搜索类似,在遍历的过程中也需要一个访问标志数组。并且,为了顺次访问路径长度为2、3、…的顶点,需附设队列以存储已被访问的路径长度为1、2、… 的顶点。
同样的给出两种存储结构的广度优先搜索算法。
邻接矩阵存储图的广度优先搜索算法

bool visited[MAX_VERTEX_NUM]; struct Quenue{ int vertexs[MAX_VERTEX_NUM]; int front; int rear; }; Quenue quenue; quenue.front = quenue.rear = 0; void MBfs(const MGraph& mGrpah, int v0) { quenue.vertexs[quenue.rear++] = v0; visited[v0] = true; while (quenue.front != quenue.rear) { int v = quenue.vertexs[quenue.front]; cout << v << ' '; quenue.front++; for (int i = 1; i <= mGrpah.n; i++) { if (visited[i] == false && mGrpah.edges[v][i] != -1) { quenue.vertexs[quenue.rear++] = i; visited[i] = true; } } } }
邻接表存储图的广度优先搜索算法

void LBfs(const LGraph& lGraph, int v0) { quenue.vertexs[quenue.rear++] = v0; visited[v0] = true; while (quenue.front != quenue.rear) { int v = quenue.vertexs[quenue.front]; cout << v << ' '; quenue.front++; ArcNode*arcPtr = lGraph.nodes[v0].firstArc; while (arcPtr != NULL) { if (visited[arcPtr->vertex] == false) { quenue.vertexs[quenue.rear++] = arcPtr->vertex; visited[arcPtr->vertex] = true; } } } }
两种算法的测试输出如下
算法分析
每个顶点至多进一次队列。遍历图的过程实质是通过边或弧找邻接点的过程,因此广度优先搜索遍历图的时间复杂度和深度优先搜索遍历相同,两者不同之处仅仅在于对顶点访问的顺序不同。