Iwen's blog Iwen's blog
首页
  • 前端文章

    • JavaScript
    • Vue
  • 学习笔记

    • 《JavaScript教程》笔记
    • 《JavaScript高级程序设计》笔记
    • 《ES6 教程》笔记
    • 《Vue》笔记
    • 《TypeScript 从零实现 axios》
    • 小程序笔记
  • HTML
  • CSS
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • Linux
  • 学习
  • 面试
  • 心情杂货
  • 友情链接
  • 网站
  • 资源
  • Vue资源
  • 分类
  • 标签
  • 归档
复盘
关于

Iwen

不摸鱼的哥哥
首页
  • 前端文章

    • JavaScript
    • Vue
  • 学习笔记

    • 《JavaScript教程》笔记
    • 《JavaScript高级程序设计》笔记
    • 《ES6 教程》笔记
    • 《Vue》笔记
    • 《TypeScript 从零实现 axios》
    • 小程序笔记
  • HTML
  • CSS
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • Linux
  • 学习
  • 面试
  • 心情杂货
  • 友情链接
  • 网站
  • 资源
  • Vue资源
  • 分类
  • 标签
  • 归档
复盘
关于
  • Vue

  • Vue进阶

  • CSS

  • ES6

  • Base

  • Core

  • Array

  • Object

  • String

  • Async

  • Browser

  • Http

  • 性能优化

  • 正则

  • 经典总结

  • 设计模式

  • 数据结构

    • 栈
    • 队列
    • 链表
    • 树
    • 集合
    • 字典
    • 图
      • 邻接矩阵
      • 邻接表
      • 关联矩阵
      • 实现
      • 广度优先遍历
      • 广度优先遍历求最短路径
      • 深度优先遍历
    • 散列表
    • 堆
  • 算法

  • 手写

  • TypeScript

  • 复盘
  • 数据结构
Mr.w
2020-11-29

图

# 图(Graph)

图是一种非线性数据结构。它的表示有以下几种:

# 邻接矩阵

邻接矩阵是表示图的常用方法, 用二维数组来表示, 数组的每个下标对应每个点。

当两个点有连线则二维数组的值为 1, 否则二维数组的值为 0。但是这种表示方法会照成存储空间的浪费(因存在大量 0)。

如果索引为i的节点和索引为j的节点相邻,则array[i][j] === 1,否则array[i][j] === 0

# 邻接表

左侧为存储的顶点, 右侧为与之想对应的点, 后文会采用这种方式实现图。

# 关联矩阵

行表示点, 列表示边。关联矩阵通常用于边的数量比顶点多的情况,以节省空间和内存。

# 实现

class Graph {
    constructor() {
        this.vertices = []          // 存储顶点
        this.adjList = new Map()    // 存储边
    }

    // 添加顶点
    addVertex(v) {
        if (!this.vertices.includes(v)) {
            this.vertices.push(v)
            this.adjList.set(v, [])
        }
    }

    // 往指定的点添加相邻的点
    addEdge(v, w) {
        // 验证顶点是否存在于图中
        if (!this.adjList.get(v)) {
            this.addVertex(v)
        }
        if (!this.adjList.get(w)) {
            this.addVertex(w)
        }
        this.adjList.get(v).push(w)
        this.adjList.get(w).push(v)
    }

