日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

js数据结构和算法(8)-图

發布時間:2025/6/15 编程问答 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 js数据结构和算法(8)-图 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

8-圖(第11章)

8.1 圖的定義

圖是一種非線性結構,由一系列頂點及其連接頂點的邊組成。比如A和B、A和D是相鄰的,而A和E不是相鄰的。一個頂點相鄰頂點的數量叫作度,比如A的度為3、D的度為4。路徑指相鄰頂點的一個連續序列,如ABEI、ACDG;簡單路徑指不包含重復頂點的路徑(除環外),如ADG;環指首尾頂點相同的路徑,如ADCA,環也屬于簡單路徑。如果圖中每兩個頂點之間都有路徑相連,則稱該圖是連通的。

如果圖中的邊具有方向,則成為有向圖。如果圖中的邊是雙向的,則稱圖是強連通的。比如下圖中的C和D是強連通的

8.2 圖的表示

8.2.1 頂點

創建圖類的第一步就是要創建一個 Vertex 類來保存頂點和邊,Vertex 類有兩個數據成員:一個用于標識頂點,另一個是表明這個頂點是否被訪問過的布爾值 。實現這個類的程序如下:

class Vertex {constructor(label, color) {this.label = labelthis.color = color} } 復制代碼

(原書上關于頂點的實現跟我上面的程序還不一樣。其實頂點不需要用程序實現的,接下來也用不到)

8.2.2 邊

圖的實際信息都保存在邊上面,因為它們描述了圖的結構。表示圖的方法有好幾種,我們選用最常見的鄰接表法表示邊。這種方法將邊存儲為由頂點的相鄰頂點列表構成的數組,并以此頂點作為索引。使用這種方案,當我們在程序中引用一個頂點時,可以高效地訪問與這個頂點相連的所有頂點的列表。比如,如果頂點 2 與頂點 0、1、 3、 4 相連,我們就可以在map鍵值為2的位置,存儲它的相鄰頂點0、1、3、4

8.2.3 圖

程序如下:這個類會記錄一個圖表示了多少條邊,并使用數組來記錄頂點數。使用一個鍵值為數組的hash表來存儲頂點的相鄰頂點

