廣度優先搜索(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上面每個節點都是黑色了。遍歷中間過程,每個節點入棧的時候是灰色的,出棧的時候是黑色的。