标签:dfs 连线 出图 问题: string 常用方法 个数 图的遍历 状态
什么是图?
图的特点:
图的常用术语:
顶点:表示图中的一个节点;
边:表示顶点和顶点给之间的连线;
相邻顶点:由一条边连接在一起的顶点称为相邻顶点;
度:一个顶点的度是相邻顶点的数量;
路径:
无向图:图中的所有边都是没有方向的;
有向图:图中的所有边都是有方向的;
无权图:无权图中的边没有任何权重意义;
带权图:带权图中的边有一定的权重含义;
表示图的常用方式为:邻接矩阵。
可以使用二维数组来表示邻接矩阵;
邻接矩阵让每个节点和一个整数相关联,该整数作为数组的下标值;
使用一个二维数组来表示顶点之间的连接;

如上图所示:
邻接矩阵的问题:
另外一种表示图的常用方式为:邻接表。

如上图所示:
邻接表的问题:
在实现过程中采用邻接表的方式来表示边,使用字典类来存储邻接表。
首先需要引入之前实现的,之后会用到的字典类和队列类:
    //封装字典类
function Dictionary(){
  //字典属性
  this.items = {}
  //字典操作方法
  //一.在字典中添加键值对
  Dictionary.prototype.set = function(key, value){
    this.items[key] = value
  }
  //二.判断字典中是否有某个key
  Dictionary.prototype.has = function(key){
    return this.items.hasOwnProperty(key)
  }
  //三.从字典中移除元素
  Dictionary.prototype.remove = function(key){
    //1.判断字典中是否有这个key
    if(!this.has(key)) return false
    //2.从字典中删除key
    delete this.items[key]
    return true
  }
  //四.根据key获取value
  Dictionary.prototype.get = function(key){
    return this.has(key) ? this.items[key] : undefined
  }
  //五.获取所有keys
  Dictionary.prototype.keys = function(){
    return Object.keys(this.items)
  }
  //六.size方法
  Dictionary.prototype.keys = function(){
    return this.keys().length
  }
  //七.clear方法
  Dictionary.prototype.clear = function(){
    this.items = {}
  }
}
   // 基于数组封装队列类
    function Queue() {
    // 属性
      this.items = []
    // 方法
    // 1.将元素加入到队列中
    Queue.prototype.enqueue = element => {
      this.items.push(element)
    }
    // 2.从队列中删除前端元素
    Queue.prototype.dequeue = () => {
      return this.items.shift()
    }
    // 3.查看前端的元素
    Queue.prototype.front = () => {
      return this.items[0]
    }
    // 4.查看队列是否为空
    Queue.prototype.isEmpty = () => {
      return this.items.length == 0;
    }
    // 5.查看队列中元素的个数
    Queue.prototype.size = () => {
      return this.items.length
    }
    // 6.toString方法
    Queue.prototype.toString = () => {
      let resultString = ‘‘
        for (let i of this.items){
          resultString += i + ‘ ‘
        }
        return resultString
      }
    }
先创建图类Graph,并添加基本属性,再实现图类的常用方法:
    //封装图类
    function Graph (){
      //属性:顶点(数组)/边(字典)
      this.vertexes = []  //顶点
      this.edges = new Dictionary() //边
      }
如图所示:

创建一个数组对象vertexes存储图的顶点;创建一个字典对象edges存储图的边,其中key为顶点,value为存储key顶点相邻顶点的数组。
代码实现:
      //添加方法
      //一.添加顶点
      Graph.prototype.addVertex = function(v){
        this.vertexes.push(v)
        this.edges.set(v, []) //将边添加到字典中,新增的顶点作为键,对应的值为一个存储边的空数组
      }
      //二.添加边
      Graph.prototype.addEdge = function(v1, v2){//传入两个顶点为它们添加边
        this.edges.get(v1).push(v2)//取出字典对象edges中存储边的数组,并添加关联顶点
        this.edges.get(v2).push(v1)//表示的是无向表,故要添加互相指向的两条边
      }