class Graph {constructor() {// 頂點數量this.vertices = []// 鄰接表this.adj = new Map()}// 添加頂點addVertice(v) {this.vertices.push(v)this.adj.set(v, [])}// 添加邊addEdge(v, w) {// console.log('this.adj.get(v):', this.adj.get(v))// console.log('this.adj.get(w):', this.adj.get(w))this.adj.get(v).push(w)this.adj.get(w).push(v)}// 顯示這個圖showGraph() {let keys = this.adj.keys()for (let k of keys) {console.log(k + '->')for (let i of this.adj.get(k)) {console.log(i + ' ')}}} } 復制代碼

測試:

let g = new Graph() let vertices = [1, 2, 3, 4, 5, 6, 7]vertices.forEach(v => {g.addVertice(v) })g.addEdge(1, 2) g.addEdge(1, 3) g.addEdge(1, 6) g.addEdge(2, 7) g.addEdge(3, 5) g.addEdge(3, 4) g.addEdge(3, 6) g.showGraph() // 打印結果 1-> 2 3 6 2-> 1 7 3-> 1 5 4 6 4-> 3 5-> 3 6-> 1 3 7-> 2 復制代碼

打印結果正確,說明我的程序沒有問題。

8.3 圖的遍歷

圖的遍歷有兩種常用的方法:深度優先搜索和廣度優先搜索。

8.3.1 深度優先搜索

深度優先搜索包括從一條路徑的起始頂點開始追溯,直到到達最后一個頂點,然后回溯,繼續追溯下一條路徑,直到到達最后的頂點,如此往復,直到沒有路徑為止。

深度優先搜索算法比較簡單:訪問一個沒有訪問過的頂點,將它標記為已訪問,再遞歸地去訪問在初始頂點的鄰接表中其他沒有訪問過的頂點。

8.3.2 廣度優先遍歷

廣度優先搜索從第一個頂點開始,嘗試訪問盡可能靠近它的頂點。本質上,這種搜索在圖上是逐層移動的,首先檢查最靠近第一個頂點的層,再逐漸向下移動到離起始頂點最遠的層。

8.4 程序實現

假設我們有以下一個圖,我們嘗試下實現這個圖,并且分別用深度遍歷和廣度遍歷訪問這個圖

原書中對圖的實現太粗糙了,原書中圖要求每個頂點都是數字,我這邊做了一下優化

// 圖 class Graph {constructor() {// 頂點數量this.vertices = []// 鄰接表this.adj = new Map()}// 添加頂點addVertice(v) {this.vertices.push(v)this.adj.set(v, [])}// 添加邊addEdge(v, w) {// console.log('this.adj.get(v):', this.adj.get(v))// console.log('this.adj.get(w):', this.adj.get(w))this.adj.get(v).push(w)this.adj.get(w).push(v)}// 顯示這個圖showGraph() {let keys = this.adj.keys()for (let k of keys) {console.log(k + '->')for (let i of this.adj.get(k)) {console.log(i + ' ')}}}// 搜索輔助函數,對對頂點進行初始化,頂點沒有被訪問過就是白色,頂點被訪問過但是還沒探索的話是灰色,頂點被探索過就是黑色initialColor() {let color = new Map()this.vertices.forEach(ele => {color.set(ele, 'white')})return color}// 深度遍歷所有頂點dfs(callback) {let color = this.initialColor()this.vertices.forEach(node => {if (color.get(node) === 'white') {this.dfsVisit(node, color, callback)}})}// 遞歸遍歷頂點的鄰接表dfsVisit(node, color, callback) {let neighbors = this.adj.get(node)color.set(node, 'grey')if (callback) {callback(node)}neighbors.forEach(neighNode => {if (color.get(neighNode) === 'white') {this.dfsVisit(neighNode, color, callback)}})color.set(node, 'black')}// 廣度遍歷所有頂點bfs(callback) {let color = this.initialColor()let queue = []if (this.vertices[0]) {queue.push(this.vertices[0])}while (queue.length > 0) {let qNode = queue.shift()let neighbors = this.adj.get(qNode)color.set(qNode, 'grey')neighbors.forEach(ele => {if (color.get(ele) === 'white') {color.set(ele, 'grey')queue.push(ele)}})color.set(qNode, 'black')if (callback) {callback(qNode)}}} } 復制代碼

上面的程序實現圖的定義,還實現了深度遍歷和廣度遍歷的方法。我們隊程序測試下

let g = new Graph() let vertices = [1, 2, 3, 4, 5, 6, 7] vertices.forEach(v => {g.addVertice(v) }) g.addEdge(1, 2) g.addEdge(1, 3) g.addEdge(1, 6) g.addEdge(2, 7) g.addEdge(3, 5) g.addEdge(3, 4) g.addEdge(3, 6)// 深度遍歷 g.dfs(v=>console.log(v)) // 1 2 7 3 5 4 6 // 廣度遍歷 g.bfs(v=>console.log(v)) // 1 2 3 6 7 5 4 復制代碼

測試結果是正確的,說明我們的程序實現沒有問題

8.5 查找最短路徑

圖的最常見的操作是查找從一個頂點到另一個頂點的最短路徑,就要用到廣度搜索。要查找從頂點 A 到頂點 D 的最短路徑,我們首先會查找從 A 到 D 是否有任何一條單邊路徑,接著查找兩條邊的路徑,以此類推。這正是廣度優先搜索的搜索過程,因此我們可以輕松地修改廣度優先搜索算法,找出最短路徑。

代碼如下:

// 獲取每一個點到頂點的距離和其上一個頂點bfsDistance() {let color = this.initialColor()let queue = []let distance = new Map()let pred = new Map()if (this.vertices[0]) {queue.push(this.vertices[0])}this.vertices.forEach(ele => {distance.set(ele, 0)pred.set(ele, null)});while (queue.length > 0) {let qNode = queue.shift()let neighbors = this.adj.get(qNode)color.set(qNode, 'grey')neighbors.forEach(ele => {if (color.get(ele) === 'white') {color.set(ele, 'grey')queue.push(ele)distance.set(ele, distance.get(qNode) + 1)pred.set(ele, qNode)}})color.set(qNode, 'black')}return {distance: distance,pred: pred}}// 獲取每一個點到頂點的路徑getBfsPath() {if (this.vertices.length > 0) {let shortestPath = this.bfsDistance()let fromVertex = this.vertices[0]let paths = []for (let i = 0; i < this.vertices.length; i++) {let path = []let toVertex = this.vertices[i]for (let v = toVertex; v !== fromVertex; v = shortestPath.pred.get(v)) {path.push(v)}path.push(fromVertex)paths.push(path)}return paths}}// 獲取到頂點的最短路徑getShortestPath(v) {let path = nulllet paths = this.getBfsPath()for (let i = 0; i < paths.length; i++) {if (paths[i][0] === v) {path = paths[i]break}}return path} 復制代碼

測試:

我們以獲取頂點到第4個點的最短路徑為例,測試如下

g.getShortestPath(g.vertices[4]) // [5,3,1] 復制代碼

案例獲取頂點到某個點的最短路徑,怎么樣獲取非頂點的兩個點之間的最短路徑呢? 其實道理很簡單,你換下頂點就可以了。在圖里,并沒有規定那個必須是頂點。

8.6 拓撲排序

拓撲排序會對有向圖的所有頂點進行排序,使有向圖從前面的頂點指向后面的頂點。如下圖是計算機學科的有向圖模型。

這個圖的拓撲排序結果將會是以下序列:

