1.3k likes | 1.47k Views
数据结构( Java 语言版). —— 第 8 章 图. 第八阶段. 8. 图 【 知识要点 】 图的基本概念; 图的存储结构; 图的遍历; 生成树和最小生成树; 构造最小生成树的典型算法; 最短路径及其算法; 拓扑排序及其算法; AOE 网和关键路径。. 返回. 第八阶段. 8.1 实例引入 【 例 8.1】 北京及周边部分城市交通图。
E N D
数据结构(Java语言版) ——第8章 图
第八阶段 8.图 • 【知识要点】 • 图的基本概念; • 图的存储结构; • 图的遍历; • 生成树和最小生成树; • 构造最小生成树的典型算法; • 最短路径及其算法; • 拓扑排序及其算法; • AOE网和关键路径。
返回 第八阶段 8.1 实例引入 • 【例8.1】北京及周边部分城市交通图。 如图8.1所示为北京及周边地区交通示意图,从图中可看出,北京市、天津市、承德市、唐山市、保定市、沧州市、廊坊市、张家口市相互之间都可以连通,选取其中五个城市及部分线路,抽象出如图8.2所示的交通路线图,其对应关系为北京市(A)、承德市(B)、唐山市(C)、保定市(D)、张家口市(E)。
第八阶段 图8.1 北京及周边部分城市交通示意图
E B A C D 第八阶段 从图中可以看出,五个城市之间都有互相连通的道路,形成了一个多对多的关系,也称为图形关系,或者网状关系。 图8.2 北京周边地区交通模拟示意图
第八阶段 8.2 图的基本概念 • 8.2.1 图的定义 • 图(Graph)是一种网状数据结构,图是由结点(Vertices)集合V和边(Edges)集合E组成的。图中的结点又称为顶点。结点之间的关系称为边。图G的二元组定义如下: • G=(V,E) • 其中,V是结点的有限非空集合,E是边的有限集合。即: • V={u|u∈构成图的数据元素集合} • E={(u,v)|u,v∈V}或E={<u,v>| u,v∈V} • 其中,(u,v)表示结点u与结点v的一条无序偶,即(u,v)没有方向;而<u,v>表示从结点u到结点v的一条有序偶,即<u,v>是有方向的。
第八阶段 通常,图G的结点集合和边集合分别记为V(G)和E(G)。 E(G)可以是空集,此时图G只有结点没有边。 图的抽象数据类型定义如下: ADT Graph{ 数据对象V: V={vi|0≤i≤n-1,n≥0,vi∈某种数据结构} 数据关系E: E={(u,v)|u,v∈V}或E={<u,v>| u,v∈V}; 基本操作: getType() //返回当前图的类型 getVexNum() //返回图中结点数 getEdgeNum() //返回图中边数 getVertex() //返回图中所有结点的迭代器
第八阶段 • getEdge() //返回图中所有边的迭代器 • remove(v) //在图中删除特定的结点v • insert(e) //在图的边集中添加一条新边 • … • adjVertexs(u) //返回结点u的所有邻接点 • DFSTraverse(v) //从结点v开始深度优先搜索遍历图 • BFSTraverse(v) //从结点v开始广度优先搜索遍历图 • shortestPath(v) //求结点v到图中所有结点的最短路径 • generateMST() //求无向图的最小生成树有向图不支持此操作 • toplogicalSort() //求有向图的拓扑序列 • }ADT Graph • 对应于上述抽象数据类型,下面给出图的Java接口: • public interface Graph {
第八阶段 • 通常,图G的结点集合和边集合分别记为V(G)和E(G)。E(G)可以是空集,此时图G只有结点没有边。 • 图的抽象数据类型定义如下: • ADT Graph{ • 数据对象V: • V={vi|0≤i≤n-1,n≥0,vi∈某种数据结构} • 数据关系E: • E={(u,v)|u,v∈V}或E={<u,v>| u,v∈V}; • 基本操作: • getType() //返回当前图的类型 • getVexNum() //返回图中结点数 • getEdgeNum() //返回图中边数
第八阶段 • getVertex() //返回图中所有结点的迭代器 • getEdge() //返回图中所有边的迭代器 • remove(v) //在图中删除特定的结点v • insert(e) //在图的边集中添加一条新边 • … • adjVertexs(u) //返回结点u的所有邻接点 • DFSTraverse(v) //从结点v开始深度优先搜索遍历图 • BFSTraverse(v) //从结点v开始广度优先搜索遍历图 • shortestPath(v) //求结点v到图中所有结点的最短路径 • generateMST() //求无向图的最小生成树有向图不支持此操作 • toplogicalSort() //求有向图的拓扑序列 • }ADT Graph
第八阶段 • 对应于上述抽象数据类型,下面给出图的Java接口: • public interface Graph { • public static final int UndirectedGraph = 0; //无向图 • public static final int DirectedGraph = 1; //有向图 • public int getType(); //返回图的类型 • public int getVexNum(); //返回图的顶点数 • public int getEdgeNum(); //返回图的边数 • public Iterator getVertex(); //返回图的所有顶点 • public Iterator getEdge(); //返回图的所有边 • … • //返回从u出发可以直接到达的邻接顶点 • public Iterator adjVertexs(Vertex u);
第八阶段 • public Iterator DFSTraverse(Vertex v); • //对图进行深度优先遍历 • public Iterator BFSTraverse(Vertex v); • //对图进行广度优先遍历 • public Iterator shortestPath(Vertex v); • //求顶点v到其他顶点的最短路径 • //求无向图的最小生成树,如果是有向图不支持此操作 • public void generateMST() throws UnsupportedOperation; • //求有向图的拓扑序列 • public Iterator toplogicalSort() throws UnsupportedOperation; • }
第八阶段 • 其中UnsupportedOperation是调用图不支持的操作时抛出的异常,定义如下: • public class UnsupportedOperation extends RuntimeException { • public UnsupportedOperation(String err) { • super(err); • } • } • 8.2.2 图的相关概念 • 1.无向图(undirected graph) • 在一个图G中,如果两个结点之间构成的(u,v)∈E是无序偶,称该边是无向边。全部由无向边构成的图,称为无向图。 • 说明:用圆括号将一对结点括起来表示无向边,如(x,y)与(y,x)表示同一条边。如图8.3(a)所示,G1为无向图,G1的结点集合V和边集合E分别表示如下:
(c) 有向图G3 (b) 有向图G2 2 1 3 4 2 1 3 4 (a) 无向图G1 第八阶段 • V(G1) = {1, 2, 3, 4} • E(G1) = {(1,2),(1,3),(2,3),(2,4),(3,4)}(a) 无向图G112431243(b) 有向图G2(c) 有向图G3 图8.3 图结构
第八阶段 • 2.有向图(directed graph) • 在图中,如果两个结点之间构成的<u,v>∈E是有序偶,称为该边为有向边,也称为弧(arc)。如<u,v>,u称为边的起点(initial node)或弧尾,v称为边的终点(terminal node)或弧头。全部由有向边组成的图,称为有向图。如图8.3(b)和(c)所示,G2和G3都是有向图。G2和G3的结点集合V和边集合E可分别表示为: • V(G2) = {1, 2, 3, 4} • E(G2) = {<1,2>,<1,3>,<1,4>,<2,4>,<3,4>} • V(G3) = {1, 2, 3} • E(G3) = {<1,2>,<2,1>,<2,3>,<3,3>} • 其中,G3中的<3,3>为自身环。 • 3.完全图(complete graph) • 具有n个结点的无向图G中,其边的最大数目为n×(n-1)/2,当边数为最大值时,则称图G为无向完全图。 • 具有n个结点的有向图G中,其边的最大数目为n×(n-1),当有向图G的边数为最大值时,则称图G为有向完全图。
第八阶段 • 4.稀疏图(sparse graph)和稠密图(dense graph) • 当一个图接近完全图时,则称该图为稠密图;相反地,当一个图含有较少的边数时,则称该图为稀疏图。 • 5.子图(subgraph) • 设有两个图G=(V,E)和G'=(V',E'),如果V'是V的子集,即V'V,而且E'是E的子集即E'E,则称G'为G的子图。即子图就是图G中的部分集合。例如,图8.3中无向图G1和有向图G2的部分子图,如图8.4所示。
第八阶段 2 2 1 1 1 1 3 3 4 3 2 2 1 1 1 1 3 3 4 4 (a) G1的部分子图 (b) G2的部分子图 图8.4 子图示例
第八阶段 • 如果G'为G的子图,且V'=V,称G'为G的生成子图(spanning subgraph),即V'=V,且E'E。 • 6.权(weight)和网(network) • 在一个图中,每条边可以标上具有某种含义的数值,此数值称为该边上的权,通常权是一个非负实数。权可以表示从一个结点到另一个结点的距离、花费的代价或时间等含义。边上标有权的图称为网,也称为带权图(weighted graph),如图8.5所示。
第八阶段 10 b a 2 5 f 8 6 c 12 e d 2 (a) 带权的无向图G4 (b) 带权的有向图G5 图8.5 带权图
第八阶段 • 7.邻接点(adjacent) • 在一个无向图中,若存在一条边(vi,vj),则称结点vi,vj互为邻接点。边(vi,vj)是结点vi和vj相关联的边,结点vi和vj是边(vi,vj)相关联的结点。 • 在一个有向图中,若存在一条边<vi,vj>,则称结点vi,vj互为邻接点。边(vi,vj)是结点vi和vj相关联的边,结点vi和vj是边<vi,vj>相关联的结点。 • 8.结点的度 • 结点的度(degree)是图中与结点v相关联边的数目,记为D(v)。例如,图8.3(a)所示G1中,结点1的度为2,记为D(v1)=2。度为1的结点称为悬挂点(pendant nodes)。 • 在有向图中,结点v的度有入度和出度之分,以v为终点的弧的数目称为入度(in degree),记为ID(v);以v为起点的弧的数目称为出度(out degree),记为OD(v)。出度为0的结点称为终端结点或叶子结点。结点的度等于它的入度和出度之和,即: • D(v)=ID(v)+OD(v)
第八阶段 • 例如,在图8.5(b)所示的G5中,结点5的入度ID(v5)=1,出度OD(v5)=2,度为D(v5)=3。 • 如果一个图中有n个结点和e条边,则该图所有顶点的度D(vi)与边数e满足如下关系: • 该式表示度与边的关系。每条边连接着两个结点,所以全部结点的度数为所有边数的2倍。 • 9.路径(path)与回路(cycle) • 在一个图G中,路径是从结点u(u∈V(G))到结点v(v∈V(G))所经过的结点序列,路径长度是指该路径上边的数目。如果一条路径上,序列中所有结点均不同,则称该路径为简单路径。如果一条路径上,起点和终点两个结点相同,则该路径被称为回路或环。除了第一个结点和最后一个结点相同,其余结点不重复出现的回路称为简单回路或简单环。 • 例如图8.6所示,从结点v1到结点v5的路径为<v1,v2>,<v2,v3>,<v3,v5>,缩写简记为{v1,v2,v3,v5},路径的长度为3,而且该路径属于简单路径。
v2 v1 v6 v4 v3 v5 第八阶段 • {v1,v2,v4,v1,v2,v3,v5}不是简单路径,因为在这条路径中结点v1和结点v2重复出现。{v1,v2, v4,v1}就是一条简单回路,路径长度为3。 图8.6 有向图G6
第八阶段 • 另外,在带权图中,从起点到终点的路径上各条边上的权值之和,称为该路径长度。例如,图8.5(b)中带权图G5,从结点1到结点5的一条路径{v1,v2,v3,v5}的路径长度为2+9+8=19。 • 10.连通(connected)、连通图(connected graph)和连通分量(connected component) • 在无向图G中,如果从结点vi到结点vj有路径,则称vi与vj是连通的。若图G中,任意两个不同的结点都连通,则称为G为连通图,否则,称为非连通图。无向图G的极大连通子图,称为图G的连通分量。显然,任何连通图的连通分量只有一个,即本身。而非连通图可能有多个连通分量。如图8.5(a)所示,两个连通分量C1和C2。 • 11.强连通图(strongly connected graph)和强连通分量 • 在有向图G中,如果任意两个结点vi和vj,从结点vi到结点vj有路径,同时,从结点vj到结点vi也有路径,则称图G是强连通图。有向图G中的极大强连通子图称为图G的强连通分量。显然,强连通图只有一个强连通分量,即本身。非强连通图可能有多个强连通分量。如图8.7(b)所示,为强连通有向图。
第八阶段 1 v1 v2 n 2 v3 v2 v4 (C1) (C2) n-1 3 (a)两个连通分量 (b) 强连通有向图 图8.7 图的连通性
第八阶段 • 8.3 图的存储结构 • 8.3.1 邻接矩阵(adjacentcy matrix) • 图的邻接矩阵是表示结点之间相邻关系的矩阵。设图G=(V,E)具有n(n≥1)个结点,结点的顺序依次为{v0,v1,…,vn-1},则图G的邻接矩阵A是一个n阶方阵,定义如下: • 例如图8.8所示,无向图G7和有向图G8,对应的邻接矩阵分别为A1和A2如下所示:
第八阶段 v2 v2 v1 v1 v3 v3 v4 v4 (a) 无向图G7 (b) 有向图G8 图8.8 无向图G7和有向图G8
第八阶段 • 从邻接矩阵A1和A2不难看出,无向图的邻接矩阵是对称矩阵;有向图的邻接矩阵不一定对称。 • 存储邻接矩阵表示一个有n个结点的图,需要n2个存储单元。不带权的有向图的邻接矩阵一般来说是一个稀疏矩阵,当图的结点较多时,可以采用三元组表的方法存储邻接矩阵。 • 对于一个带权图G,设wij代表边(vi,vj)或<vi,vj>上的权值,则图G的邻接矩阵A定义如下:
第八阶段 • 例如,如图8.5所示,带权图G4和G5,对应的邻接矩阵分别为A3和A4如下所示。
第八阶段 • 用邻接矩阵表示图,很容易判断任意两个结点之间是否有边,并容易求出各个结点的度。对于无向图,邻接矩阵的第i行或第i列的非零元素正好是第i个结点vi的度;对于有向图,邻接矩阵的第i行的非零元素的个数正好是第i个结点vi的出度;第i列的非零元素的个数正好是第i个结点vi的入度。 • 对于一个具有n个结点的图G来说,可以将图G的邻接矩阵存储在一个二维数组matrix1中,声明一个MGraph1类,表示如下: • public class MGraph1{ //使用邻接矩阵存储图类 • protected int n; //图的结点个数 • protected int matrix1[][]; //利用二维数组存储图的邻接矩阵 • } • 图的邻接矩阵表示是惟一的。用邻接矩阵存储图,虽然能很好的确定图中的任意两个结点之间是否有边,但是,要确定图中有多少条边,则必须按行、按列对每个数据元素进行检测,花费的时间代价较大。不论是求任意一个结点的度,还是查找任意一结点的邻接点,都需要访问对应的一行或一列中的所有元素,其时间复杂度为O(n),n为邻接矩阵的阶数。对于结点为n的图来说,从空间上看,不论图中的结点之间是否有边,都要在邻接矩阵中预留存储空间,其空间复杂度为O(n2)。空间效率较低。这也是邻接矩阵存储图的局限性。
第八阶段 • 8.3.2 邻接表(adjacentcy list) • 邻接表是图的一种链式存储方法,邻接表表示类似于树的孩子链表表示。在邻接表中,对于图G中的每个结点vi建立一个单链表,第i个单链表中的结点表示依附于结点vi的边(对于有向图就是以结点vi为尾的弧),即将所有邻接于结点vi的结点链成一个单链表,并在表头附设一个表头结点,这个单链表就称为结点vi的邻接表。 • 邻接表包括两部分:表头结点和表结点,其结点结构如下:
第八阶段 表头结点 表头结点 表结点 表结点 data data firstarc firstarc adjvex adjvex nextarc nextarc info info
第八阶段 • 表头结点包括:data和firstarc两个成员。data表示结点数据元素的信息;firstarc表示指向链表中的第1个结点。 • 表结点包括:adjvex、nextarc和info三个成员。adjvex指示于结点vi邻接的点在图中的位置;nextarc指示下一条边或弧的结点;info存储与边相关的信息,如权值等。图中的每个结点用表结点表示,表结点中都对应于该结点相关联的一条边。 • 例如,图8.8中的无向图G7、有向图G8和图8.5中的带权有向图G5对应的邻接表分别如图8.9(a)、(b)和(c)所示:
第八阶段 0 0 v1 1 3 0 3 3 0 3 2 1 2 v1 1 2 3 0 v1 0 ∧ ∧ ∧ ∧ ∧ ∧ ∧ ∧ 1 1 v2 v2 2 2 v3 v3 3 3 v4 v4 (a)无向图G7对应的邻接表 0 1 2 3 2 ∧ 1 v2 0 4 2 9 ∧ 2 v3 4 8 ∧ 3 v4 2 7 ∧ 4 v5 2 5 3 1 ∧ (c)带权有向图G5对应的邻接表 (b)无向图G8对应的邻接表 图8.9 三个邻接表
第八阶段 • 邻接表具有以下特点: • (1)邻接表的表示不是惟一的。因为在每个结点的邻接表中,各边结点的链接次序可以随意安排,取决与建立邻接表的算法及与边的输入次序。 • (2)在无向图的邻接表中,结点vi的度恰为该结点的邻接表中边结点的个数;而在有向图中,结点vi的邻接表中边结点的个数仅为该结点的出度。有向图中结点的入度,可以通过建立一个有向图的逆邻接表得出。 • (3)对于n个结点和e条边的无向图,其邻接表有n个结点和2e个边结点。显然,在边数小于n×(n-1)/2时,邻接表比邻接矩阵节省存储空间。
第八阶段 • 8.4 图的遍历 • .4.1 深度优先搜索遍历(DFS) • 深度优先搜索(depth first search)遍历类似于树的先根遍历,是树先根遍历的推广。 • 1.算法描述 • 从图中的某个结点v开始访问,访问它的任意一个邻接结点w1;再从w1出发,访问与w1邻接但还没有被访问过的结点w2;然后再从w2出发,进行类似的访问,……如此进行下去,直至所有的邻接结点都被访问过为止。接着,退回一步,退到前一次刚访问过的结点,看是否还有其它没有被访问的邻接结点。如果有,则访问此结点,之后再从此结点出发,进行与前述类似的访问。重复上述过程,直到连通图中所有结点都被访问过为止。 • 遍历的过程是一个递归的过程。 • 例如,图8.10(a)无向图G9进行深度优先搜索遍历的过程,如图8.10(b)所示。
第八阶段 v1 v1 v2 v3 v2 v3 v4 v5 v6 v7 v4 v5 v6 v7 v8 v8 (b) 图G9的深度优先搜索遍历过程 (a) 无向图G9 图8.10 深度优先搜索遍历的过程
第八阶段 • 假定v1是出发点,首先访问v1。因v1有两个邻接点v2、v3均未被访问过,选择访问结点v2,再找v2的未被访问过的邻接点v4、v5,选择访问结点v4。重复上述搜索过程,依次访问结点v8、v5。v5被访问过后,由于与v5相邻的顶点均已被访问过,搜索退回到v8。v8的邻接点v4、v5也被访问过;同理,依次退回结点v4、v2,最后退回到结点v1。这时选择结点v1的未被访问过的邻接点v3,继续搜索,依次访问结点v3、v6、v7,从而遍历图中全部结点。这就是深度优先搜索遍历的整个过程,得到的结点的深度遍历序列为: • {v1,v2,v4,v8,v5,v3,v6,v7} • 图的深度优先搜索遍历的过程是递归的。深度优先搜索遍历图所得的结点序列,定义为图的深度优先遍历序列,简称DFS序列。一个图的DFS序列不一定是惟一的。
第八阶段 • 2.图的深度优先搜索算法实现 • 从某个结点v出发的深度优先搜索过程是一个递归的搜索过程,因此可简单的使用递归算法实现。在遍历的过程中,必须对访问过的结点做标记,避免同一结点被多次访问。深度优先搜索算法的具体实现如下: • 算法8.1:图的非递归深度优先搜索算法和递归深度优先搜索算法 • //对图进行深度优先遍历 • public Iterator DFSTraverse(Vertex v) { • LinkedList traverseSeq = new LinkedListDLNode(); • resetVexStatus(); //重置结点状态 • DFS(v, traverseSeq); //从v点出发深度优先搜索 • Iterator it = getVertex(); //从图中未曾访问的其他结点出发重新搜索
第八阶段 • for(it.first(); !it.isDone(); it.next()){ • Vertex u = (Vertex)it.currentItem(); • if (!u.isVisited()) DFS(u, traverseSeq); • } • return traverseSeq.elements(); • } • //深度优先的递归算法 • private void DFSRecursion(Vertex v, LinkedList list){ • v.setToVisited(); • list.insertLast(v); • Iterator it = adjVertexs(v); //取得结点v的所有邻接点 • for(it.first(); !it.isDone(); it.next()){ • Vertex u = (Vertex)it.currentItem(); • if (!u.isVisited()) DFSRecursion(u,list); • } • }
第八阶段 • 在算法中对图进行深度优先搜索遍历时,对图中每个结点最多调用一次DFSRecursion方法,因为某个结点一旦被访问,就不再从该结点出发进行搜索。因此,遍历图的过程实际就是查找每个结点的邻接点的过程。设图G有n个结点和e条边(e≥n),当存储结构采用邻接矩阵存储时,需要扫描邻接矩阵中的每一个结点,其时间复杂度为O(n);当存储结构采用邻接表时,需要扫描邻接表中的每个边结点,所以其时间复杂度为O(e);两者的空间复杂度都为O (n)。 • 【例8.2】无向图的深度优先搜索遍历示例。 • 分析:根据深度优先搜索遍历的算法,从某个开始访问,可以用如下步骤实现: • (1)对于某个结点如果可能,则访问该结点未被访问的其中一个邻结点,输出该结点,并把该结点放入栈中,给于标记。 • (2)当(1)不能执行而且栈不为空时,从栈中弹出一个结点。 • (3)如果(1)和(2)都不能执行,就完成了图的深度优先搜索遍历。
第八阶段 • 参考程序如下: • import java.util.Stack; • class Vertex{ • public char label; • public boolean wasVisited; • public Vertex(char lab){ • label = lab; • wasVisited = false; • } • } • class Graph{ • private final int MAX_VERTS = 20; • private Vertex vertexList[]; //邻接结点 • private int adjMat[][]; //邻接矩阵 • private int nVerts; //结点数 • private Stack<Integer> theStack;
第八阶段 • public Graph(){ • vertexList = new Vertex[MAX_VERTS]; • adjMat = new int[MAX_VERTS][MAX_VERTS]; //邻接矩阵 • nVerts = 0; • for(int y=0; y<MAX_VERTS; y++) • for(int x=0; x<MAX_VERTS; x++) • adjMat[x][y] = 0; • theStack = new Stack<Integer>(); • } • public void addVertex(char lab) { • vertexList[nVerts++] = new Vertex(lab); • } • public void addEdge(int start, int end) {
第八阶段 • adjMat[start][end] = 1; • adjMat[end][start] = 1; • } • public void displayVertex(int v){ • System.out.print(vertexList[v].label); • } • public void DFS(){ //深度优先搜索 • vertexList[0].wasVisited = true; //标记 • displayVertex(0); //输出 • theStack.push(0); //压栈 • while( !theStack.isEmpty() ) { //栈不为空 • int v = getAdjUnvisitedVertex(theStack.peek()); • if(v == -1) • theStack.pop(); • else{ • vertexList[v].wasVisited = true; • displayVertex(v); • theStack.push(v); • } • } // 栈为空,则搜索完毕
第八阶段 • for(int j=0; j<nVerts; j++) • vertexList[j].wasVisited = false; • } • public int getAdjUnvisitedVertex(int v){ • for(int j=0; j<nVerts; j++) • if(adjMat[v][j]==1 && vertexList[j].wasVisited==false) • return j; • return -1; • } • } • class DFSApp { • public static void main(String[] args){ • Graph theGraph = new Graph(); • theGraph.addVertex('A'); // 0
第八阶段 • theGraph.addVertex('B'); // 1 • theGraph.addVertex('C'); // 2 • theGraph.addVertex('D'); // 3 • theGraph.addVertex('E'); // 4 • theGraph.addEdge(0, 1); // AB • theGraph.addEdge(1, 2); // BC • theGraph.addEdge(0, 3); // AD • theGraph.addEdge(3, 4); // DE • System.out.print("遍历的结果: "); • theGraph.DFS(); • System.out.println(); • } • } • 程序运行的结果为: • 遍历的结果: ABCDE
第八阶段 • 8.4.2 广度优先搜索遍历(BFS) • 广度优先搜索(breadth first search)遍历类似于树的层次遍历,是树的层次遍历的推广。 • 从图中的某个结点v开始访问,依次访问结点v的各个未被访问过的邻接结点w1,w2,……然后依次顺序访问结点w1,w2,……的各个还未被访问过的邻接结点。再从这些访问过的结点出发,依次访问它们的所有还未被访问过的邻接结点,……重复上述过程,直到图中所有结点都被访问过为止。也就是说:广度优先搜索遍历图的过程是一个以v为起始点,由近及远,依次访问和v有路径相通且路径长度为1,2,3……的结点,且遵循先被访问的结点,其邻接结点就先被访问。
第八阶段 • 广度优先搜索是一种分层的搜索过程,每向前走一步可能访问一批结点,不像深度优先搜索那样有回退的情况。因此,广度优先搜索不是一个递归的过程。 • 在广度优先搜索遍历中,需要使用队列,依次记住被访问过的结点。 • 算法开始时,访问初始结点v后,并插入队列中,以后每从队列中删除一个元素,就依次访问它的每一个未被访问过的邻接结点,并令其进入队列。这样,当队列为空时,表明所有与起点相通的结点都已被访问完毕,算法结束。 • 例如,图8.10(a)无向图G9从v1出发进行广度优先搜索遍历的过程。 • 首先,访问起点v1。v1有两个未曾访问的邻接结点v2和v3。先访问v2,再访问v3。然后,再访问v2的未曾访问过的邻接结点v4、v5及v3的未曾访问过的邻接结点v6和v7,最后访问v4的未曾访问过的邻接结点v8。至此图中所有顶点均已被访问过。得到的顶点访问序列为: • {v1,v2,v3,v4,v5,v6,v7,v8}
v1 第八阶段 • 其队列操作过程如下: • (1)首先,从结点v1开始广度优先搜索,将结点v1存入队列中。
v2 v3 第八阶段 • (2)将结点v1从队列中取出,将结点v1的邻接结点v2和v3依次存入队列中。
v3 v4 v5 第八阶段 • (3)将结点v2从队列中取出,然后,按照先访问v2的未曾被访问过的邻接点,再访问v3未曾被访问过的邻接点的次序。即取出结点v2,将其邻接结点v4和v5依次存入队列中。