數據結構——樹

首先,咱們瞭解一下關於樹的基本知識:javascript

  1. 每個樹都包含一系列的父子關係的節點,每一個節點都有一個父節點和若干的子節點(零個 或者 多個)
  2. 沒有父節點的節點稱做是根節點
  3. 一個節點 能夠有祖先 和 後代,子樹由節點和他的後代構成,節點的一個屬性是深度:深度就是一個節點祖先節點的數量
  4. 、樹的高度是 全部節點深度中的最大值

二叉樹和二叉樹的搜索

二叉樹:就是每一個節點最多隻能有兩個節點 一個是左側的節點 另外一個是右側的節點
二叉樹搜索又是一種特殊的二叉樹:只容許在左側存儲比父節點小的值 在右側存儲比父節點大的值,以下面的圖所示:
圖片描述java

由上圖能夠看出每個節點都有兩個指針,一個是指向左側節點,一個是指向右側節點node

由此,咱們能夠發現有關樹的規則:算法

  1. 樹的左側全部的節點的值確定是小於根節點的值,樹的右側確定是大於根節點的值
  2. 因此當刪除一個擁有兩個節點的節點時,爲了遵循樹的規律,須要找到節點右側樹的最小節點值

接下來,咱們將經過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值小於節點值就在節點的右側樹上尋找,在節點不爲空的狀況下回調該函數,縮小右側樹的尋找範圍,最後節點若是爲空就是沒有找到,反之,找到;大於節點值同理

最後,咱們完成移除節點的方法
根據子節點的數量,咱們將節點分爲三種狀況:

  1. 葉子節點,既沒有左節點也沒有右節點
  2. 只有一個節點,左節點 或者 右節點
  3. 有兩個節點左節點和右節點

針對上面三種狀況,有三種刪除節點的方法
對於第一種狀況:由於葉子節點即沒有左節點,也沒有右節點,因此直接將該節點賦值爲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;        
       }
     
     }

最後,咱們詳細解釋一下第三種狀況爲何會這樣寫,當該節點被刪除的時候,咱們須要尋找一個節點來代替,尋找哪一個節點呢?確定是尋找比他值大的節點,由於要保證左節點的值要小於尋找到的新值,因此咱們鎖定的目標是節點的右側樹,然而找的節點值還必須小於右節點的值,因此尋找右側樹的最小值,並將該節點刪除

以上是我對樹的理解和總結,本人接受各位同仁大神的指點,謝謝

相關文章
相關標籤/搜索