  • CS1
  • CS2
  • 匯編語言
  • 數據結構
  • 操作系統
  • 算法
  • 課程3和課程4可以同時上,課程5和課程6也可以同時上,但是其他課程就要有優先級了。比如c1一定要在cs2之前上。

    拓撲排序代碼如下:

    // 拓撲排序topSort() {let stack = []let color = this.initialColor()this.vertices.forEach(ele => {if (color.get(ele) === 'white') {this.topSortVisit(ele, stack, color)}})console.log('stack:', stack)}topSortVisit(ele, stack, color) {color.set(ele, 'grey')stack.push(ele)let neighbors = this.adj.get(ele)neighbors.forEach(nNode => {if (color.get(nNode) === 'white') {this.topSortVisit(nNode, stack, color)}})} 復制代碼

    用已有的案例測試:

    g.topSort() // stack: (7)?[1, 2, 7, 3, 5, 4, 6] 復制代碼

    用其他案例測試下:

    let g = new Graph() let vertices = ['cs1', 'cs2', '匯編語言', '數據結構', '操作系統', '算法']vertices.forEach(v => {g.addVertice(v) })g.addEdge('cs1', 'cs2') g.addEdge('cs2', '匯編語言') g.addEdge('cs2', '數據結構') g.addEdge('數據結構', '操作系統') g.addEdge('數據結構', '算法') g.topSort() // stack: (6)?["cs1", "cs2", "匯編語言", "數據結構", "操作系統", "算法"] 復制代碼

    測試應該是沒問題的。

    本章節完整代碼如下:

