圖的遍歷(深度優先和廣度優先)
阿新 • • 發佈:2021-06-19
圖的遍歷
定義
遍歷(Traversing Graph):從圖中某點出發訪問各頂點,每個頂點僅被訪問一次(有且僅有一次)。
深度優先遍歷(Depth First Search):也稱深度優先搜尋,簡稱DFS。從圖中某個頂點v出發做深度優先搜尋,訪問頂點v,然後從v的未被訪問的鄰接頂點出發做深度優先搜尋,直到圖中所有和v有路徑相通的頂點都被訪問到。明顯,這是個遞迴的過程。
廣度優先遍歷(Breadth First Search):也稱廣度優先搜尋,簡稱BFS。從圖中某個頂點v出發做廣度優先搜尋,先快取頂點v,從快取中取出頂點v並訪問,然後快取v的未被訪問的鄰接頂點(可理解為v的下層頂點),從快取中取出頂點,再訪問,直到圖中所有和v有路徑相通的頂點都被訪問到。
複雜度分析
從下面的程式碼可以得出,對於n個頂點e條邊的圖來說,鄰接矩陣表示的圖由於是二維陣列,所以遍歷二維陣列需要O(n2)的時間;對於鄰接表表示的圖,找鄰接點所需的時間取決於頂點和邊的數量,所以遍歷鄰接表表示的圖的時間複雜度是O(n+e)的時間。
程式碼
以下圖中的圖為例,採用不同的遍歷方式遍歷不同儲存結構的圖。
C#程式碼
using System; using System.Collections.Generic; namespace GraphDfs { class Program { static void Main(string[] args) { // 建立圖(鄰接矩陣表示法) int self = GraphAjacencyMatrix.SelfToSelf, noedge = GraphAjacencyMatrix.NoEdge; AjacentVertex[] vertexes = new AjacentVertex[] { new AjacentVertex(){ Data = "A" }, // 0 new AjacentVertex(){ Data = "B" }, // 1 new AjacentVertex(){ Data = "C" }, // 2 new AjacentVertex(){ Data = "D" }, // 3 new AjacentVertex(){ Data = "E" }, // 4 new AjacentVertex(){ Data = "F" }, // 5 new AjacentVertex(){ Data = "G" }, // 6 new AjacentVertex(){ Data = "H" }, // 7 new AjacentVertex(){ Data = "I" }, // 8 }; int[][] edges = new int[][] { new int[]{ self, 1, noedge, noedge, noedge, 1, noedge, noedge, noedge }, // A new int[]{ 1, self, 1, noedge, noedge, noedge, 1, noedge, 1 }, // B new int[]{ noedge, 1, self, 1, noedge, noedge, noedge, noedge, 1 }, // C new int[]{ noedge, noedge, 1, self, 1, noedge, 1, 1, 1 }, // D new int[]{ noedge, noedge, noedge, 1, self, 1, noedge, 1, noedge }, // E new int[]{ 1, noedge, noedge, noedge, 1, self, 1, noedge, noedge }, // F new int[]{ noedge, 1, noedge, 1, noedge, 1, self, 1, noedge }, // G new int[]{ noedge, noedge, noedge, 1, 1, noedge, 1, self, noedge }, // H new int[]{ noedge, 1, 1, 1, noedge, noedge, noedge, noedge, self }, // I }; GraphAjacencyMatrix graph = new GraphAjacencyMatrix(vertexes, edges); // 建立圖(鄰接表表示法) Vertex[] vertexesAjacencyList = new Vertex[] { new Vertex("A", new Edge[]{ new Edge(1, "<A, B>"), new Edge(5, "<A, F>") }), // 0 new Vertex("B", new Edge[]{ new Edge(0, "<B, A>"), new Edge(2, "<B, C>"), new Edge(6, "<B, G>"), new Edge(8, "<B, I>")}), // 1 new Vertex("C", new Edge[]{ new Edge(1, "<C, B>"), new Edge(3, "<C, D>"), new Edge(8, "<C, I>")}), // 2 new Vertex("D", new Edge[]{ new Edge(2, "<D, C>"), new Edge(4, "<D, E>"), new Edge(6, "<D, G>"), new Edge(7, "<D, H>"), new Edge(8, "<D, I>")}), // 3 new Vertex("E", new Edge[]{ new Edge(3, "<E, D>"), new Edge(5, "<E, F>"), new Edge(7, "<E, H>")}), // 4 new Vertex("F", new Edge[]{ new Edge(0, "<F, A>"), new Edge(4, "<F, E>"), new Edge(6, "<F, G>")}), // 5 new Vertex("G", new Edge[]{ new Edge(1, "<G, B>"), new Edge(3, "<G, D>"), new Edge(5, "<G, F>"), new Edge(7, "<G, H>")}), // 6 new Vertex("H", new Edge[]{ new Edge(3, "<H, D>"), new Edge(4, "<H, E>"), new Edge(6, "<H, G>"),}), // 7 new Vertex("I", new Edge[]{ new Edge(1, "<I, B>"), new Edge(2, "<I, C>"), new Edge(3, "<I, D>"),}), // 8 }; GraphAjacencyList graphAjacencyList = new GraphAjacencyList(vertexesAjacencyList); Console.WriteLine("圖的深度優先搜尋(鄰接矩陣表示法):"); Dfs1(graph); /** 執行結果: 圖的深度優先搜尋(鄰接矩陣表示法): V0 = A V1 = B V2 = C V3 = D V4 = E V7 = H V6 = G V8 = I V5 = F */ Console.WriteLine("圖的深度優先搜尋(鄰接表表示法):"); Dfs2(graphAjacencyList); /** 執行結果: 圖的深度優先搜尋(鄰接表表示法): V0 = A V1 = B V2 = C V3 = D V4 = E V7 = H V6 = G V8 = I V5 = F */ Console.WriteLine("圖的廣度優先搜尋(鄰接矩陣表示法):"); Bfs1(graph); /** 執行結果: 圖的廣度優先搜尋(鄰接矩陣表示法): V0 = A V1 = B V5 = F V2 = C V6 = G V8 = I V4 = E V3 = D V7 = H */ Console.WriteLine("圖的廣度優先搜尋(鄰接表表示法):"); Bfs2(graphAjacencyList); /** 執行結果: 圖的廣度優先搜尋(鄰接表表示法): V0 = A V5 = F V1 = B V6 = G V4 = E V8 = I V2 = C V7 = H V3 = D */ } /// <summary> /// 圖的深度優先搜尋(Depth-First-Search)演算法(非遞迴)。 /// 圖用鄰接矩陣表示。 /// </summary> /// <param name="g">用鄰接矩陣表示的圖。</param> public static void Dfs1(GraphAjacencyMatrix g) { bool[] isDiscovered = new bool[g.NumberOfVertex]; for (int i = 0; i < isDiscovered.Length; i++) { isDiscovered[i] = false; } Stack<int> s = new Stack<int>(); for (int i = 0; i < g.NumberOfVertex; i++) { if (isDiscovered[i] == false) { s.Push(i); isDiscovered[i] = true; } while (s.Count != 0) { int v = s.Pop(); // visit node operation Console.WriteLine($"V{v} = {g.Vertexes[v].Data}"); for (int j = g.NumberOfVertex - 1; j >= 0; j--) //for (int j = 0; j < g.NumberOfVertex; j++) { if (g.Edges[v][j] != GraphAjacencyMatrix.SelfToSelf && g.Edges[v][j] != GraphAjacencyMatrix.NoEdge && isDiscovered[j] == false) { s.Push(j); isDiscovered[j] = true; } } } } } /// <summary> /// 圖的深度優先搜尋(Depth-First-Search)演算法(非遞迴)。 /// 圖用鄰接表表示。 /// </summary> /// <param name="g">用鄰接矩陣表示的圖。</param> public static void Dfs2(GraphAjacencyList g) { bool[] isDiscovered = new bool[g.NumberOfVertex]; for (int i = 0; i < isDiscovered.Length; i++) { isDiscovered[i] = false; } Stack<int> s = new Stack<int>(); for (int i = 0; i < g.NumberOfVertex; i++) { if (isDiscovered[i] == false) { s.Push(i); isDiscovered[i] = true; } while (s.Count != 0) { int v = s.Pop(); // visit node operation Console.WriteLine($"V{v} = {g.Vertexes[v].Data}"); Edge e = g.Vertexes[v].Edge; while (e != null) { int j = e.HeadVertex; if (isDiscovered[j] == false) { s.Push(j); isDiscovered[j] = true; } e = e.Next; } } } } /// <summary> /// 圖的廣度優先搜尋(Depth-First-Search)演算法。 /// 圖用鄰接矩陣表示。 /// </summary> /// <param name="g">用鄰接矩陣表示的圖。</param> public static void Bfs1(GraphAjacencyMatrix g) { bool[] isDiscovered = new bool[g.NumberOfVertex]; for (int i = 0; i < isDiscovered.Length; i++) { isDiscovered[i] = false; } Queue<int> q = new Queue<int>(); for (int i = 0; i < g.NumberOfVertex; i++) { if (isDiscovered[i] == false) { q.Enqueue(i); isDiscovered[i] = true; } while (q.Count != 0) { int v = q.Dequeue(); // visit node operation Console.WriteLine($"V{v} = {g.Vertexes[v].Data}"); for (int j = 0; j < g.NumberOfVertex; j++) { if (g.Edges[v][j] != GraphAjacencyMatrix.SelfToSelf && g.Edges[v][j] != GraphAjacencyMatrix.NoEdge && isDiscovered[j] == false) { q.Enqueue(j); isDiscovered[j] = true; } } } } } /// <summary> /// 圖的廣度優先搜尋(Breadth-First-Search)演算法。 /// 圖用鄰接表表示。 /// </summary> /// <param name="g">用鄰接矩陣表示的圖。</param> public static void Bfs2(GraphAjacencyList g) { bool[] isDiscovered = new bool[g.NumberOfVertex]; for (int i = 0; i < isDiscovered.Length; i++) { isDiscovered[i] = false; } Queue<int> q = new Queue<int>(); for (int i = 0; i < g.NumberOfVertex; i++) { if (isDiscovered[i] == false) { q.Enqueue(i); isDiscovered[i] = true; } while (q.Count != 0) { int v = q.Dequeue(); // visit node operation Console.WriteLine($"V{v} = {g.Vertexes[v].Data}"); Edge e = g.Vertexes[v].Edge; while (e != null) { int j = e.HeadVertex; if (isDiscovered[j] == false) { q.Enqueue(j); isDiscovered[j] = true; } e = e.Next; } } } } } /// <summary> /// 用鄰接矩陣表示的圖的頂點類。 /// </summary> public class AjacentVertex { /// <summary> /// 頂點的資料域。 /// </summary> public string Data { get; set; } = ""; } /// <summary> /// 鄰接矩陣表示的圖類。 /// </summary> public class GraphAjacencyMatrix { /// <summary> /// 用系統能夠表示的最大整數表示無窮。 /// </summary> public static int Inifinity = int.MaxValue; /// <summary> /// 用無窮表示不存在邊(Vi, Vj)或弧<Vi, Vj> /// </summary> public static int NoEdge = Inifinity; /// <summary> /// 用於在鄰接矩陣中表示頂點Vi到頂點Vi的情形。 /// </summary> public static int SelfToSelf = 0; /// <summary> /// 圖中的所有頂點構成的頂點陣列。 /// </summary> public AjacentVertex[] Vertexes { get; private set; } /// <summary> /// 圖中的所有邊。用一個二維整型陣列表示。 /// 例如:Edges[1][2] == 2表示Vertexes陣列中 /// 索引為1的頂點到索引為2的頂點<V1, V2>弧的權值為2。 /// </summary> public int[][] Edges { get; private set; } /// <summary> /// 圖的頂點數量。 /// </summary> public int NumberOfVertex { get => Vertexes.Length; } /// <summary> /// 提供圖的所有頂點及邊,用於建立圖類例項。 /// </summary> /// <param name="vertexes">圖中的所有頂點。</param> /// <param name="edges">圖中的所有邊。</param> public GraphAjacencyMatrix(AjacentVertex[] vertexes, int[][] edges) { Vertexes = vertexes; Edges = edges; } } /// <summary> /// 圖G的頂點。用鄰接表來表示頂點的出邊。 /// </summary> public class Vertex { /// <summary> /// 儲存的資料。 /// </summary> public string Data { get; set; } = ""; /// <summary> /// 出邊。 /// </summary> public Edge Edge { get; set; } = null; public Vertex(string data, Edge[] adjacentEdges) { this.Data = data; Edge e = null; for (int i = 0; i < adjacentEdges.Length; i++) { e = new Edge(adjacentEdges[i].HeadVertex, adjacentEdges[i].Data); e.Next = (Edge == null ? null : Edge); Edge = e; } } } /// <summary> /// 圖G的邊。以鄰接表表示頂點的出邊。 /// </summary> public class Edge { /// <summary> /// 邊的弧頭頂點在頂點陣列中的索引。 /// </summary> public int HeadVertex { get; set; } = -1; /// <summary> /// 邊的弧尾頂點的下一條出邊。 /// </summary> public Edge Next { get; set; } = null; /// <summary> /// 邊的描述/資料。 /// </summary> public string Data { get; set; } = string.Empty; public Edge(int vertex = -1, string data = "", Edge edge = null) { this.HeadVertex = vertex; this.Next = edge; this.Data = data; } } /// <summary> /// 鄰接表表示的圖類。 /// </summary> public class GraphAjacencyList { /// <summary> /// 圖中的所有頂點構成的頂點陣列。 /// </summary> public Vertex[] Vertexes { get; private set; } /// <summary> /// 圖的頂點數量。 /// </summary> public int NumberOfVertex { get => Vertexes.Length; } /// <summary> /// 提供圖的所有頂點及邊,用於建立圖類例項。 /// </summary> /// <param name="vertexes">圖中的所有頂點。</param> public GraphAjacencyList(Vertex[] vertexes) { Vertexes = vertexes; } } }
TypeScript程式碼
/** * 用鄰接矩陣表示的圖的頂點類。 */ class AjacentVertex { /** * 頂點的資料域。 */ data: string = ""; } /** * 鄰接矩陣表示的圖類。 */ class GraphAjacencyMatrix { /** * 用系統能夠表示的最大整數表示無窮。 */ static inifinity: number = Number.MAX_VALUE; /** * 用無窮表示不存在邊(Vi, Vj)或弧<Vi, Vj> */ static noEdge: number = GraphAjacencyMatrix.inifinity; /** * 用於在鄰接矩陣中表示頂點Vi到頂點Vi的情形。 */ static selfToSelf: number = 0; /** * 圖中的所有頂點構成的頂點陣列。 */ vertexes: AjacentVertex[]; /** * 圖中的所有邊。用一個二維整型陣列表示。 * 例如:Edges[1][2] == 2表示Vertexes陣列中 * 索引為1的頂點到索引為2的頂點<V1, V2>弧的權值為2。 */ edges: number[][]; /** * 圖的頂點數量。 */ get numberOfVertex(): number { return this.vertexes.length; } /** * 提供圖的所有頂點及邊,用於建立圖類例項。 * @param vertexes 圖中的所有頂點。 * @param edges 圖中的所有邊。 */ constructor(vertexes: AjacentVertex[], edges: number[][]) { this.vertexes = vertexes; this.edges = edges; } } /** * 圖G的頂點。用鄰接表來表示頂點的出邊。 */ class Vertex { /** * 儲存的資料。 */ data: string = ""; /** * 出邊。 */ edge: Edge = null; constructor(data: string, adjacentEdges: Edge[]) { this.data = data; let e: Edge = null; for (let i: number = 0; i < adjacentEdges.length; i++) { e = new Edge(adjacentEdges[i].headVertex, adjacentEdges[i].data); e.next = (this.edge == null ? null : this.edge); this.edge = e; } } } /** * 圖G的邊。以鄰接表表示頂點的出邊。 */ class Edge { /** * 邊的弧頭頂點在頂點陣列中的索引。 */ headVertex: number = -1; /** * 邊的弧尾頂點的下一條出邊。 */ next: Edge = null; /** * 邊的描述/資料。 */ data: string = ""; constructor(vertex: number = -1, data: string = "", edge: Edge = null) { this.headVertex = vertex; this.next = edge; this.data = data; } } /** * 鄰接表表示的圖類。 */ class GraphAjacencyList { /** * 圖中的所有頂點構成的頂點陣列。 */ vertexes: Vertex[]; /** * 圖的頂點數量。 */ get numberOfVertex(): number { return this.vertexes.length; } /** * 提供圖的所有頂點及邊,用於建立圖類例項。 * @param vertexes 圖中的所有頂點。 */ constructor(vertexes: Vertex[]) { this.vertexes = vertexes; } } /** * 圖的深度優先搜尋(Depth-First-Search)演算法(非遞迴)。 * 圖用鄰接矩陣表示。 * @param g 用鄰接矩陣表示的圖。 */ function dfs1(g: GraphAjacencyMatrix): void { let isDiscovered: boolean[] = []; isDiscovered.length = g.numberOfVertex; for (let i: number = 0; i < isDiscovered.length; i++) { isDiscovered[i] = false; } let s: number[] = []; for (let i: number = 0; i < g.numberOfVertex; i++) { if (isDiscovered[i] == false) { s.push(i); isDiscovered[i] = true; } while (s.length != 0) { let v: number = s.pop(); // visit node operation console.log(`V${v} = ${g.vertexes[v].data}`); for (let j: number = g.numberOfVertex - 1; j >= 0; j--) //for (int j = 0; j < g.NumberOfVertex; j++) { if (g.edges[v][j] != GraphAjacencyMatrix.selfToSelf && g.edges[v][j] != GraphAjacencyMatrix.noEdge && isDiscovered[j] == false) { s.push(j); isDiscovered[j] = true; } } } } } /** * 圖的深度優先搜尋(Depth-First-Search)演算法(非遞迴)。 * 圖用鄰接表表示。 * @param g 用鄰接矩陣表示的圖。 */ function dfs2(g: GraphAjacencyList): void { let isDiscovered: boolean[] = []; isDiscovered.length = g.numberOfVertex; for (let i: number = 0; i < isDiscovered.length; i++) { isDiscovered[i] = false; } let s: number[] = []; for (let i: number = 0; i < g.numberOfVertex; i++) { if (isDiscovered[i] == false) { s.push(i); isDiscovered[i] = true; } while (s.length != 0) { let v: number = s.pop(); // visit node operation console.log(`V${v} = ${g.vertexes[v].data}`); let e: Edge = g.vertexes[v].edge; while (e != null) { let j: number = e.headVertex; if (isDiscovered[j] == false) { s.push(j); isDiscovered[j] = true; } e = e.next; } } } } /** * 圖的廣度優先搜尋(Depth-First-Search)演算法。 * 圖用鄰接矩陣表示。 * @param g 用鄰接矩陣表示的圖。 */ function bfs1(g: GraphAjacencyMatrix): void { let isDiscovered: boolean[] = []; isDiscovered.length = g.numberOfVertex; for (let i: number = 0; i < isDiscovered.length; i++) { isDiscovered[i] = false; } let q: number[] = []; for (let i: number = 0; i < g.numberOfVertex; i++) { if (isDiscovered[i] == false) { q.push(i); isDiscovered[i] = true; } while (q.length != 0) { let v: number = q.shift(); // visit node operation console.log(`V${v} = ${g.vertexes[v].data}`); for (let j: number = 0; j < g.numberOfVertex; j++) { if (g.edges[v][j] != GraphAjacencyMatrix.selfToSelf && g.edges[v][j] != GraphAjacencyMatrix.noEdge && isDiscovered[j] == false) { q.push(j); isDiscovered[j] = true; } } } } } /** * 圖的廣度優先搜尋(Breadth-First-Search)演算法。 * 圖用鄰接表表示。 * @param g 用鄰接矩陣表示的圖。 */ function bfs2(g: GraphAjacencyList): void { let isDiscovered: boolean[] = []; isDiscovered.length = g.numberOfVertex; for (let i: number = 0; i < isDiscovered.length; i++) { isDiscovered[i] = false; } let q: number[] = []; for (let i: number = 0; i < g.numberOfVertex; i++) { if (isDiscovered[i] == false) { q.push(i); isDiscovered[i] = true; } while (q.length != 0) { let v: number = q.shift(); // visit node operation console.log(`V${v} = ${g.vertexes[v].data}`); let e: Edge = g.vertexes[v].edge; while (e != null) { let j: number = e.headVertex; if (isDiscovered[j] == false) { q.push(j); isDiscovered[j] = true; } e = e.next; } } } } function main(): void { // 建立圖(鄰接矩陣表示法) let self: number = GraphAjacencyMatrix.selfToSelf, noedge = GraphAjacencyMatrix.noEdge; let vertexes: AjacentVertex[] = [ { data: "A" }, // 0 { data: "B" }, // 1 { data: "C" }, // 2 { data: "D" }, // 3 { data: "E" }, // 4 { data: "F" }, // 5 { data: "G" }, // 6 { data: "H" }, // 7 { data: "I" }, // 8 ]; let edges: number[][] = [ [self, 1, noedge, noedge, noedge, 1, noedge, noedge, noedge], // A [1, self, 1, noedge, noedge, noedge, 1, noedge, 1], // B [noedge, 1, self, 1, noedge, noedge, noedge, noedge, 1], // C [noedge, noedge, 1, self, 1, noedge, 1, 1, 1], // D [noedge, noedge, noedge, 1, self, 1, noedge, 1, noedge], // E [1, noedge, noedge, noedge, 1, self, 1, noedge, noedge], // F [noedge, 1, noedge, 1, noedge, 1, self, 1, noedge], // G [noedge, noedge, noedge, 1, 1, noedge, 1, self, noedge], // H [noedge, 1, 1, 1, noedge, noedge, noedge, noedge, self], // I ]; let graph: GraphAjacencyMatrix = new GraphAjacencyMatrix(vertexes, edges); // 建立圖(鄰接表表示法) let vertexesAjacencyList: Vertex[] = [ new Vertex("A", [new Edge(1, "<A, B>"), new Edge(5, "<A, F>")]), // 0 new Vertex("B", [new Edge(0, "<B, A>"), new Edge(2, "<B, C>"), new Edge(6, "<B, G>"), new Edge(8, "<B, I>")]), // 1 new Vertex("C", [new Edge(1, "<C, B>"), new Edge(3, "<C, D>"), new Edge(8, "<C, I>")]), // 2 new Vertex("D", [new Edge(2, "<D, C>"), new Edge(4, "<D, E>"), new Edge(6, "<D, G>"), new Edge(7, "<D, H>"), new Edge(8, "<D, I>")]), // 3 new Vertex("E", [new Edge(3, "<E, D>"), new Edge(5, "<E, F>"), new Edge(7, "<E, H>")]), // 4 new Vertex("F", [new Edge(0, "<F, A>"), new Edge(4, "<F, E>"), new Edge(6, "<F, G>")]), // 5 new Vertex("G", [new Edge(1, "<G, B>"), new Edge(3, "<G, D>"), new Edge(5, "<G, F>"), new Edge(7, "<G, H>")]), // 6 new Vertex("H", [new Edge(3, "<H, D>"), new Edge(4, "<H, E>"), new Edge(6, "<H, G>"),]), // 7 new Vertex("I", [new Edge(1, "<I, B>"), new Edge(2, "<I, C>"), new Edge(3, "<I, D>"),]), // 8 ]; let graphAjacencyList: GraphAjacencyList = new GraphAjacencyList(vertexesAjacencyList); console.log("圖的深度優先搜尋(鄰接矩陣表示法):"); dfs1(graph); console.log("圖的深度優先搜尋(鄰接表表示法):"); dfs2(graphAjacencyList); console.log("圖的廣度優先搜尋(鄰接矩陣表示法):"); bfs1(graph); console.log("圖的廣度優先搜尋(鄰接表表示法):"); bfs2(graphAjacencyList); } main();
注意:對於tsc編譯器可使用下面的命令來編譯上面的TypeScript程式碼,否則會報錯誤:TS1056: Accessors are only available when targeting ECMAScript 5 and higher.更早版本的ES不支援訪問器(getter/setter)。
tsc graphDfsBfs.ts -t es6
在終端中使用node來執行被tsc編譯成js後的程式碼。
node graphDfsBfs.js
參考資料:
《大話資料結構》(溢彩加強版) - 程傑 著 - 清華大學出版社 - P203