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 拓扑排序

拓扑排序会对有向图的所有顶点进行排序,使有向图从前面的顶点指向后面的顶点。如下图是计算机学科的有向图模型。

这个图的拓扑排序结果将会是以下序列:

  1. CS1
  2. CS2
  3. 汇编语言
  4. 数据结构
  5. 操作系统
  6. 算法

课程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)-图相关推荐

  1. python函数结构图_Python数据结构与算法之图结构(Graph)实例分析

    本文实例讲述了Python数据结构与算法之图结构(Graph).分享给大家供大家参考,具体如下: 图结构(Graph)--算法学中最强大的框架之一.树结构只是图的一种特殊情况. 如果我们可将自己的工作 ...

  2. JS数据结构与算法_链表

    上一篇:JS数据结构与算法_栈&队列 下一篇:JS数据结构与算法_集合&字典 写在前面 说明:JS数据结构与算法 系列文章的代码和示例均可在此找到 上一篇博客发布以后,仅几天的时间竟然 ...

  3. python define graph_Python数据结构与算法之图结构(Graph)实例分析

    本文实例讲述了Python数据结构与算法之图结构(Graph).分享给大家供大家参考,具体如下: 图结构(Graph)--算法学中最强大的框架之一.树结构只是图的一种特殊情况. 如果我们可将自己的工作 ...

  4. python棋盘最短路径_Python数据结构与算法之图的最短路径(Dijkstra算法)完整实例...

    本文实例讲述了Python数据结构与算法之图的最短路径(Dijkstra算法).分享给大家供大家参考,具体如下: # coding:utf-8 # Dijkstra算法--通过边实现松弛 # 指定一个 ...

  5. 特征图注意力_从数据结构到算法:图网络方法初探

    作者 | 朱梓豪 来源 | 机器之心 原文 | 从数据结构到算法:图网络方法初探 如果说 2019 年机器学习领域什么方向最火,那么必然有图神经网络的一席之地.其实早在很多年前,图神经网络就以图嵌入. ...

  6. 数据结构与算法之-----图(拓扑排序)

    [​​​​​​​ 写在前面的话:本专栏的主要内容:数据结构与算法. 1.对于​​​​​​​初识数据结构的小伙伴们,鉴于后面的数据结构的构建会使用到专栏前面的内容,包括具体数据结构的应用,所使用到的数据 ...

  7. 数据结构与算法之-----图(搜索算法)

    [ 写在前面的话:本专栏的主要内容:数据结构与算法. 1.对于​​​​​​​初识数据结构的小伙伴们,鉴于后面的数据结构的构建会使用到专栏前面的内容,包括具体数据结构的应用,所使用到的数据结构,也是自己 ...

  8. 数据结构与算法之图的应用

    数据结构与算法之图的应用 图的定义和基本概念 图的实现 数组〈邻接矩阵〉 邻接表 图的应用 最小生成树 Prim(普里姆)算法 Kruskal(克鲁斯卡尔)算法 最短路径 迪克斯特拉算法 拓扑排序 执 ...

  9. JS数据结构与算法 笔记

    JS数据结构与算法笔记 前言:不定时更新说明 1. 栈(Stack) 1.1 基于数组实现栈 1.2 基于对象实现栈 1.3 基于链表实现栈 1.4 栈的简单应用 1.4.1 字符串中的括号匹配问题 ...

最新文章

  1. 算法与数据结构(约瑟夫问题)
  2. 鼠标控制视角wasd移动_无线款,轻量级,稳控制:雷蛇(Razer) 毒蝰终极版鼠标的快速体验...
  3. linux安装anaconda3时出现error,the size of ** should be 6*** bytes
  4. SDUT OJ 数据结构实验之链表四:有序链表的归并
  5. 剪裁tiff影像数据_能看更会用,超擎影像云平台带你轻松玩转海量影像!
  6. P2290-[HNOI2004]树的计数【组合数,Prufer序列】
  7. 打印n×n数字方阵python_2020华为春招amp;#8211;N阶方阵旋转(python) - 易采站长站
  8. 苹果要做第一个吃螃蟹的人!将率先尝试台积电5nm工艺
  9. 阿里电商架构演变之路(一)
  10. 重学AS3之基础知识重点记忆
  11. Claris FileMaker Pro更新至19.2.1.14中文版
  12. 多线程中的互斥控制程序代码_互斥锁解决 Python 中多线程共享全局变量的问题...
  13. disk dynamic invalid 解决办法 动态磁盘转换器
  14. 一文解决scrapy带案例爬取当当图书
  15. Java的位运算符详解实例——与()、非(~)、或(|)、异或(^)
  16. 【立创EDA开源推荐】10期基于PCB彩印教程(水转印)
  17. php怎么使用sendcloud,PHP开发之SendCloud发送邮件知几何
  18. Ubuntu 日常系列:常用软件
  19. java neon_Eclipse Neon安装指导
  20. 正确思维,和非理性自我斗争

热门文章

  1. 关于动态门户WEB博雅互动的源代码(HTML+CSS+javascript)
  2. DarkTrack 4 Alien Version Released RAT 下载地址视频教程
  3. 支付宝PC即时到账和手机网站支付同步
  4. 0基础学习ios开发笔记第二天
  5. C#学习笔记之线程 - 同步上下文
  6. javascript基础拾遗——词法作用域
  7. 官方文档:Office VBA 参考
  8. 算法第5章上机实践报告
  9. Django 权限管理-后台根据用户权限动态生成菜单
  10. 【BUG】小米5中 video.js 在钉钉 webview 中全屏后 后退无法弹出虚拟键盘