JS版数据结构之 图 ( 图的遍历、最短路径 )

一、图

1. 图的分类

  1. 无向图:边没有方向。边与边之间的关系仅仅是连接。
  2. 有向图:边不仅连接顶点,并具有方向性.
  3. 带权图:边带着权重。

2. 图的存储结构

  1. 顺序存储
  2. 链式存储

线性表:仅有的关系就是线性关系。
树:有清晰的层次关系。
图:集合中每一个元素都可能有关系,图由顶点和边构成,所以要存储图形结构的信息,无非就是存储图的顶点和边。如果使用数组,存储边时会比较复杂。

因此我们采用以下两种存储结构:

  1. 邻接矩阵:n*m数据的集合,矩阵是由行和列组成的一组数表,邻接矩阵让每一个节点和整数相关联。
    用1表示有关系,用0表示没关系。
    优点:表示非常的明确。
    缺点:非常浪费计算机内存空间,存储了太多的0
    在这里插入图片描述

  2. 邻接表:由图中每个顶点及与顶点相邻的顶点列表组成。数组、链表、字典
    在这里插入图片描述

3.实现

// 存储顶点和边,顶点可以使用数组来存
class Graph {
    
    
  constructor() {
    
    
    // 存储顶点
    this.vertiecs = []
    // 存储边
    this.edgeList = {
    
    }
    /**
     * A:['B','C','D'] => 键值对的形式
     */
  }

  // 添加顶点
  addVerTex(v) {
    
    
    this.vertiecs.push(v)
    this.edgeList[v] = []
  }

  // 添加边
  addEdge(a, b) {
    
    
    // 无向图
    this.edgeList[a].push(b)
    this.edgeList[b].push(a)
  }

  // 添加toString方法
  toString() {
    
    
    let rst = ''
    for (let i = 0; i < this.vertiecs.length; i++) {
    
    
      // 顶点
      let vertex = this.vertiecs[i]
      rst += `${
      
      vertex}=>`
      // 边
      let edge = this.edgeList[vertex]
      for (let j = 0; j < edge.length; j++) {
    
    
        rst += edge[j]
      }
      rst += '\n'
    }
    return rst
  }
}

const graph = new Graph()

// 添加点
graph.addVerTex('A')
graph.addVerTex('B')
graph.addVerTex('C')
graph.addVerTex('D')
graph.addVerTex('E')
graph.addVerTex('F')

// 添加边
graph.addEdge('A', "B")
graph.addEdge('B', "C")
graph.addEdge('B', "F")
graph.addEdge('C', "D")

console.log(graph)
console.log(graph.toString())

4.遍历

遍历:从某个结点触发,一次访问数据结构中全部结点,每个结点访问一次。

  • 广度优先遍历 BFC:优先横向遍历图,从图中的某个结点v出发,访问了v后,一次访问v的各个未曾访问过的邻接点,然后分别从这些结点触发,依次访问它们的邻接点,直到所有结点被访问过。
  • 深度优先遍历 DFS:先遍历图的纵向。
  • 图遍历的思路:
    • 每一个顶点有三种状态:
      • 未发现(没有发现这个顶点)
      • 已经发现(发现其他顶点连接到这里,但是没有发现这个顶点全部的邻接点)
      • 已经探索(已经发现这个顶点的所有邻接点)
    • 记录顶点是否被访问,使用三种颜色老i标识
      • 白色(未发现)
      • 灰色(已发现)
      • 黑色(已探索)

(1)广度优先遍历

  • 广度优先遍历的过程
    • 发现未发现的顶点后,存放至队列中,等待查找,并将结点标识为已发现
    • 在队列中拿出以发现的结点,开始探索其他顶点,并跳过已探索的顶点
    • 遍历完这个顶点以后,将顶点标识为已探索
    • 继续在队列中探索下一个顶点
class Queue {
    
    
  constructor() {
    
    
    this.items = []
  }

  // 入队
  enqueue(ele) {
    
    
    this.items.push(ele)
  }

  // 出队
  dequeue() {
    
    
    return this.items.shift()
  }

  // 查看队首元素
  front() {
    
    
    return this.items[0]
  }

  // 查看队尾元素
  real() {
    
    
    return this.items[this.items.length - 1]
  }

  // 查看队列是否为空
  isEmpty() {
    
    
    return this.items.length === 0
  }

  // 长度
  size() {
    
    
    return this.items.length
  }

  // 清空
  clear() {
    
    
    this.items = []
  }
}

// 存储顶点和边,顶点可以使用数组来存
class Graph {
    
    
  constructor() {
    
    
    // 存储顶点
    this.vertiecs = []
    // 存储边
    this.edgeList = {
    
    }
    /**
     * A:['B','C','D'] => 键值对的形式
     */
  }

  // 添加顶点
  addVerTex(v) {
    
    
    this.vertiecs.push(v)
    this.edgeList[v] = []
  }

  // 添加边
  addEdge(a, b) {
    
    
    // 无向图
    this.edgeList[a].push(b)
    this.edgeList[b].push(a)
  }