    // 圖 class Graph {constructor() {// 頂點數量this.vertices = []// 鄰接表this.adj = new Map()}// 添加頂點addVertice(v) {this.vertices.push(v)this.adj.set(v, [])}// 添加邊addEdge(v, w) {// console.log('this.adj.get(v):', this.adj.get(v))// console.log('this.adj.get(w):', this.adj.get(w))this.adj.get(v).push(w)this.adj.get(w).push(v)}// 顯示這個圖showGraph() {let keys = this.adj.keys()for (let k of keys) {console.log(k + '->')for (let i of this.adj.get(k)) {console.log(i + ' ')}}}// 搜索輔助函數,對對頂點進行初始化,頂點沒有被訪問過就是白色,頂點被訪問過但是還沒探索的話是灰色,頂點被探索過就是黑色initialColor() {let color = new Map()this.vertices.forEach(ele => {color.set(ele, 'white')})return color}// 深度遍歷所有頂點dfs(callback) {let color = this.initialColor()this.vertices.forEach(node => {if (color.get(node) === 'white') {this.dfsVisit(node, color, callback)}})}// 遞歸遍歷頂點的鄰接表dfsVisit(node, color, callback) {let neighbors = this.adj.get(node)color.set(node, 'grey')if (callback) {callback(node)}neighbors.forEach(neighNode => {if (color.get(neighNode) === 'white') {this.dfsVisit(neighNode, color, callback)}})color.set(node, 'black')}// 廣度遍歷所有頂點bfs(callback) {let color = this.initialColor()let queue = []if (this.vertices[0]) {queue.push(this.vertices[0])}while (queue.length > 0) {let qNode = queue.shift()let neighbors = this.adj.get(qNode)color.set(qNode, 'grey')neighbors.forEach(ele => {if (color.get(ele) === 'white') {color.set(ele, 'grey')queue.push(ele)}})color.set(qNode, 'black')if (callback) {callback(qNode)}}}// 獲取每一個點到頂點的距離和其上一個頂點bfsDistance() {let color = this.initialColor()let queue = []let distance = new Map()let pred = new Map()if (this.vertices[0]) {queue.push(this.vertices[0])}this.vertices.forEach(ele => {distance.set(ele, 0)pred.set(ele, null)});while (queue.length > 0) {let qNode = queue.shift()let neighbors = this.adj.get(qNode)color.set(qNode, 'grey')neighbors.forEach(ele => {if (color.get(ele) === 'white') {color.set(ele, 'grey')queue.push(ele)distance.set(ele, distance.get(qNode) + 1)pred.set(ele, qNode)}})color.set(qNode, 'black')}return {distance: distance,pred: pred}}// 獲取每一個點到頂點的路徑getBfsPath() {if (this.vertices.length > 0) {let shortestPath = this.bfsDistance()let fromVertex = this.vertices[0]let paths = []for (let i = 0; i < this.vertices.length; i++) {let path = []let toVertex = this.vertices[i]for (let v = toVertex; v !== fromVertex; v = shortestPath.pred.get(v)) {path.push(v)}path.push(fromVertex)paths.push(path)}return paths}}// 獲取到頂點的最短路徑getShortestPath(v) {let path = nulllet paths = this.getBfsPath()for (let i = 0; i < paths.length; i++) {if (paths[i][0] === v) {path = paths[i]break}}return path}// 拓撲排序topSort() {let stack = []let color = this.initialColor()this.vertices.forEach(ele => {if (color.get(ele) === 'white') {this.topSortVisit(ele, stack, color)}})console.log('stack:', stack)}topSortVisit(ele, stack, color) {color.set(ele, 'grey')stack.push(ele)let neighbors = this.adj.get(ele)neighbors.forEach(nNode => {if (color.get(nNode) === 'white') {this.topSortVisit(nNode, stack, color)}})} }let g = new Graph() let vertices = [1, 2, 3, 4, 5, 6, 7]vertices.forEach(v => {g.addVertice(v) })g.addEdge(1, 2) g.addEdge(1, 3) g.addEdge(1, 6) g.addEdge(2, 7) g.addEdge(3, 5) g.addEdge(3, 4) g.addEdge(3, 6) g.getBfsPath() 復制代碼

    個人評價:關于圖的知識常用的大概就這么多,書上有關圖的代碼寫的真是一團糟,很多要自己查資料實現。這作者真是太不負責任了

    總結

    以上是生活随笔為你收集整理的js数据结构和算法(8)-图的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。