为图类Graph添加toString方法,实现以邻接表的形式输出图中各顶点。
代码实现:
      //三.实现toString方法:转换为邻接表形式
      Graph.prototype.toString = function (){
        //1.定义字符串,保存最终结果
        let resultString = ""
        //2.遍历所有的顶点以及顶点对应的边
        for (let i = 0; i < this.vertexes.length; i++) {//遍历所有顶点
          resultString += this.vertexes[i] + ‘-->‘
          let vEdges = this.edges.get(this.vertexes[i])
          for (let j = 0; j < vEdges.length; j++) {//遍历字典中每个顶点对应的数组
            resultString += vEdges[j] + ‘  ‘;
          }
          resultString += ‘\n‘
        }
        return resultString
      }
测试代码:
   //测试代码
    //1.创建图结构
    let graph = new Graph()
    //2.添加顶点
    let myVertexes = [‘A‘, ‘B‘, ‘C‘, ‘D‘, ‘E‘, ‘F‘, ‘G‘, ‘H‘, ‘I‘]
    for (let i = 0; i < myVertexes.length; i++) {
      graph.addVertex(myVertexes[i])
    }
    //3.添加边
    graph.addEdge(‘A‘, ‘B‘)
    graph.addEdge(‘A‘, ‘C‘)
    graph.addEdge(‘A‘, ‘D‘)
    graph.addEdge(‘C‘, ‘D‘)
    graph.addEdge(‘C‘, ‘G‘)
    graph.addEdge(‘D‘, ‘G‘)
    graph.addEdge(‘D‘, ‘H‘)
    graph.addEdge(‘B‘, ‘E‘)
    graph.addEdge(‘B‘, ‘F‘)
    graph.addEdge(‘E‘, ‘I‘)
    //4.输出结果
    console.log(graph.toString());
测试结果:

图的遍历思想:
遍历图的两种算法:
为了记录顶点是否被访问过,使用三种颜色来表示它们的状态
首先封装initializeColor方法将图中的所有顶点初始化为白色,代码实现如下:
      //四.初始化状态颜色
      Graph.prototype.initializeColor = function(){
        let colors = []
        for (let i = 0; i < this.vertexes.length; i++) {
           colors[this.vertexes[i]] = ‘white‘;
        }
        return colors
      }
广度优先搜索算法的思路:

实现思路:
基于队列可以简单地实现广度优先搜索算法:
代码实现:
      //五.实现广度搜索(BFS)
      //传入指定的第一个顶点和处理结果的函数
      Graph.prototype.bfs = function(initV, handler){
        //1.初始化颜色
        let colors = this.initializeColor()
        //2.创建队列
        let que = new Queue()
        //3.将顶点加入到队列中
        que.enqueue(initV)
        //4.循环从队列中取出元素,队列为空才停止
        while(!que.isEmpty()){
          //4.1.从队列首部取出一个顶点
          let v = que.dequeue()
          //4.2.从字典对象edges中获取和该顶点相邻的其他顶点组成的数组
          let vNeighbours = this.edges.get(v)
          //4.3.将v的颜色变为灰色
          colors[v] = ‘gray‘
          //4.4.遍历v所有相邻的顶点vNeighbours,并且加入队列中
          for (let i = 0; i < vNeighbours.length; i++) {
            const a = vNeighbours[i];
            //判断相邻顶点是否被探测过,被探测过则不加入队列中;并且加入队列后变为灰色,表示被探测过
            if (colors[a] == ‘white‘) {
              colors[a] = ‘gray‘
              que.enqueue(a)
            }
          }
          //4.5.处理顶点v
          handler(v)
          //4.6.顶点v所有白色的相邻顶点都加入队列后,将顶点v设置为黑色。此时黑色顶点v位于队列最前面,进入下一次while循环时会被取出
          colors[v] = ‘black‘
        }
      }
过程详解:
下为指定的第一个顶点为A时的遍历过程:


