日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 >

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

發(fā)布時(shí)間:2025/6/15 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 js数据结构和算法(8)-图 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

8-圖(第11章)

8.1 圖的定義

圖是一種非線性結(jié)構(gòu),由一系列頂點(diǎn)及其連接頂點(diǎn)的邊組成。比如A和B、A和D是相鄰的,而A和E不是相鄰的。一個(gè)頂點(diǎn)相鄰頂點(diǎn)的數(shù)量叫作度,比如A的度為3、D的度為4。路徑指相鄰頂點(diǎn)的一個(gè)連續(xù)序列,如ABEI、ACDG;簡(jiǎn)單路徑指不包含重復(fù)頂點(diǎn)的路徑(除環(huán)外),如ADG;環(huán)指首尾頂點(diǎn)相同的路徑,如ADCA,環(huán)也屬于簡(jiǎn)單路徑。如果圖中每?jī)蓚€(gè)頂點(diǎn)之間都有路徑相連,則稱該圖是連通的。

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

8.2 圖的表示

8.2.1 頂點(diǎn)

創(chuàng)建圖類的第一步就是要?jiǎng)?chuàng)建一個(gè) Vertex 類來保存頂點(diǎn)和邊,Vertex 類有兩個(gè)數(shù)據(jù)成員:一個(gè)用于標(biāo)識(shí)頂點(diǎn),另一個(gè)是表明這個(gè)頂點(diǎn)是否被訪問過的布爾值 。實(shí)現(xiàn)這個(gè)類的程序如下:

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

(原書上關(guān)于頂點(diǎn)的實(shí)現(xiàn)跟我上面的程序還不一樣。其實(shí)頂點(diǎn)不需要用程序?qū)崿F(xiàn)的,接下來也用不到)

8.2.2 邊

圖的實(shí)際信息都保存在邊上面,因?yàn)樗鼈兠枋隽藞D的結(jié)構(gòu)。表示圖的方法有好幾種,我們選用最常見的鄰接表法表示邊。這種方法將邊存儲(chǔ)為由頂點(diǎn)的相鄰頂點(diǎn)列表構(gòu)成的數(shù)組,并以此頂點(diǎn)作為索引。使用這種方案,當(dāng)我們?cè)诔绦蛑幸靡粋€(gè)頂點(diǎn)時(shí),可以高效地訪問與這個(gè)頂點(diǎn)相連的所有頂點(diǎn)的列表。比如,如果頂點(diǎn) 2 與頂點(diǎn) 0、1、 3、 4 相連,我們就可以在map鍵值為2的位置,存儲(chǔ)它的相鄰頂點(diǎn)0、1、3、4

8.2.3 圖

程序如下:這個(gè)類會(huì)記錄一個(gè)圖表示了多少條邊,并使用數(shù)組來記錄頂點(diǎn)數(shù)。使用一個(gè)鍵值為數(shù)組的hash表來存儲(chǔ)頂點(diǎn)的相鄰頂點(diǎn)

class Graph {constructor() {// 頂點(diǎn)數(shù)量this.vertices = []// 鄰接表this.adj = new Map()}// 添加頂點(diǎn)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)}// 顯示這個(gè)圖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 + ' ')}}} } 復(fù)制代碼

測(cè)試:

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() // 打印結(jié)果 1-> 2 3 6 2-> 1 7 3-> 1 5 4 6 4-> 3 5-> 3 6-> 1 3 7-> 2 復(fù)制代碼

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

8.3 圖的遍歷

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

8.3.1 深度優(yōu)先搜索

深度優(yōu)先搜索包括從一條路徑的起始頂點(diǎn)開始追溯,直到到達(dá)最后一個(gè)頂點(diǎn),然后回溯,繼續(xù)追溯下一條路徑,直到到達(dá)最后的頂點(diǎn),如此往復(fù),直到?jīng)]有路徑為止。

深度優(yōu)先搜索算法比較簡(jiǎn)單:訪問一個(gè)沒有訪問過的頂點(diǎn),將它標(biāo)記為已訪問,再遞歸地去訪問在初始頂點(diǎn)的鄰接表中其他沒有訪問過的頂點(diǎn)。

8.3.2 廣度優(yōu)先遍歷

廣度優(yōu)先搜索從第一個(gè)頂點(diǎn)開始,嘗試訪問盡可能靠近它的頂點(diǎn)。本質(zhì)上,這種搜索在圖上是逐層移動(dòng)的,首先檢查最靠近第一個(gè)頂點(diǎn)的層,再逐漸向下移動(dòng)到離起始頂點(diǎn)最遠(yuǎn)的層。

8.4 程序?qū)崿F(xiàn)

假設(shè)我們有以下一個(gè)圖,我們嘗試下實(shí)現(xiàn)這個(gè)圖,并且分別用深度遍歷和廣度遍歷訪問這個(gè)圖

