JavaScript 二叉搜索樹以及實現翻轉二叉樹

本文包括:二叉搜索樹(建立、遍歷、搜索、插入等)、JavaScript 實現翻轉二叉樹node

歡迎關注個人:我的博客Githubgit

什麼是二叉樹?

二叉樹的定義:二叉樹的每一個結點至多隻有二棵子樹(不存在度大於2的結點),二叉樹的子樹有左右之分,次序不能顛倒。github

二叉查找樹(BST):又稱爲是二叉排序樹(Binary Sort Tree)或二叉搜索樹。二叉查找樹是二叉樹的一種,可是它只容許你在左側節點存儲(比父節點)小的值,在右側節點存儲(比父節點)大(或者等於)的值。算法

建立一個二叉查找樹

首先建立一個 BinarySearchTree 類。bash

// 使用了 ES6 的 Class 語法
class BinarySearchTree {
  constructor() {
    this.root = null
  }

  Node(key) {
    let left = null
    let right = null
    return {
      key,
      left,
      right
    }
  }
}
複製代碼

來看一下二叉查找樹的數據結構組織方式(沒有找到二叉搜索樹的先用二叉樹的代替一下):數據結構

二叉樹是經過指針(指向下一個節點)來表示節點之間的關係的,因此須要在聲明 Node 的時候,定義兩個指針,一個指向左邊,一個指向右邊。 還須要聲明一個 root 來保存樹的根元素。函數

向樹中插入一個鍵(節點)

class BinarySearchTree {
  // ...省略前面的
  
  insert (key) {
    let newNode = this.Node(key)
    if (this.root === null) {
      // 若是根節點爲空,那麼插入的節點就爲根節點
      this.root = newNode
    } else {
      // 若是根節點不爲空
      this.insertNode(this.root, newNode)
    }
  }

  insertNode (node, newNode) {
    // 當新節點比父節點小,插入左邊
    if (newNode.key < node.key) {
      // 左邊沒有內容則插入
      if (node.left === null) {
        node.left = newNode
      } else {
        // 有內容就繼續遞歸,直到沒有內容而後能夠插入
        this.insertNode(node.left, newNode)
      }
    } else {
      // 右邊和左邊相同,不重複說明
      if (node.right === null) {
        node.right = newNode
      } else {
        this.insertNode(node.right, newNode)
      }
    }
  }
}
複製代碼

由於使用了 class 因此沒有學過 class 的同窗能夠先看一下 ES6 的 class,再來看文章。post

仔細分析上面的代碼,多看幾遍就能夠了解其中的奧妙(也能夠本身在遊覽器中運行一下,插入幾個值試一下)。學習

運行一遍試一下:ui

let m = new BinarySearchTree()
m.insert(5)
m.insert(4)
m.insert(3)
m.insert(6)
m.insert(7)
複製代碼

會獲得這樣的結構:

{
  key: 5,
  left: {
    key: 4,
    left: {
      key: 3,
      left: null,
      right: null
    },
    right: null
  },
  right: {
    key: 6,
    left: null,
    right: {
      key: 7,
      left: null,
      right: null
    }
  }
}
複製代碼

emmm,真複雜(本身看的都頭暈),仍是畫個圖吧。

會生成這樣一個二叉查找樹~,插入功能就算完成啦!

樹的遍歷

遍歷一棵樹是指訪問樹的每一個節點並對它們進行某種操做的過程。訪問樹會有三種方法:中序、先序、後續。下面分別講解

中序遍歷

中序遍歷是一種以上行順序訪問 BST 全部節點的遍歷方式,也就是從最小到最大的順序進行訪問全部節點。具體方法,看代碼吧,配上圖多看兩遍代碼就能明白了(我是這麼認爲的)。

class BinarySearchTree {
  // ...省略前面的
  
  inOrderTraverse (callback) {
    this.inOrderTraverseNode(this.root, callback)
  }
  