如此循环直到队列中元素为0,即所有顶点都变黑并移出队列后才停止,此时图中顶点已被全部遍历。
测试代码:
    //测试代码
    //1.创建图结构
    let graph = new Graph()
    //2.添加顶点
    let myVertexes = [‘A‘, ‘B‘, ‘C‘, ‘D‘, ‘E‘, ‘F‘, ‘G‘, ‘H‘, ‘I‘]
    for (let i = 0; i < myVertexes.length; i++) {
      graph.addVertex(myVertexes[i])
    }
    //3.添加边
    graph.addEdge(‘A‘, ‘B‘)
    graph.addEdge(‘A‘, ‘C‘)
    graph.addEdge(‘A‘, ‘D‘)
    graph.addEdge(‘C‘, ‘D‘)
    graph.addEdge(‘C‘, ‘G‘)
    graph.addEdge(‘D‘, ‘G‘)
    graph.addEdge(‘D‘, ‘H‘)
    graph.addEdge(‘B‘, ‘E‘)
    graph.addEdge(‘B‘, ‘F‘)
    graph.addEdge(‘E‘, ‘I‘)
    
    //4.测试bfs遍历方法
    let result = ""
    graph.bfs(graph.vertexes[0], function(v){
      result += v + "-"
    })
    console.log(result);
测试结果:

可见,安装了广度优先搜索的顺序不重复地遍历了所有顶点。
广度优先算法的思路:

实现思路:
基于递归实现深度优先搜索算法:定义dfs方法用于调用递归方法dfsVisit,定义dfsVisit方法用于递归访问图中的各个顶点。
在dfs方法中:
在dfsVisit方法中:
代码实现:
      //六.实现深度搜索(DFS)
      Graph.prototype.dfs = function(initV, handler){
        //1.初始化顶点颜色
        let colors = this.initializeColor()
        //2.从某个顶点开始依次递归访问
        this.dfsVisit(initV, colors, handler)
      }
      //为了方便递归调用,封装访问顶点的函数,传入三个参数分别表示:指定的第一个顶点、颜色、处理函数
      Graph.prototype.dfsVisit = function(v, colors, handler){
        //1.将颜色设置为灰色
        colors[v] = ‘gray‘
        //2.处理v顶点
        handler(v)
        //3.访问V的相邻顶点
        let vNeighbours = this.edges.get(v)
        for (let i = 0; i < vNeighbours.length; i++) {
          let a = vNeighbours[i];
          //判断相邻顶点是否为白色,若为白色,递归调用函数继续访问
          if (colors[a] == ‘white‘) {
            this.dfsVisit(a, colors, handler)
          }
          
        }
        //4.将v设置为黑色
        colors[v] = ‘black‘
      }
过程详解:
这里主要解释一下代码中的第3步操作:访问指定顶点的相邻顶点。


下图为遍历图中各顶点的完整过程:

测试代码:
    //测试代码
    //1.创建图结构
    let graph = new Graph()
    //2.添加顶点
    let myVertexes = [‘A‘, ‘B‘, ‘C‘, ‘D‘, ‘E‘, ‘F‘, ‘G‘, ‘H‘, ‘I‘]
    for (let i = 0; i < myVertexes.length; i++) {
      graph.addVertex(myVertexes[i])
    }
    //3.添加边
    graph.addEdge(‘A‘, ‘B‘)
    graph.addEdge(‘A‘, ‘C‘)
    graph.addEdge(‘A‘, ‘D‘)
    graph.addEdge(‘C‘, ‘D‘)
    graph.addEdge(‘C‘, ‘G‘)
    graph.addEdge(‘D‘, ‘G‘)
    graph.addEdge(‘D‘, ‘H‘)
    graph.addEdge(‘B‘, ‘E‘)
    graph.addEdge(‘B‘, ‘F‘)
    graph.addEdge(‘E‘, ‘I‘)
    
    //4.测试dfs遍历顶点
    let result = ""
    graph.dfs(graph.vertexes[0], function(v){
      result += v + "-"
    })
    console.log(result);
    
