广度优先遍历基本思想
图的广度优先搜索:类似于一个分层搜索的过程,广度优先搜索需要使用一个队列以保持访问过的结点的顺序,以便按照这个顺序来访问这些结点的领结结点
对不深度优先搜索,深度优先搜索得到的路径不仅取决于图的结构,还取决于图的表示和递归调用的性质。我们很自然的对下面这些问题感兴趣:
单点最短路径,给定一幅图和一个起点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 }}