首先,咱們瞭解一下關於樹的基本知識:javascript
二叉樹:就是每一個節點最多隻能有兩個節點 一個是左側的節點 另外一個是右側的節點
二叉樹搜索又是一種特殊的二叉樹:只容許在左側存儲比父節點小的值 在右側存儲比父節點大的值,以下面的圖所示:java
由上圖能夠看出每個節點都有兩個指針,一個是指向左側節點,一個是指向右側節點node
由此,咱們能夠發現有關樹的規則:算法
接下來,咱們將經過javascript 建立一個二叉樹類,二叉樹是由一個個節點組成的,因此,在類中須要有一個節點類,根節點屬性,還有基本的操做方法,分別是 插入,刪除,遍歷(前序,中序,後序),查詢(最大值,最小值,和某一指定值)函數
//建立一個二叉樹類 function BinarySearchTree(key){ //節點類,有值,左節點,右節點三個公有屬性 var Node = function(key){ this.key = key; this.left = null; this.rigth = null; } //在樹中插入一個節點 this.insert = function(newNode){ if(root === null){ root = newNode; }else{ insertNode(root,newNode); } } //在樹中查找一個鍵,若是鍵存在就返回true this.search = function(key){ return searchNode(root,key); } //經過中序遍歷全部的節點 this.inOrderTraverse = function(){ inOrderTraverseNode(root,callback); } //經過先序遍歷全部的節點 this.preOrderTravers = function(){ preOrderTraverseNode(root,callback); } //經過後續遍歷全部的節點 this.postOrderTravers = function(){ postOrderTraverseNode(root,callback); } //返回樹中最小的鍵值 this.min = function(){ return minNode(root); } //返回樹中最大的鍵值 this.max = function(){ return maxNode(root); } //從樹中移除某一個鍵值 this.remove = function(key){ root = removeNode(root,key); } var root = null; }
首先,實現 insert方法,大體思路是這樣的:先判斷 根節點是否爲空,若是爲空的話,就直接將新的節點做爲根節點,若是不爲空的話就調用一個插入節點的私有方法。post
//建立插入節點的方法' this.insert = function(newNode){ if(root === null){ root = newNode; }else{ insertNode(root,newNode); } }
insertNode的具體實現是這樣的:this
var insertNode = function(node,newNode){ if(newNode.key < node.key){ if(node.left === null){ node.left = newNode; }else{ insertNode(node.left,newNode); } }else{ if(node.right === null){ node.right = newNode; }else{ insertNode(node.right,newNode); } } }
函數解釋:基本思想就是 將節點 他的左節點 和 他的右節點 看做是一個小的子樹,先判斷新節點的鍵值 是否小於節點的鍵值,若是小,就判斷左節點是否爲空,若是左節點爲空,就將新的節點直接賦值給左節點,若是左節點不爲空,就將左節點做爲搜索比較的基點,調用自身再進行搜索比較(遞歸算法),相反若是大於節點的鍵值,再判斷右節點是否爲空,若是爲空就直接將節點賦值給右節點,若是節點不爲空一樣調用自身。spa
接下來,咱們定義 中序遍歷的函數:
中序遍歷是二叉樹的一種遍歷方式之一,實質上就是以從小到大的順序訪問二叉樹,中序遍歷的一種應用就是對樹中存儲的值進行從小到大的排列,中序遍歷的輔助方法以下所述:指針
var inOrderTraverseNode = function(node,callback){ if(node !== null){ inOrderTraverseNode(node.left,callback);(1) callback(node.key);(2) inOrderTraverseNode(node.right.callback);(3) } }
我以一個例子畫了調用過程(簡單粗糙,後期會修改):code
先序遍歷的做用是打印樹的一個結構化文檔,他的輔助函數以下面所述:
var preOrderTraverseNode = function(node,callback){ if(node !== null){ callback(node.key); preOrderTraverseNode(node.left,callback); preOrderTraverseNode(node.right,callback); } }
調用過程圖 與 中序遍歷類似
後序遍歷的做用的計算樹的子目錄 和 父目錄的全部文件所佔空間的大小,其輔助函數的具體實現以下面所示:
var postOrderTraverseNode = function(node,callback){ if(node!== null){ postOrderTraverseNode(node.left,callback); postOrderTraverseNode(node.right.callback); callback(node.key); } }
以上咱們對三種遍歷方式作了具體的實現,接下來咱們完成對樹中的全部值進行搜索。
基於構建樹時所遵循的具體規則,咱們能夠看出在樹的最左端是樹的最小值,樹的最右端就是樹的最大值
那麼尋找最小值的輔助函數是這樣實現的:
//搜索最小值時的搜索函數 var minNode = function(node){ //判斷節點是否存在,若是存在就執行循環 if(node){ while(node && node.left !== null){ node = node.left; } return node.key; } return null; }
函數解釋:首先判斷所傳的節點(通常是根節點,看是否存在),若是存在的話就進行循環,直到找到最左端的節點(節點的左節點不存在,那麼這就是最左邊的節點),返回節點的全部值
尋找最大值的輔助函數是這樣實現的:
//搜索樹中的最大值函數 var maxNode = function(node){ if(node){ while(node && node.right !== null){ node = node.right; } return node.key; } return null; }
與尋找最小值相反,該函數是循環找最右邊的節點
搜索特定值得輔助函數以下:
//搜索特定值得輔助函數 var searchNode = fucntion(node,key){ var result; if(node === null){ result = false; } if(key < node.key){ searchNode(node.left,key); }else if(key > node.key){ searchNode(node.right,key); }else{ result = true; } return result; }
函數解釋:若是想要尋找的key值小於節點值就在節點的右側樹上尋找,在節點不爲空的狀況下回調該函數,縮小右側樹的尋找範圍,最後節點若是爲空就是沒有找到,反之,找到;大於節點值同理
最後,咱們完成移除節點的方法
根據子節點的數量,咱們將節點分爲三種狀況:
針對上面三種狀況,有三種刪除節點的方法
對於第一種狀況:由於葉子節點即沒有左節點,也沒有右節點,因此直接將該節點賦值爲null就能夠了,可是,單單這樣是遠遠不夠的,咱們還須要對其相應的指針進行處理,對於葉子節點來講,咱們須要處理葉子節點父節點的指針,使其的指針指向null,因此須要在執行完刪除之後返回當前所基於的node節點,層層返回後實質上是以node節點爲root的子樹
對於第二種狀況:移除只有一個節點時的節點,那麼節點的左側節點或者右側節點就能夠取代節點的位置
對於第三種狀況:當移除有兩個子節點的節點時,須要找到該節點右側樹上節點值最小的節點,並用該節點代替它,最後使其的右側節點指向刪除節點後的樹
//移除一個節點的輔助函數 var removeNode = function(node,key){ if(node === null){ return null; } //尋找節點是key值的節點 if(key < node.key){ node.left = removeNode(node.left,key); return node; }else if(key > node.key){ node.right = removeNode(node.right,key); return node; }else{ //第一種狀況——兩個葉子節點都沒有 if(node.left === null && node.right === null){ node = null; return node; } //第二種狀況——只有一個葉子節點 if(node.left === null){ node = node.right; return node; }else if(node.right === null){ node = node.left; return node; } //第三種狀況——有兩個葉子節點的節點 var min = minNode(node.right); node.key = min; node.right = removeNode(node.right,min); return node; } }
最後,咱們詳細解釋一下第三種狀況爲何會這樣寫,當該節點被刪除的時候,咱們須要尋找一個節點來代替,尋找哪一個節點呢?確定是尋找比他值大的節點,由於要保證左節點的值要小於尋找到的新值,因此咱們鎖定的目標是節點的右側樹,然而找的節點值還必須小於右節點的值,因此尋找右側樹的最小值,並將該節點刪除
以上是我對樹的理解和總結,本人接受各位同仁大神的指點,謝謝