测试结果:

    //封装图结构
    function Graph (){
      //属性:顶点(数组)/边(字典)
      this.vertexes = []  //顶点
      this.edges = new Dictionary() //边
      //方法
      //添加方法
      //一.添加顶点
      Graph.prototype.addVertex = function(v){
        this.vertexes.push(v)
        this.edges.set(v, []) //将边添加到字典中,新增的顶点作为键,对应的值为一个存储边的空数组
      }
      //二.添加边
      Graph.prototype.addEdge = function(v1, v2){//传入两个顶点为它们添加边
        this.edges.get(v1).push(v2)//取出字典对象edges中存储边的数组,并添加关联顶点
        this.edges.get(v2).push(v1)//表示的是无向表,故要添加互相指向的两条边
      }
      //三.实现toString方法:转换为邻接表形式
      Graph.prototype.toString = function (){
        //1.定义字符串,保存最终结果
        let resultString = ""
        //2.遍历所有的顶点以及顶点对应的边
        for (let i = 0; i < this.vertexes.length; i++) {//遍历所有顶点
          resultString += this.vertexes[i] + ‘-->‘
          let vEdges = this.edges.get(this.vertexes[i])
          for (let j = 0; j < vEdges.length; j++) {//遍历字典中每个顶点对应的数组
            resultString += vEdges[j] + ‘  ‘;
          }
          resultString += ‘\n‘
        }
        return resultString
      }
      //四.初始化状态颜色
      Graph.prototype.initializeColor = function(){
        let colors = []
        for (let i = 0; i < this.vertexes.length; i++) {
           colors[this.vertexes[i]] = ‘white‘;
        }
        return colors
      }
      //五.实现广度搜索(BFS)
      //传入指定的第一个顶点和处理结果的函数
      Graph.prototype.bfs = function(initV, handler){
        //1.初始化颜色
        let colors = this.initializeColor()
        //2.创建队列
        let que = new Queue()
        //3.将顶点加入到队列中
        que.enqueue(initV)
        //4.循环从队列中取出元素
        while(!que.isEmpty()){
          //4.1.从队列中取出一个顶点
          let v = que.dequeue()
          //4.2.获取和顶点相相邻的其他顶点
          let vNeighbours = this.edges.get(v)
          //4.3.将v的颜色变为灰色
          colors[v] = ‘gray‘
          //4.4.遍历v所有相邻的顶点vNeighbours,并且加入队列中
          for (let i = 0; i < vNeighbours.length; i++) {
            const a = vNeighbours[i];
            //判断相邻顶点是否被探测过,被探测过则不加入队列中;并且加入队列后变为灰色,表示被探测过
            if (colors[a] == ‘white‘) {
              colors[a] = ‘gray‘
              que.enqueue(a)
            }
          }
          //4.5.处理顶点v
          handler(v)
          //4.6.顶点v所有白色的相邻顶点都加入队列后,将顶点v设置为黑色。此时黑色顶点v位于队列最前面,进入下一次while循环时会被取出
          colors[v] = ‘black‘
        }
      }
      //六.实现深度搜索(DFS)
      Graph.prototype.dfs = function(initV, handler){
        //1.初始化顶点颜色
        let colors = this.initializeColor()
        //2.从某个顶点开始依次递归访问
        this.dfsVisit(initV, colors, handler)
      }
      //为了方便递归调用,封装访问顶点的函数,传入三个参数分别表示:指定的第一个顶点、颜色、处理函数
      Graph.prototype.dfsVisit = function(v, colors, handler){
        //1.将颜色设置为灰色
        colors[v] = ‘gray‘
        //2.处理v顶点
        handler(v)
        //3.访问v相连的其他顶点
        let vNeighbours = this.edges.get(v)
        for (let i = 0; i < vNeighbours.length; i++) {
          let a = vNeighbours[i];
          //判断相邻顶点是否为白色,若为白色,递归调用函数继续访问
          if (colors[a] == ‘white‘) {
            this.dfsVisit(a, colors, handler)
          }
          
        }
        //4.将v设置为黑色
        colors[v] = ‘black‘
      }
    }
标签:dfs 连线 出图 问题: string 常用方法 个数 图的遍历 状态
原文地址:https://www.cnblogs.com/AhuntSun-blog/p/12636692.html