数据结构标准入门-BFS

251 阅读3分钟

广度优先遍历基本思想

图的广度优先搜索:类似于一个分层搜索的过程,广度优先搜索需要使用一个队列以保持访问过的结点的顺序,以便按照这个顺序来访问这些结点的领结结点

对不深度优先搜索,深度优先搜索得到的路径不仅取决于图的结构,还取决于图的表示和递归调用的性质。我们很自然的对下面这些问题感兴趣:

单点最短路径,给定一幅图和一个起点S,回答:从s到给定的顶点V之间是否存在一条路径?如果有,找出其中最短的那一条(所含边数最少)。等类似的问题【填昨天晚上的坑】

解决这个问题的经典方法叫做广度优先搜索(BFS),他是许多图算法的基石。深度优先搜索在这个问题上没有什么作为,因为他遍历图的整个顺序是为了找出最短路径,和目标没有什么关系。相比之下,关顾优先搜索正是为了这个目标才出现的。要找到从s到v的最短路径,从s开始,在所有由一条边就可以到达的顶点中寻找v,如果找不到我们就继续在s距离两条边的所有顶点中查找v,如此一直进行,深度优先搜索就好像一个人在走迷宫,广度优先搜索就好像一群人在走迷宫。每个人都有自己的绳子,当出现别的路时。可以假设一个探索者可以分裂为更多的人来搜索他们。当两个探索者相遇时。会合而为1并继续使用先到达者的轮子

广度优先遍历算法步骤

  • 访问结点V并且标记结点V为已访问

  • 结点v入队列

  • 当队列非空时,继续执行,否则算法结束

  • 出队列,取得队头结点u

  • 查找结点u中的第一个领结结点w

  • 若结点u的领结结点w不存在,则转到步骤3:否则循环执行以下三个步骤

  • 若结点w尚未被访问,则访问结点标记为已访问

  • 结点w入队列

  • 查找结点u的继w领结结点后的下一个领结结点w。转到步骤8

算法实现

// 创建一个图的对象function Graph(){  // 顶点  let ver=[]  // 边  let dic=new Map()  // 新增一个顶点  this.addVer=function(key){    ver.push(key)    dic.addKey(key,[])  }  // 新增边  this.addDic=function(key,value){    dic.findKey(key).push(value)    dic.findKey(value).push(key)  }  // 打印领结矩阵  this.toString=function(){    let s=''    for(let i=0;i<ver.length;i++){      s+=`${ver[i]}:`      let tmpArr=dic.findKey(ver[i])      for(let j=0;j<tmpArr.length;j++){        s+=`${tmpArr[j]},`      }      s+='\n'    }    return s;  }  // 获取一个顶点  this.getValue=function(key){    return ver.find(e=>{key===e})  }  this.getVer=function(){    return ver  }  this.getAdjByVer=function(key){    return dic.findKey(key)  }}// 创建一个字典class Map{  constructor(){    this.dataStore={}  }  addKey(key,value){    if(!(value instanceof Array)){      console.log("这玩意不是数组")      value=[]    }    this.dataStore[key]=value  }  findKey(key){    if(this.dataStore[key]===undefined){      this.addKey(key,[])    }    return this.dataStore[key]  }}class Queue{  constructor(){    this.dataStore=[]  }  getValue(){    return this.dataStore.pop()  }  pushValue(value){    this.dataStore.push(value)  }  isEmpty(){    return this.dataStore.length===0?true:false  }}class BFS{  constructor(G,s){    this.marked=new Boolean()    this.edgeTo={}    // 记录顶点    this.s=s    bfs(G,s)  }  bfs(G,s){    // 创建一个队列,存储被顶点带出来的边数据    let queue=new Queue()    // 设置s为顶点,那么s就为true    this.marked[s]=true    // 将s带入到队列当中去    queue.pushValue(s)    // 如果说队列不为空的话    while(!queue.isEmpty()){      // 将刚刚存进去的s出列      let v=queue.getValue()      // 获取到顶点对应的边      for(let item of G.getAdjByVer(v)){        // 如果说这个顶点没有被标记过的话        if(!marked[item]){          // edgeTo对象存储这个顶点的对应上一条边          this.edgeTo[item]=v          // 设置当前顶点已经被访问过了          this.marked[item]=true          // 并且将这个顶点对应的所有点都放入队列当中去          queue.pushValue(item)        }      }    }  }  // 判断顶点和这个点v之间是否有直接路径  hasPathTo(v){    return this.marked[v]  }  // 处理v点到达顶点需要经过哪些点  pathTo(v){    if(!this.hasPathTo(v))return null    let path=[]    for(let x=v;x!=s;x=this.edgeTo[x]){      path.push(x)    }    path.push(s)    return path  }}

视频讲解

www.bilibili.com/video/BV1MZ…