  inOrderTraverseNode (node, callback) {
    if (node !== null) {
      this.inOrderTraverseNode(node.left, callback)
      callback(node.key)
      this.inOrderTraverseNode(node.right, callback)
    }
  }
}
複製代碼

一樣,用圖展現一下遍歷的過程,具體過程看代碼多思考一下。

先序遍歷

先序遍歷會先訪問節點自己,而後再訪問它的左側子節點,最後再訪問右側的節點。

class BinarySearchTree {
  // ...省略前面的
  
  preOrderTraverse (callback) {
    this.preOrderTraverseNode(this.root, callback)
  }
  
  preOrderTraverseNode (node, callback) {
    if (node !== null) {
      callback(node.key)
      this.preOrderTraverseNode(node.left, callback)
      this.preOrderTraverseNode(node.right, callback)
    }
  }
}
複製代碼

仔細看代碼,發現和中序遍歷的區別不過是先執行了 callback 而後再遍歷左右。

後序遍歷

後序遍歷則是先訪問節點的後代節點,而後再訪問節點自己。實現:

class BinarySearchTree {
  // ...省略前面的
  
  postOrderTraverse (callback) {
    this.postOrderTraverseNode(this.root, callback)
  }
  
  postOrderTraverseNode (node, callback) {
    if (node !== null) {
      this.postOrderTraverseNode(node.left, callback)
      this.postOrderTraverseNode(node.right, callback)
      callback(node.key)
    }
  }
}
複製代碼

再仔細看代碼,發現和中序遍歷的區別不過是先執行了遍歷了左右,最後執行了 callback

慣例,畫張圖~

三種遍歷方式講完啦,不懂的能夠多看幾遍代碼哦~

搜索二叉搜索樹中的值

在樹中,一般有三種常常使用的搜索類型:

  • 搜索最大值
  • 搜索最小值
  • 搜索特定值

下面一一列舉

搜索最小和最大值

首先咱們知道二叉搜索樹中的最小值在最左邊,最大值在最右邊。既然知道這個,那麼實現搜索最大和最小就十分簡單了。因此直接上代碼:

class BinarySearchTree {
  // ...省略前面的
  
  // 搜索最小
  min () {
    return this.minNode(this.root)
  }
  
  minNode (node) {
    if (node) {
      // 若是節點存在,並且左邊不爲 null
      while (node && node.left !== null) {
        node = node.left
      }
      
      return node.key
    }
    
    // 若是樹爲空,則返回 null
    return null
  }
  
  // 搜索最大
  max () {
    return this.maxNode(this.root)
  }
  
  maxNode (node) {
    if (node) {
      while (node && node.right !== null) {
        node = node.right
      }
      return node.key
    }
    
    return null
  }
}
複製代碼

搜索特定的值

基本上的思路和遍歷節點差很少,具體看代碼。

class BinarySearchTree {
  // ...省略前面的
  
  search (key) {
  	return this.searchNode(this.root, key)
  }
  
  searchNode (node, key) {
    if (node === null) {
      return false
    }
    
    // 若是 key 比節點的值小,那麼搜索左邊的子節點,下面的相反
    if (key < node.key) {
      return this.searchNode(node.left, key)
    } else if (key > node.key) {
      return this.searchNode(node.right, key)
    } else {
      return true
    }
  }
}
複製代碼

翻轉二叉樹

翻轉一個二叉樹,直觀上看,就是把二叉樹的每一層左右順序倒過來。

例如:

Input:

4
   /   \
  2     7
 / \   / \
1   3 6   9
複製代碼

Output:

4
   /   \
  7     2
 / \   / \
9   6 3   1
複製代碼

仔細看就是先把最底下的節點反轉,而後上一個節點再翻轉。例如:1 - 3 反轉成 3 - 1,6 - 9 反轉成 9 - 6, 而後再讓 2 - 7 反轉。固然反過來也同樣,先反轉 2 - 7 也是能夠的。