    log() {
        let str = ''
        let neighbors
        for (let i = 0; i < this.vertices.length; i++) {
            str += `${this.vertices[i]} -> `
            neighbors = this.adjList.get(this.vertices[i]).join(' ')
            str += neighbors + '\n'
        }
        return str
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

按之前邻接表的图示, 跑如下测试用例:

const graph = new Graph()

var topArr = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I']
for (let i of topArr) {
    graph.addVertex(i)
}

graph.addEdge('A', 'B')
graph.addEdge('A', 'C')
graph.addEdge('A', 'D')
graph.addEdge('B', 'E')
graph.addEdge('B', 'F')
graph.addEdge('C', 'D')
graph.addEdge('C', 'G')
graph.addEdge('D', 'G')
graph.addEdge('D', 'H')
graph.addEdge('E', 'I')

console.log(graph.log())
// A -> B C D
// B -> A E F
// C -> A D G
// D -> A C G H
// E -> B I
// F -> B
// G -> C D
// H -> D
// I -> E
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

# 广度优先遍历

顾名思义, 广度优先即横向优先, 英文名为 breadth first search(BFS), 它示意图如下:

思想: 用到了队列的思想 (标白: 未发现; 标灰: 已找寻)

  • 创建队列 u, 将标灰的顶点插入队列;
  • 若队列 u 不为空;
    • 从队列取出值 v;
    • 将 v 的相邻节点标灰并插入队列 u;
bfs(v, callback) {
    const obj = {}
    const queue = new Queue()

    for (let i of this.vertices) { // 初始化颜色
        obj[i] = 'white'
    }

    obj[v] = 'red'                  // 已完成
    queue.enqueue(v)                // 搜索值先入栈

    let shiftQueue, neighbour

    while (!queue.empty()) {
        shiftQueue = queue.dequeue()    
        neighbour = this.adjList.get(shiftQueue)

        for (let i of neighbour) {      // 循环相邻节点
            if (obj[i] === 'white') {   // 未完成的
                obj[i] = 'red'
                queue.enqueue(i)
            }
        }

        if (callback) {
            callback(shiftQueue)
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
graph.bfs('A', (e) => console.log(e))
graph.bfs('B', (e) => console.log(e))
// A B C D E F G H I
// B A E F C D I G H
1
2
3
4

# 广度优先遍历求最短路径

在上述 bfs 函数实现的基础上, 加入两个变量分别存储距离以及最短路径上先前的点

BFS(v) {
    const obj = {}
    const queue = new Queue()

    const distance = {} // 距离
    const prev = {}     // 前溯点

    for (let i of this.vertices) { // 初始化颜色
        obj[i] = 'white'
        distance[i] = 0
        prev[i] = null
    }

    obj[v] = 'red'                  // 搜索值
    queue.enqueue(v)                // 入栈

    let shiftQueue, neighbour

    while (!queue.empty()) {
        shiftQueue = queue.dequeue()
        neighbour = this.adjList.get(shiftQueue)

        for (let i of neighbour) {
            if (obj[i] === 'white') {
                obj[i] = 'red'
                queue.enqueue(i)

                // 第二行的点距离第一行的点相差为 1, 第三行的点距离第二行的点相差为 1, 以此类推。
                // shiftQueue 是 i 的前溯点 (上一级的节点)
                distance[i] = distance[shiftQueue] + 1 
                prev[i] = shiftQueue   
            }
        }
    }

    return {
        distance,
        prev
    }
}

logMinPath(v) {
    const { distance, prev } = this.BFS(v)
    let path = ''
    const arr = []
    Object.keys(distance).map(r => {
        path = r
        while (prev[r]) { // 终止条件为 prev 中值为 null 时
            path = prev[r] + ' - ' + path
            r = prev[r]
        }
        arr.push(path)
    })
    return arr.join('\n')
}

console.log(graph.BFS('A'))

// A
// A - B
// A - C
// A - D
// A - B - E
// A - B - F
// A - C - G
// A - D - H
// A - B - E - I
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67

# 深度优先遍历

深度优先遍历用到了栈的思想。英文名为 depth first search(DFS), 其示意图如下:

dfs(v, cb) {
    const obj = {}

    for (let i of this.vertices) { // 初始化颜色
        obj[i] = 'white'
    }

    const find = (v, color, cb) => {
        color[v] = 'red'

        if (cb) {
            cb(v)
        }
        
        let neighbour = this.adjList.get(v)
        for (let i of neighbour) {  // v的所有邻点列表
            if (color[i] === 'white') { // 未访问过的
                find(i, color, cb)
            }
        }
    }

    find(v, obj, cb)
}

let str = ''
graph.dfs('A', (e) => str += ' ' + e )

console.log(str)
//  A B E I F C D G H
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

下面这个示意图展示了该算法每一步的执行过程。

字典
散列表

← 字典 散列表→

最近更新
01
flex布局页面自适应滚动条问题
12-28
02
前后端分离开发请求接口跨域如何携带cookie的问题
12-28
03
怎么实现div长宽比固定width-height4比3
12-28
更多文章>
Theme by Vdoing | Copyright © 2017-2022 Iwen | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式