  // 添加toString方法
  toString() {
    
    
    let rst = ''
    for (let i = 0; i < this.vertiecs.length; i++) {
    
    
      // 顶点
      let vertex = this.vertiecs[i]
      rst += `${
      
      vertex}=>`
      // 边
      let edge = this.edgeList[vertex]
      for (let j = 0; j < edge.length; j++) {
    
    
        rst += edge[j]
      }
      rst += '\n'
    }
    return rst
  }

  // 初始化颜色
  initColor() {
    
    
    let colors = {
    
    }
    for (let i = 0; i < this.vertiecs.length; i++) {
    
    
      colors[this.vertiecs[i]] = 'white'
    }
    return colors
  }

  // 广度优先
  bfs(v, callback) {
    
    
    // 所有结点都设置为白色
    let colors = this.initColor()
    /**
     * colors:{
     *  'A':'white',
     *  'B':'white',
     * }
     */

    // 创建队列
    let queue = new Queue()
    queue.enqueue(v)
    while (!queue.isEmpty()) {
    
    
      // A 出列
      const qVertex = queue.dequeue(v)
      // 获取A 所有的邻接点
      const edge = this.edgeList[qVertex]
      // 遍历
      for (let i = 0; i < edge.length; i++) {
    
    
        // 当前节点
        const e = edge[i]
        if (colors[e] === 'white') {
    
    
          colors[e] = 'gray'
          queue.enqueue(e)
        }
      }
      // A 已经探索,设置为黑色
      colors[qVertex] = 'black'
      if (callback) {
    
    
        callback(qVertex)
      }
    }
  }
}

const graph = new Graph()

// 添加点
graph.addVerTex('A')
graph.addVerTex('B')
graph.addVerTex('C')
graph.addVerTex('D')
graph.addVerTex('E')
graph.addVerTex('F')

// 添加边
graph.addEdge('A', "B")
graph.addEdge('A', "C")
graph.addEdge('B', "E")
graph.addEdge('A', "D")
graph.addEdge('B', "F")


graph.bfs('A', (e) => {
    
    
  console.log(e)
})

(2)深度优先遍历

广度优先遍历使用的是队列,深度优先的原理:递归
深度优先的过程:

  • 从某一顶点开始查找,并把结点标识为灰色
  • 从这个结点开始探索其他顶点,并跳过已经发现的顶点
  • 遍历完这个结点后将这个结点标记为已探索(黑色)
  • 递归返回,继续探索下一个路径的最深顶点

  // 递归实现深度优先
  dfsVisit(v, color, callback) {
    
    
    color[v] = 'gray'

    // 已经被访问到了
    if (callback) {
    
    
      callback(v)
    }

    // 获取所有的边
    let edge = this.edgeList[v]
    for (let i = 0; i < edge.length; i++) {
    
    
      // 当前边
      let e = edge[i]
      if (color[e] === 'white'){
    
    
        color[e] = 'gray'
        this.dfsVisit(e,color,callback)
      } 

    }
  }
  // 深度优先遍历
  dfs(v, callback) {
    
    
    const color = this.initColor()
    this.dfsVisit(v,color,callback)
  }
  
// 调用深度优先
graph.dfs('A', (e) => {
    
    
  console.log(e)
})

5.最短路径

最短路径:一个顶点到另一个顶点之间的最短路径。
回溯点:回溯点是离上一个顶点最近的顶点。
回溯路径:由所有回溯点组成。
两种常见的求最短路径的算法:

  • 迪杰斯特拉算法:贪心算法的一个应用,解决单源点到其余顶点的最短路径问题。
    思想:每次找到离源点(如1号结点)最近的一个顶点,然后以该顶点为中心进行扩展,最终得到源点到其余所有点的最短路径。
  • 佛洛依德算法:经典动态规划应用,解决多源最短路径,时间复杂度O(n^3) ,空间复杂度O(n^2)

  // 利用广度优先遍历,设置回溯点
  // 利用栈来存回溯路径
  bfs(v, callback) {
    
    
    let colors = this.initColor()
    let queue = new Queue()
    queue.enqueue(v)

    // 最短路径
    // 所有的回溯点设置为null
    let prev = {
    
    }
    for (let i = 0; i < this.vertiecs.length; i++) {
    
    
      prev[this.vertiecs[i]] = null
    }

    while (!queue.isEmpty()) {
    
    
      const qVertex = queue.dequeue()
      const edge = this.edgeList[qVertex]
      for (let i = 0; i < edge.length; i++) {
    
    
        const e = edge[i]
        if (colors[e] === 'white') {
    
    
          colors[e] = 'gray'

          // 设置回溯点
          prev[e] = qVertex

          queue.enqueue(e)
        }
      }
      colors[qVertex] = 'black'
      if (callback) {
    
    
        callback(qVertex)
      }
    }
    return prev
  }

const prev = graph.bfs('A')

const shortPath = (from, to) => {
    
    
  // 当前结点
  let vertex = to
  let stack = new Stack()
  while (vertex !== from) {
    
    
    // console.log(vertex)
    stack.push(vertex)
    vertex = prev[vertex]   //寻找自己的回溯点
  }
  stack.push(vertex)

  let path = ''
  while (!stack.isEmpty()) {
    
    
    path += `${
      
      stack.pop()}=>`
  }
  return path
}

const path = shortPath('A', 'F')

console.log(path)

Guess you like

Origin blog.csdn.net/ladream/article/details/119918093