原書中對(duì)圖的實(shí)現(xiàn)太粗糙了,原書中圖要求每個(gè)頂點(diǎn)都是數(shù)字,我這邊做了一下優(yōu)化

// 圖 class Graph {constructor() {// 頂點(diǎn)數(shù)量this.vertices = []// 鄰接表this.adj = new Map()}// 添加頂點(diǎn)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)}// 顯示這個(gè)圖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 + ' ')}}}// 搜索輔助函數(shù),對(duì)對(duì)頂點(diǎn)進(jìn)行初始化,頂點(diǎn)沒有被訪問過就是白色,頂點(diǎn)被訪問過但是還沒探索的話是灰色,頂點(diǎn)被探索過就是黑色initialColor() {let color = new Map()this.vertices.forEach(ele => {color.set(ele, 'white')})return color}// 深度遍歷所有頂點(diǎn)dfs(callback) {let color = this.initialColor()this.vertices.forEach(node => {if (color.get(node) === 'white') {this.dfsVisit(node, color, callback)}})}// 遞歸遍歷頂點(diǎn)的鄰接表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')}// 廣度遍歷所有頂點(diǎn)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)}}} } 復(fù)制代碼

上面的程序?qū)崿F(xiàn)圖的定義,還實(shí)現(xiàn)了深度遍歷和廣度遍歷的方法。我們隊(duì)程序測(cè)試下

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 復(fù)制代碼

測(cè)試結(jié)果是正確的,說明我們的程序?qū)崿F(xiàn)沒有問題

8.5 查找最短路徑

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

代碼如下:

// 獲取每一個(gè)點(diǎn)到頂點(diǎn)的距離和其上一個(gè)頂點(diǎn)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}}// 獲取每一個(gè)點(diǎn)到頂點(diǎn)的路徑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}}// 獲取到頂點(diǎn)的最短路徑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} 復(fù)制代碼

測(cè)試:

我們以獲取頂點(diǎn)到第4個(gè)點(diǎn)的最短路徑為例,測(cè)試如下

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

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

8.6 拓?fù)渑判?/p>

拓?fù)渑判驎?huì)對(duì)有向圖的所有頂點(diǎn)進(jìn)行排序,使有向圖從前面的頂點(diǎn)指向后面的頂點(diǎn)。如下圖是計(jì)算機(jī)學(xué)科的有向圖模型。

這個(gè)圖的拓?fù)渑判蚪Y(jié)果將會(huì)是以下序列:

  • CS1
  • CS2
  • 匯編語言
  • 數(shù)據(jù)結(jié)構(gòu)
  • 操作系統(tǒng)
  • 算法
  • 課程3和課程4可以同時(shí)上,課程5和課程6也可以同時(shí)上,但是其他課程就要有優(yōu)先級(jí)了。比如c1一定要在cs2之前上。

    拓?fù)渑判虼a如下:

    // 拓?fù)渑判騮opSort() {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)}})} 復(fù)制代碼

    用已有的案例測(cè)試:

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

    用其他案例測(cè)試下:

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

    測(cè)試應(yīng)該是沒問題的。

    本章節(jié)完整代碼如下:

    // 圖 class Graph {constructor() {// 頂點(diǎn)數(shù)量this.vertices = []// 鄰接表this.adj = new Map()}// 添加頂點(diǎn)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)}// 顯示這個(gè)圖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 + ' ')}}}// 搜索輔助函數(shù),對(duì)對(duì)頂點(diǎn)進(jìn)行初始化,頂點(diǎn)沒有被訪問過就是白色,頂點(diǎn)被訪問過但是還沒探索的話是灰色,頂點(diǎn)被探索過就是黑色initialColor() {let color = new Map()this.vertices.forEach(ele => {color.set(ele, 'white')})return color}// 深度遍歷所有頂點(diǎn)dfs(callback) {let color = this.initialColor()this.vertices.forEach(node => {if (color.get(node) === 'white') {this.dfsVisit(node, color, callback)}})}// 遞歸遍歷頂點(diǎn)的鄰接表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')}// 廣度遍歷所有頂點(diǎn)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)}}}// 獲取每一個(gè)點(diǎn)到頂點(diǎn)的距離和其上一個(gè)頂點(diǎn)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}}// 獲取每一個(gè)點(diǎn)到頂點(diǎn)的路徑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}}// 獲取到頂點(diǎn)的最短路徑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}// 拓?fù)渑判騮opSort() {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() 復(fù)制代碼

    個(gè)人評(píng)價(jià):關(guān)于圖的知識(shí)常用的大概就這么多,書上有關(guān)圖的代碼寫的真是一團(tuán)糟,很多要自己查資料實(shí)現(xiàn)。這作者真是太不負(fù)責(zé)任了

    總結(jié)

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

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。