前端: JavaScript 中的二叉樹算法實現
























接下來讓咱們一塊兒來探討js數據結構中的樹。這裏的樹類比現實生活中的樹,有樹幹,樹枝,在程序中樹是一種數據結構,對於存儲須要快速查找的數據非有用,它是一種分層數據的抽象模型。一個樹結構包含一系列存在父子關係的節點。每一個節點都有一個父節點以及零個或多個子節點。以下因此爲一個樹結構:)
前端



和樹相關的概念:1. 子樹:由節點和他的後代構成,如上圖標示處。2. 深度:節點的深度取決於它祖節點的數量,好比節點5有2個祖節點,他的深度爲2。3. 高度:樹的高度取決於全部節點深度的最大值。

二叉樹和二叉搜索樹介紹

二叉樹中的節點最多隻能有2個子節點,一個是左側子節點,一個是右側子節點,這樣定義的好處是有利於咱們寫出更高效的插入,查找,刪除節點的算法。
二叉搜索樹是二叉樹的一種,可是它只容許你在左側子節點存儲比父節點小的值,但在右側節點存儲比父節點大的值。接下來咱們將按照這個思路去實現一個二叉搜索樹。

1. 建立BinarySearchTree類

這裏咱們將使用構造函數去建立一個類:
function BinarySearchTree(){
    // 用於建立節點的類
    let Node = function(key{
        this.key = key;
        this.left = null;
        this.right = null;
    }
    // 根節點
    let root = null;
}

咱們將使用和鏈表相似的指針方式去表示節點之間的關係,若是不瞭解鏈表,請看我後序的文章《如何實現單向鏈表和雙向鏈表》。node

2.插入一個鍵

// 插入一個鍵
this.insert = function(key{
    let newNode = new Node(key);
    root === null ? (root = newNode) : (insertNode(root, newNode))
}

向樹中插入一個新的節點主要有如下三部分:1.建立新節點的Node類實例 --> 2.判斷插入操做是否爲根節點,是根節點就將其指向根節點 --> 3.將節點加入非根節點的其餘位置。web

insertNode的具體實現以下:算法

function insertNode(node, newNode){
    if(newNode.key < node.key) {
        node.left === null ? (node.left = newNode) : (insertNode(node.left, newNode))
    }else {
        node.right === null ? (node.right = newNode) : (insertNode(node.right, newNode))
    }
}

這裏咱們用到遞歸,接下來要實現的search,del等都會大量使用遞歸,因此說不了解的能夠先自行學習瞭解。咱們建立一個二叉樹實例,來插入一個鍵:微信

let tree = new BinarySearchTree();
tree.insert(20);
tree.insert(21);
tree.insert(520);
tree.insert(521);
插入的結構會按照二叉搜索樹的規則去插入,結構相似於上文的第一個樹圖。

樹的遍歷

訪問樹的全部節點有三種遍歷方式:中序,先序和後序。
  • 中序遍歷:以從最小到最大的順序訪問全部節點
  • 先序遍歷:以優先於後代節點的順序訪問每一個節點
  • 後序遍歷:先訪問節點的後代節點再訪問節點自己
根據以上的介紹,咱們能夠有如下的實現代碼。
  1. 中序排序
this.inOrderTraverse = function(cb){
    inOrderTraverseNode(root, cb);
}

// 輔助函數
function inOrderTraverseNode(node, cb){
    if(node !== null){
        inOrderTraverseNode(node.left, cb);
        cb(node.key);
        inOrderTraverseNode(node.right, cb);
    }
}
使用中序遍歷能夠實現對樹進行從小到大排序的功能。
  1. 先序排序
// 先序排序 --- 優先於後代節點的順序訪問每一個節點
   this.preOrderTraverse = function(cb{
     preOrderTraverseNode(root, cb);
   }

   // 先序排序輔助方法
   function preOrderTraverseNode(node, cb{
     if(node !== null) {
       cb(node.key);
       preOrderTraverseNode(node.left, cb);
       preOrderTraverseNode(node.right, cb);
     }
   }
使用先序排序能夠實現結構化輸出的功能。
  1. 後序排序
// 後續遍歷 --- 先訪問後代節點,再訪問節點自己
   this.postOrderTraverse = function(cb{
     postOrderTraverseNode(root, cb);
   }

   // 後續遍歷輔助方法
   function postOrderTraverseNode(node, cb{
     if(node !== null){
       postOrderTraverseNode(node.left, cb);
       postOrderTraverseNode(node.right, cb);
       cb(node.key);
     }
   }
後序遍歷能夠用於計算有層級關係的全部元素的大小。

搜索樹中的值

在樹中有三種常常執行的搜索類型:最大值,最小值,特定的值。
  1. 最小值
最小值經過定義能夠知道便是左側樹的最底端的節點,具體實現代碼以下:
// 最小值
   this.min = function(){
     return minNode(root)
   }

    function minNode(node{
     if(node) {
       while(node && node.left !== null){
         node = node.left;
       }
       return node.key
     }
     return null
   }
類似的,實現最大值的方法以下:
// 最大值
   this.max = function({
     return maxNode(root)
   }

   function maxNode(node{
     if(node){
       while(node && node.right !== null){
         node = node.right;
       }
       return node.key
     }
     return null
   }
2.搜索一個特定的值
// 搜索樹中某個值
this.search = function(key{
    return searchNode(root, key)
}

// 搜索輔助方法
function searchNode(node, key){
    if(node === null) {
        return false
    }
    if(key < node.key) {
        return searchNode(node.left, key)
    } else if(key > node.key) {
        return searchNode(node.right, key)
    }else {
        return true
    }
}
  1. 移除一個節點
this.remove = function(key){
    root = removeNode(root, key);
}

// 發現最小節點
function findMinNode(node{
    if(node) {
    while(node && node.left !== null){
        node = node.left;
    }
    return node
    }
    return null
}

// 移除節點輔助方法
function removeNode(node, key{
    if(node === null) {
    return null
    }

    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
    }

    // 有兩個子節點的節點
    let aux = findMinNode(node.right);
    node.key = aux.key;
    node.right = removeNode(node.right, aux.key);
    return node
    }
}

刪除節點須要考慮的狀況比較多,這裏咱們會使用和min相似的實現去寫一個發現最小節點的函數,當要刪除的節點有兩個子節點時,咱們要將當前要刪除的節點替換爲子節點中最大的一個節點的值,而後將這個子節點刪除。數據結構

至此,一個二叉搜索樹已經實現,可是還存在一個問題,若是樹的一遍很是深,將會存在必定的性能問題,爲了解決這個問題,咱們能夠利用AVL樹,一種自平衡二叉樹,也就是說任何一個節點的左右兩側子樹的高度之差最多爲1。函數

若是想學習更多js算法和數據結構,能夠繼續觀看哦~
post


點個在看,你最好看性能

本文分享自微信公衆號 - 趣談前端(beautifulFront)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。學習

相關文章
相關標籤/搜索