樹是一種分層數據的抽象模型。最多見的樹是家譜。(圖來自網絡)$h _r$javascript
在明代世系表這棵樹中,全部的皇帝都被稱爲節點。朱元璋稱爲根節點。後代是皇帝的節點,稱爲內部節點。沒有子元素的節點好比明思宗朱由檢稱爲外部節點或葉節點。朱棣及其後代節點稱爲朱元璋的子樹。java
以明宣宗朱瞻基爲例子,他擁有三個祖先節點。所以他的深度爲3。node
樹的高度取決於節點深度的最大值。根節點出於第0層。朱棣屬於第二層。以此類推。整個世系表中,他的高度爲12。json
二叉樹最多隻能有·2個子節點。後端
如:B爲A的左側子節點。E爲A的右側子節點。網絡
二叉搜索樹(BST)是一種特殊的節點。左側子節點存放比父節點小的值。右側子節點存放大於等於父節點的值、數據結構
js建立一棵二叉樹(BinarySearchTree),能夠借鑑鏈表的思路ide
還記得鏈表(linkList)嗎,能夠經過指針來表示節點之間的關係。同時,還能夠用對象來實現這個二叉樹,函數
實現如下功能:工具
// 樹 class BinarySearchTree{ constructor(){ this.Node=function(key){ this.key=key; this.left=null; this.right=null; } this.root=null this.insertNode=this.insertNode.bind(this) } insertNode(_root,_node){ if(_root.key>_node.key){ if(_root.left==null){ _root.left=_node; }else{ this.insertNode(_root.left,_node); } }else{ if(_root.right==null){ _root.right=_node; }else{ this.insertNode(_root.right,_node) } } } // 插入 insert(key){ let Node=this.Node; let node=new Node(key); if(this.root==null){ this.root=node; }else{ this.insertNode(this.root,node) } } }
跑一下測試用例:
let a=new BinarySearchTree(); a.insert(11) a.insert(7) a.insert(15) a.insert(5) a.insert(3) a.insert(9) a.insert(8) a.insert(10) a.insert(13) a.insert(12) a.insert(14) a.insert(20) a.insert(18) a.insert(25)
輸出結果轉化以後:
遍歷一棵樹,應當從頂層,左層仍是右層開始?
遍歷的方法須要以訪問者模式(回調函數)體現。
樹方法最經常使用的就是遞歸。那麼應如何設計?
中序遍歷的順序是「從最小到最大」。
// 中序遍歷 inOrderTraverse(callback){ // 中序遍歷所需的必要方法 const inOrderTraverseNode=(_root,_callback=()=>{})=>{ // 從頂層開始遍歷 if(_root!==null){ inOrderTraverseNode(_root.left,_callback); _callback(_root.key); inOrderTraverseNode(_root.right,_callback); } } inOrderTraverseNode(this.root,callback); }
打印結果發現,其實這個遍歷實現了樹的key值從小到大排列。
a.inOrderTraverse((key)=>{console.log(key)}) // 3 5 6 7 8 9 10 11 12 13 14 15 18 20 25
先序遍歷的過程:
先把左側子節點所有訪問完了,再尋找一個距此時位置(「親緣關係」)最近的右側節點。
preOrderTraverse(callback){ // 中序遍歷所需的必要方法 const preOrderTraverseNode=(_root,_callback=()=>{})=>{ // 從頂層開始遍歷 if(_root!==null){ _callback(_root.key); preOrderTraverseNode(_root.left,_callback); preOrderTraverseNode(_root.right,_callback); } } preOrderTraverseNode(this.root,callback); }
因此,所謂先序遍歷就是把callback的位置提早了。
後續遍歷是先訪問一個樹的後代節點。最後才訪問自己。
那麼後序遍歷的方法是否是把callback放到最後執行呢?
是的。簡直無腦。
// 後序遍歷 postOrderTraverse(callback){ // 中序遍歷所需的必要方法 const postOrderTraverseNode=(_root,_callback=()=>{})=>{ // 從頂層開始遍歷 if(_root!==null){ postOrderTraverseNode(_root.left,_callback); postOrderTraverseNode(_root.right,_callback); _callback(_root.key);//我在後面 } } postOrderTraverseNode(this.root,callback); }
//是否存在 search(_key,_root){ if(!_root){ _root=this.root } if(!_root){ return false; }else if(_root.key==_key){ return true; } if(_root.key>_key){ if(_root.left==null){ return false; }else{ if(_root.left.key==_key){ return true }else{ return this.search(_key,_root.left) } } }else{ if(_root.right==null){ return false }else{ if(_root.right.key==_key){ return true }else{ return this.search(_key,_root.right) } } } }
// 工具函數 find(_root,side){ if(!_root[side]){ return _root.key }else{ return this.find(_root[side],side) } } // 最大值,不斷查找右邊 max(){ return this.find(this.root,'right') } // 最小值 min(){ return this.find(this.root,'left') }
會發現這是個很是輕鬆的事。
Bst最麻煩的方法莫過於此。
首先,你得找到這個節點=>遞歸終止的條件
其次,判斷這個節點(_root)的父節點(parentNode)和這個節點的子節點(_root.left、_root.right)判斷:
_root
沒有子節點,那麼直接把父節點對應的side值設爲null
_root
擁有一個子節點,跳過這個節點,直接把父節點的指針指向這個子節點。若是兩個都有:
_root
右邊子樹的最小節點_node
,而後令parentNode的指針指向這個節點_remove(_node,_key,parentNode,side){ if(_key<_node.key){ return this._remove(_node.left,_key,_node,'left') }else if(_key>_node.key){ return this._remove(_node.right,_key,_node,'right') }else if(_node.key==_key){ // 頂層:移除根節點 if(!parentNode){ this.root=null; return this.root; }else{ if(!_node.left&&!_node.right){ // 刪除的若是是葉節點 parentNode[side]=null }else if(_node.left&&!_node.right){ let tmp=_node.left; parentNode[side]=tmp }else if(_node.right&&!_node.left){ let tmp=_node.right; parentNode[side]=tmp }else{ let tmpRight=_node.right; // 找到右側子樹的最小節點。__node let __node=this.find(tmpRight,'left'); // 刪除這個節點。 this._remove(tmpRight,__node.key); // 從新賦值 parentNode[side]=__node.key; } return this.root } } } remove(key){ if(this.search(key)){ return this._remove(this.root,key) }else{ console.log('未找到key') return false; } } a.remove(15)
打印結果以下
測試經過。
在實際工做生活中,好比一本書常分爲第一講,第1-1節,第2-1節...,第二講:第2-1節...
若是後端發給你一個這樣的數據:
let data = [{ id: '1', children: [{ id: `1-1`, children: [{ id: '1-1-1', children: [{ id: '1-1-1-1' },{ id:'1-1-1-2' }] },{ id:'1-1-2', children: [{ id: '1-1-2-1' },{ id:'1-1-2-2' }] }] },{ id:'2', children:[{ id:'2-1' },{ id:'2-2', children:[{ id:'2-2-1' },{ id:'2-2-2', children: [{ id: '2-2-2-1' },{ id:'2-2-2-2' }] }] }] }] }]
如何扁平化以下的json對象?
const flatJson=(_data,arr)=>{ if(!arr){ arr=[] } for(let i=0;i<_data.length;i++){ console.log(_data[i].id) arr.push(_data[i].id); if(_data[i].children){ flatJson(_data[i].children,arr) } } return arr; } console.log(flatJson(data))
測試用例結果經過:
能夠進一步思考:這裏arr.push()在判斷前執行。若是是在判斷後執行,會是什麼結果呢?