1、(圖的遍歷)深度優先和廣度優先node
廣度優先搜索(BFS)隊列實現 dom
-相似二叉樹的先序遍歷yii
越是接近根結點的結點將越早地遍歷。post
找到從起始結點到目標結點的路徑,特別是最短路徑。測試
廣度優先遍歷 BFS 從圖中某頂點v出發,在訪問了v以後依次訪問v的各個不曾訪問過的鄰接點,而後分別從這些鄰接點出發依次訪問它們的鄰接點,並使得「先被訪問的頂點的鄰接點先於後被訪問的頂點的鄰接點被訪問,直至圖中全部已被訪問的頂點的鄰接點都被訪問到。 若是此時圖中尚有頂點未被訪問,則須要另選一個不曾被訪問過的頂點做爲新的起始點,重複上述過程,直至圖中全部頂點都被訪問到爲止。ui
/* 廣度遍歷dom */ //非遞歸版本 //新建隊列和nodes列表 //隊列不爲空,取出第一個元素,push進nodes,循環添加children進隊列 //返回nodes let breadthTraversal = (node) =>{ let nodes = [] let queue = [] if(node){ queue.push(node) while(queue.length){ let item = queue.shift() let children = item.children nodes.push(item) // 隊列,先進先出 // nodes = [] queue= [parent] // nodes = [parent] queue= [child1,child2,child3] // nodes = [parent, child1] queue= [child2,child3,child1-1,child1-2] for(let i = 0;i<children.length;i++){ queue.push(children[i]) } } } return nodes }
深度優先搜索(DFS)棧實現spa
-相似二叉樹的層次遍歷3d
更早訪問的結點可能不是更靠近根結點的結點。code
所以,你在DFS 中找到的第一條路徑可能不是最短路徑。orm
假設初始狀態是圖中全部頂點均未被訪問,則從某個頂點v出發,首先訪問該頂點而後依次從它的各個未被訪問的鄰接點出發深度優先搜索遍歷圖,直至圖中全部和v有路徑相通的頂點都被訪問到。若此時尚有其餘頂點未被訪問到,則另選一個未被訪問的頂點做起始點,重複上述過程,直至圖中全部頂點都被訪問到爲止。
/* 深度遍歷dom */ //非遞歸版本 let deepTraversal = (node) => { let stack = [] let nodes = [] if (node) { // 推入當前處理的node stack.push(node) while (stack.length) { let item = stack.pop() let children = item.children nodes.push(item) // node = [] stack = [parent] // node = [parent] stack = [child3,child2,child1] // node = [parent, child1] stack = [child3,child2,child1-2,child1-1] // node = [parent, child1-1] stack = [child3,child2,child1-2] for (let i = children.length - 1; i >= 0; i--) { stack.push(children[i]) } } } return nodes } //遞歸版本 let deepTraversal1 = (node) => { let nodes =[] if(node!== null) { nodes.push(node) let children = node.children; for(let i = 0;i<children.length;i++){ nodes = nodes.concat(deepTraversal(children[i])) } } return nodes; } let parent = document.querySelector('.parent'); let joe = deepTraversal(parent); console.log(joe);
應用:搜索某個結點的路徑,添加path屬性(bfs使用shift(隊列),dfs使用pop(棧))
· bfs利用隊列實現,循環中作的是push => shift => push => shift
· dfs利用棧實現,循環中作的是push => pop => push => pop
/* 查找對應name路徑 廣度遍歷 隊列實現 bfs */ function bfs(target, name) { const queue = [...target] do { const current = queue.shift() if (current.children) { queue.push(...current.children.map(x => ({ ...x, path: (current.path || current.name) + '-' + x.name }))) } if (current.name === name) { return current//返回整個目標對象 } } while(queue.length) return undefined }
/* 查找對應name路徑 深度遍歷 棧實現 dfs */ function dfs(target,name){ const stack = [...target] do{ const current = stack.pop() if(current.children){ stack.push(...current.children.map(x => ({...x,path:(current.path || current.name) + '-' + x.name}))) } if(current.name === name){ return current.path//返回整個目標對象的path屬性 } }while(stack.length) return undefined }
/* 公共的搜索方法,mode默認bfs */ function commonSearch(target, name, mode) { const stackOrQueue = [...target] do { const current = stackOrQueue[mode === 'dfs' ? 'pop' : 'shift']() if (current.children) { stackOrQueue.push(...current.children.map(x => ({ ...x, path: (current.path || current.name) + '-' + x.name }))) } if (current.name === name) { return current } } while(stackOrQueue.length) return undefined }
/* 搜索地區測試用例 */ const data = [{ id: '1', name: '廣東省', children: [ { id: '12', name: '廣州市', children: [ { id:'121', name: '天河區' }, { id:'122', name: '荔灣區' } ] } ] }] console.log(dfs(data,'天河區'))
· 結果1:返回對象
· 結果2:返回對象的path屬性
2、(二叉樹的遍歷)前序中序後序
重點中的重點,最好同時掌握遞歸和非遞歸版本。
遞歸版本很容易書寫,可是真正考察基本功的是非遞歸版本。
· 二叉樹的前序遍歷(根節點N在左右子樹前)NLR - ABDECF
· 二叉樹的中序遍歷(根節點N在左右子樹中)LNR - DBEAFC
· 二叉樹的後序遍歷(根節點N在左右子樹後)LRN - DEBFCA
/* 遞歸寫法 */ /* 前序 */ const preorderTraversal = function (root, array=[]) { if(root) { array.push(root.val); preorderTraversal(root.left, array); preorderTraversal(root.right, array); } return array; } /* 中序 */ const inorderTraversal = function(root, array= []) { if(root) { inorderTraversal(root.left, array); array.push(root.val); inorderTraversal(root.right, array); } return array; } /* 後序 */ const postorderTraversal = function(root, array = []) { if(root){ postorderTraversal(root.left, array); postorderTraversal(root.right, array); array.push(root.val); } return array; } /* 非遞歸寫法 */ /* 前序 */ const preorderTraversal1 = function (root) { const result = []; const stack = []; let current = root; while(current||stack.length > 0){ while(current){ //取根節點值,左節點入棧,循環到左節點爲空 result.push(current.val); stack.push(current); current = current.left; } //節點出棧,再遍歷右節點 current = stack.pop(); current = current.right; } return result; } /* 中序 */ const inorderTraversal1 = function (root) { const result = []; const stack = []; let current = root; while(current||stack.length > 0){ while(current){ //左節點入棧,循環到左節點爲空 stack.push(current); current = current.left; } //節點出棧,取節點值,再遍歷右節點 current = stack.pop(); result.push(current.val); current = current.right; } return result; } /* 後序 */ const postorderTraversal1 = function (root) { const result = []; const stack = []; let last = null; let current = root; while(current || stack.length > 0) { while(current) { //左節點入棧,循環到左節點爲空 stack.push(current); current = current.left; } //取棧內最後一個節點,判斷是爲空或者已遍歷過,則將節點值傳入 current = stack[stack.length-1]; if(!current.right || current.right == last){ //節點出棧 current = stack.pop(); result.push(current.val); last = current; current = null; //繼續循環彈棧 } else { //取右節點作遍歷操做 current = current.right } } return result; } /* 測試用例 */ const data = { val:1, left:{ val:2, left:{ val:4, }, right:{ val:5 } }, right:{ val:3, left:{ val:6 }, right:{ val:7 } } } console.log('遞歸計算結果') console.log(preorderTraversal(data)) console.log(inorderTraversal(data)) console.log(postorderTraversal(data)) console.log('非遞歸計算結果') console.log(preorderTraversal1(data)) console.log(inorderTraversal1(data)) console.log(postorderTraversal1(data)) //遞歸計算結果 [ 1, 2, 4, 5, 3, 6, 7 ] [ 4, 2, 5, 1, 6, 3, 7 ] [ 4, 5, 2, 6, 7, 3, 1 ] //非遞歸計算結果 [ 1, 2, 4, 5, 3, 6, 7 ] [ 4, 2, 5, 1, 6, 3, 7 ] [ 4, 5, 2, 6, 7, 3, 1 ]