广度优先搜索(BFS)和深度优先搜索(DFS),你们可能在oj上见过,各类求路径、最短路径、最优方法、组合等等。因而,咱们不妨动手试一下js版本怎么玩。javascript
队列是先进先出,后进后出,经常使用的操做是取第一个元素(shift)、尾部加入一个元素(push)。java
栈是后进先出,就像一个垃圾桶,后入的垃圾先被倒出来。经常使用的操做是,尾部加入元素(push),尾部取出元素(pop)node
BFS是靠一个队列来辅助运行的。顾名思义,广度搜索,就是对于一个树形结构,咱们一层层节点去寻找目标节点。
按照这个顺序进行广度优先遍历,明显是队列能够完美配合整个过程:算法
到了最后,队列清空,树也遍历了一次数组
假设有几个点,咱们须要设计一个算法,断定两个点有没有相通测试
假设点12345是这样的结构:spa
问:1能不能到达5设计
显然咱们一眼看上去是不会到达的,若是是设计算法的话,怎么作呢?调试
咱们把点之间的关系用一个矩阵表示,0表示不链接,n行m列的1表示点n通向点mcode
var map = [ [0,1,1,0,0], [0,0,1,1,0], [0,1,1,1,0], [1,0,0,0,0], [0,0,1,1,0] ]
function bfs(arr,start,end){ var row = arr.length var quene = [] var i = start var visited = {}//记录遍历顺序 var order = [] //记录顺序,给本身看的 quene.push(i) //先把根节点加入 while(quene.length){ //若是队列没有被清空,也就是还没遍历完毕 for(var j = 0;j<row;j++){ if(arr[i][j]){ //若是是1 if(!visited[j]){ quene.push(j)//队列加入未访问 } } } quene.shift()//取出队列第一个 visited[i] = true//记录已经访问 while(visited[quene[0]]){ quene.shift() } order.push(i)//记录顺序 i = quene[0] } return {visited:visited,result:!!visited[end],order:order} } bfs(map,0,4)
举个例子,3月24号今日头条笔试题第二题的最少操做步数:
定义两个字符串变量:s和m,再定义两种操做,
第一种操做:
m = s;
s = s + s;
第二种操做:
s = s + m;
假设s, m初始化以下:
s = "a";
m = s;
求最小的操做步骤数,能够将s拼接到长度等于n
输入一个整数n,代表咱们须要获得s字符长度,0<n<10000
案例:
in:
6
out:
3
思路:利用广度优先搜索,假设左节点是操做1,右节点是操做2,这样子就造成了操做树。利用bfs的规则,把上层的父节点按顺序加入队列,而后从前面按顺序移除,同时在队列尾部加上移除的父节点的子节点。我这里,先把父节点拿出来对比,他的子节点放在temp,对比完了再把子节点追加上去
每一个节点分别用两个数记录s,m。发现第一次两种操做是同样的,因此我就略去右边的了
function bfs(n){ if(n<2||n!==parseInt(n)||typeof n !=='number') return if(n==2) return 1 var quene = [[2,1]]//从2开始 var temp = []//存放父节点队列的子节点 var count = 0 var state = false//判断是否结束循环 while(!state){ while(quene.length){//若是队列不是空,从前面一个个取,并把他的子节点放在temp var arr = quene.pop() if(arr[0]==n){//找到了直接结束 state = true break } temp.push([arr[0]*2,arr[1]*2]) temp.push([arr[0]+arr[1],arr[1]]) } count++//队列已经空,说明这层的节点已经所有检索完,并且子节点也保存好了 quene = [...temp]//队列是子节点全部的元素集合,重复前面操做 temp = [] } return count }
DFS着重于这个搜索的过程,通常以“染色”的形式配合栈来运行,也比较完全。V8老生代的垃圾回收机制中的标记-清除也利用了DFS。咱们定义三种颜色:黑白灰,白色是未处理过的,灰是已经通过了但没有处理,黑色是已经处理过了
仍是前面那幅图
咱们用两个数组,一个是栈,一个是保存咱们遍历顺序的,数组的元素拿到的都是原对象树的引用,是会改变原对象的节点颜色的
咱们能够看到,入栈的时候,从白色染灰色,出栈的时候,从灰色到黑色。整个过程当中,染黑的顺序相似于二叉树的后序遍历
v8的垃圾回收,将持有引用的变量留下,没有引用的变量清除。由于若是持有引用,他们必然在全局的树中被遍历到。若是没有引用,那这个变量必然永远是白色,就会被清理
咱们用对象来表示上面这棵树:
var tree = { val: 1, children: [ {val: 2,children: [{val:5,children:null,color:'white'},{val: 6,children:null,color:'white'}],color:'white'}, {val: 3,children: [{val: 7,children:null,color:'white'}],color:'white'}, {val: 4,children: [{val:8,children:null,color:'white'},{val: 9,children:null,color:'white'}],color:'white'} ], color: 'white' }
开始咱们的DFS:
function dfs ( tree ) { var stack = []//记录栈 var order = []//记录遍历顺序 !function travel (node) { stack.push(node)//入栈 node.color = 'gray' console.log(node) if(!node.children) {//没有子节点的说明已经遍历到底 node.color = 'black' console.log(node) stack.pop() order.push(node) return } var children = node.children children.forEach(child=>{ travel(child) }) node.color = 'black' stack.pop()//出栈 order.push(node) console.log(node) }(tree) return order }
过程用递归比较简单,上面大部分代码都是调试代码,本身能够改一下测试其余的相似场景。遍历完成后,tree上面每个节点都是黑色了。遍历中间过程,每个节点入栈的时候是灰色的,出栈的时候是黑色的。