因此具體的過程是:

  1. 翻轉根節點的左子樹(遞歸調用當前函數)
  2. 翻轉根節點的右子樹(遞歸調用當前函數)
  3. 交換根節點的左子節點與右子節點

最後看一下實現的代碼:

class BinarySearchTree {
  // ...省略前面的
 
  invertTree (node = this.root) {
    if (node === null) {
      return
    }
    this.invertTree(node.left)
    this.invertTree(node.right)
    this.exchange(node)
  }
  
  exchange (node) {
    let temp = node.left
    node.left = node.right
    node.right = temp
  }
}
複製代碼

這樣就簡單實現啦,舒服舒服~

代碼

所有代碼在這裏~

class BinarySearchTree {
  constructor() {
    this.root = null
  }

  Node(key) {
    let left = null
    let right = null
    return {
      key,
      left,
      right
    }
  }

  insert(key) {
    let newNode = this.Node(key)
    if (this.root === null) {
      // 若是根節點爲空,那麼插入的節點就爲根節點
      this.root = newNode
    } else {
      this.insertNode(this.root, newNode)
    }
  }

  insertNode(node, newNode) {
    console.log(node)
    if (newNode.key < node.key) {
      if (node.left === null) {
        node.left = newNode
      } else {
        this.insertNode(node.left, newNode)
      }
    } else {
      if (node.right === null) {
        node.right = newNode
      } else {
        this.insertNode(node.right, newNode)
      }
    }
  }

  inOrderTraverse(callback) {
    this.inOrderTraverseNode(this.root, callback)
  }

  inOrderTraverseNode(node, callback) {
    if (node !== null) {
      this.inOrderTraverseNode(node.left, callback)
      callback(node.key)
      this.inOrderTraverseNode(node.right, callback)
    }
  }

  preOrderTraverse(callback) {
    this.preOrderTraverseNode(this.root, callback)
  }

  preOrderTraverseNode(node, callback) {
    if (node !== null) {
      callback(node.key)
      this.preOrderTraverseNode(node.left, callback)
      this.preOrderTraverseNode(node.right, callback)
    }
  }
  postOrderTraverse(callback) {
    this.postOrderTraverseNode(this.root, callback)
  }

  postOrderTraverseNode(node, callback) {
    if (node !== null) {
      this.postOrderTraverseNode(node.left, callback)
      this.postOrderTraverseNode(node.right, callback)
      callback(node.key)
    }
  }

  // 搜索最小
  min() {
    return this.minNode(this.root)
  }

  minNode(node) {
    if (node) {
      // 若是節點存在,並且左邊不爲 null
      while (node && node.left !== null) {
        node = node.left
      }

      return node.key
    }

    // 若是樹爲空,則返回 null
    return null
  }

  // 搜索最大
  max() {
    return this.maxNode(this.root)
  }

  maxNode(node) {
    if (node) {
      while (node && node.right !== null) {
        node = node.right
      }

      return node.key
    }

    return null
  }
  
  search(key) {
    return this.searchNode(this.root, key)
  }

  searchNode(node, key) {
    console.log('node-', node, '---', node === null, '-key-', key)
    if (node === null) {
      return false
    }
    // 若是 key 比節點的值小,那麼搜索左邊的子節點,下面的相反
    if (key < node.key) {
      return this.searchNode(node.left, key)
    } else if (key > node.key) {
      return this.searchNode(node.right, key)
    } else {
      console.log('didi')
      return true
    }
  }

  invertTree (node = this.root) {
    if (node === null) {
      return
    }
    this.invertTree(node.left)
    this.invertTree(node.right)
    this.exchange(node)
  }
  
  exchange (node) {
    let temp = node.left
    node.left = node.right
    node.right = temp
  }
}
複製代碼

最後

文章是本身的學習的一個記錄,若是可以順便幫助你們學習一下,那就再好不過了。

可是由於本人技術技術有限,因此文章不免會有疏漏,歡迎指出。

參考

相關文章
相關標籤/搜索