js數據結構和算法(7)-二叉樹

7-二叉樹(第10章)node

樹是計算機科學中常常用到的一種數據結構。樹是一種非線性的數據結構,以分層的方式存儲數據 。本章將研究一種特殊的樹: 二叉查找樹。算法

7.1 什麼是樹數據結構

樹是一種數據結構,由一組以邊線鏈接的節點組成。如下就是一顆普通的樹函數

樹的節點:一棵樹最上面的節點稱爲根節點,若是一個節點下面鏈接多個節點,那麼該節點稱爲父節點,它下面的節點稱爲子節點。一個節點能夠有 0 個、 1 個或多個子節點。沒有任何子節點的節點稱爲葉子節點。post

樹的遍歷:沿着樹的一組特定的邊,能夠從一個節點走到另一個與它不直接相連的節點。從一個節點到另外一個節點的這一組邊稱爲路徑,在圖中用虛線表示。以某種特定順序訪問樹中全部的節點稱爲樹的遍歷測試

樹的深度:樹能夠分爲幾個層次,根節點是第 0 層,它的子節點是第 1 層,子節點的子節點是第 2層,以此類推。樹中任何一層的節點能夠都看作是子樹的根,該子樹包含根節點的子節點,子節點的子節點等。咱們定義樹的層數就是樹的深度this

7.2 二叉樹spa

首先咱們要了解下什麼是二叉樹,二叉樹是一種特殊的樹,它的子節點個數不超過兩個。二叉查找樹是在二叉樹的基礎上對樹的結構進行了進一步的約束。code

二叉查找樹又名二叉排序樹,有時候也叫二叉搜索樹。它具備如下特徵:對象

  • 若左子樹不爲空,則左子樹上全部的結點的值均小於它根節點的值
  • 若右子樹不爲空,那麼右子樹上全部節點的值均大於它根節點的值
  • 左右子樹也分別爲二叉查找樹
  • 沒有鍵值相等的節點

7.3 實現一個二叉樹

二叉樹都是由節點組成的,因此咱們要實現二叉樹必需要先實現節點,一個節點一般包含三部分——數據、左子節點、右子節點。所以咱們能夠定義這樣的一個node對象來表明咱們的節點。

class Node {
  constructor(data, left, right) {
    this.data = data
    this.left = left
    this.right = right
  }
}
複製代碼

Node 對象既保存數據,也保存和其餘節點的連接

如今咱們能夠建立一個類,用來表示二叉查找樹,咱們讓類只包含一個數據成員:一個表示二叉查找樹根節點的 Node 對象。該類的構造函數將根節點初始化爲 null,以此建立一個空節點。

接下來咱們來實現一個insert方法,用來向樹中插入新的節點。實現這個方法的算法以下:

  1. 若是跟節點爲null,則把根節點設置爲要插入的節點
  2. 若是待插入節點保存的數據小於當前節點,則獲取當前節點的左節點,若是左節點存在則與待插入的節點進行比較,重複步驟2。若是當前節點的左節點不存在,則直接把待插入的節點設置爲當前節點的左節點
  3. 若是待插入節點保存的數據大於當前節點,則獲取當前節點的右節點,若是右節點存在則與待插入的節點進行比較,重複步驟2。若是當前節點的右節點不存在,則直接把待插入的節點設置爲當前節點的右節點

實現二叉樹

class BST {
  constructor() {
    this.root = null
  }
  insert(data) {
    let n = new Node(data, null, null)
    if (this.root === null) {
      this.root = n
    } else {
      var current = this.root
      while (true) {
        if (n.data < current.data) {
          if (current.left === null) {
            current.left = n
            break
          }
          current = current.left
        } else {
          if (current.right === null) {
            current.right = n
            break
          }
          current = current.right
        }
      }
    }

  }
}
複製代碼

*驗證二叉樹

let bst = new BST()
bst.insert(8)
bst.insert(3)
bst.insert(6)
bst.insert(4)
bst.insert(9)
bst.insert(11)
bst.insert(2)
bst.insert(5)
bst.insert(7)
console.log('bst:',bst)
複製代碼

真實的打印結果很難顯示在博客裏,畫成圖的話以下:

實現是正確的,說明咱們的二叉樹程序沒有問題

7.4 二叉樹的遍歷

二叉樹遍歷經常使用的有如下三種。

  • 前序遍歷:先訪問根->遍歷左子樹->遍歷右子樹
  • 中序遍歷:遍歷左子樹->訪問根->遍歷右子樹
  • 後序遍歷:遍歷左子樹->遍歷右子樹->訪問根

遍歷的前中後順序實際上是指訪問根節點的順序

前序遍歷:

// 前序遍歷
  preOrder(node) {
    if (node != null) {
      console.log(node.data)
      this.preOrder(node.left)
      this.preOrder(node.right)
    }
  }
複製代碼

中序遍歷:

// 中序遍歷
  inOrder(node) {
    if (node != null) {
      this.inOrder(node.left)
      console.log(node.data)
      this.inOrder(node.right)
    }
  }
複製代碼

後序遍歷

// 後序遍歷
  postOrder(node) {
    if (node !== null) {
      this.postOrder(node.left)
      this.postOrder(node.right)
      console.log(node.data)
    }
  }
複製代碼

**遍歷的方法使用的是遞歸,很是繞,須要多理解理解

完整代碼以下

class Node {
  constructor(data, left, right) {
    this.data = data
    this.left = left
    this.right = right
  }

}

class BST {
  constructor() {
    this.root = null
  }
  insert(data) {
    let n = new Node(data, null, null)
    if (this.root === null) {
      this.root = n
    } else {
      var current = this.root
      while (true) {
        if (n.data < current.data) {
          if (current.left === null) {
            current.left = n
            break
          }
          current = current.left
        } else {
          if (current.right === null) {
            current.right = n
            break
          }
          current = current.right
        }
      }
    }

  }
  // 前序遍歷
  preOrder(node) {
    if (node != null) {
      console.log(node.data)
      this.preOrder(node.left)
      this.preOrder(node.right)
    }
  }
  // 中序遍歷
  inOrder(node) {
    if (node != null) {
      this.inOrder(node.left)
      console.log(node.data)
      this.inOrder(node.right)
    }
  }
  // 後序遍歷
  postOrder(node) {
    if (node !== null) {
      this.postOrder(node.left)
      this.postOrder(node.right)
      console.log(node.data)
    }
  }
}
複製代碼

測試:

let bst = new BST()
bst.insert(8)
bst.insert(3)
bst.insert(6)
bst.insert(4)
bst.insert(9)
bst.insert(11)
bst.insert(2)
bst.insert(5)
bst.insert(7)

bst.preOrder(bst.root) // 8 3 2 6 4 5 7 9 11

bst.inOrder(bst.root) // 2 3 4 5 6 7 8 9 11

bst.postOrder(bst.root) // 2 5 4 7 6 3 11 9 8
複製代碼

測試沒有,說明咱們的遍歷是正確的。

7.5 二叉樹查找

  • 查找給定的值

  • 查找最大值

  • 查找最小值

    // 查找最大是
    getMax() {
      let node = this.root
      while (true) {
        if (node.right === null) {
          console.log(node.data)
          break
        }
        node = node.right
      }
    }
    // 查找最小值
    getMin() {
      let node = this.root
      while (true) {
        if (node.left === null) {
          console.log(node.data)
          break
        }
        node = node.left
      }
    }
    getSomeNum(num) {
      let node = this.root
      while (node) {
        if (num < node.data) {
          node = node.left
        } else if (num > node.data) {
          node = node.right
        } else if (num === node.data) {
          console.log('node.data:', node.data)
          break
        }
      }
    }
    複製代碼

測試

bst.getMax() // 11
bst.getMin() // 2
bst.getSomeNum(9) // node.data: 9
bst.getSomeNum(1) // 
複製代碼

7.6 從二叉樹上刪除節點

在二叉樹上刪除節點會比較複雜,若是要刪除的節點沒有子節點還好,若是要刪除的節點有一個節點或者兩個節點,都要考慮到這些節點的處理狀況,這時就比較難以處理。

  • 若是待刪除節點是葉子節點(沒有子節點的節點),那麼只須要將從父節點指向它的連接改成 null
  • 若是待刪除節點只包含一個子節點,那麼本來指向它的節點就得作些調整,使其指向它的子節點。
  • 最後,若是待刪除節點包含兩個子節點,正確的作法有兩種:要麼查找待刪除節點左子樹上的最大值,要麼查找其右子樹上的最小值。這裏咱們選擇後一種方式。

程序以下:

getSmallest(ndoe) {
    while (node.left !== null) {
      node = node.left
    }
    return node
  }
  remove(data) {
    this.root = this.removeNode(this.root, data)
  }
  // 若是要刪除的節點是當前節點,則返回null,不然返回原節點,並按照規則,遞歸查找下一個節點是否是目標節點
  removeNode(node, data) {
    if (node === null) {
      return null
    }
    if (data === node.data) {
      if (node.left === null && node.right === null) {
        return null
      }
      // 沒有左子節點
      if (node.left === null) {
        return node.right
      }
      // 沒有右子節點
      if (node.right === null) {
        return node.left
      }
      // 有兩個字節點
      let tempNode = this.getSmallest(node.right)
      node.data = tempNode.data
      node.right = this.removeNode(node.right, tempNode.data)
      return node
    } else if (data < node.data) {
      node.left = this.removeNode(node.left, data)
      return node
    } else {
      node.right = this.removeNode(node.right, data)
      return node
    }
  }
複製代碼

測試

class Node {
  constructor(data, left, right) {
    this.data = data
    this.left = left
    this.right = right
  }

}

class BST {
  constructor() {
    this.root = null
  }
  insert(data) {
    let n = new Node(data, null, null)
    if (this.root === null) {
      this.root = n
    } else {
      var current = this.root
      while (true) {
        if (n.data < current.data) {
          if (current.left === null) {
            current.left = n
            break
          }
          current = current.left
        } else {
          if (current.right === null) {
            current.right = n
            break
          }
          current = current.right
        }
      }
    }

  }
  // 前序遍歷
  preOrder(node) {
    if (node != null) {
      console.log(node.data)
      this.preOrder(node.left)
      this.preOrder(node.right)
    }
  }
  // 中序遍歷
  inOrder(node) {
    if (node != null) {
      this.inOrder(node.left)
      console.log(node.data)
      this.inOrder(node.right)
    }
  }
  // 後序遍歷
  postOrder(node) {
    if (node !== null) {
      this.postOrder(node.left)
      this.postOrder(node.right)
      console.log(node.data)
    }
  }
  // 查找最大是
  getMax() {
    let node = this.root
    while (true) {
      if (node.right === null) {
        console.log(node.data)
        break
      }
      node = node.right
    }
  }
  // 查找最小值
  getMin() {
    let node = this.root
    while (true) {
      if (node.left === null) {
        console.log(node.data)
        break
      }
      node = node.left
    }
  }
  getSomeNum(num) {
    let node = this.root
    while (node) {
      if (num < node.data) {
        node = node.left
      } else if (num > node.data) {
        node = node.right
      } else if (num === node.data) {
        console.log('node.data:', node.data)
        break
      }
    }
  }
  getSmallest(ndoe) {
    while (node.left !== null) {
      node = node.left
    }
    return node
  }
  remove(data) {
    this.root = this.removeNode(this.root, data)
  }
  // 若是要刪除的節點是當前節點,則返回null,不然返回原節點,並按照規則,遞歸查找下一個節點是否是目標節點
  removeNode(node, data) {
    if (node === null) {
      return null
    }
    if (data === node.data) {
      if (node.left === null && node.right === null) {
        return null
      }
      // 沒有左子節點
      if (node.left === null) {
        return node.right
      }
      // 沒有右子節點
      if (node.right === null) {
        return node.left
      }
      // 有兩個字節點
      let tempNode = this.getSmallest(node.right)
      node.data = tempNode.data
      node.right = this.removeNode(node.right, tempNode.data)
      return node
    } else if (data < node.data) {
      node.left = this.removeNode(node.left, data)
      return node
    } else {
      node.right = this.removeNode(node.right, data)
      return node
    }
  }
}
let bst = new BST()
bst.insert(8)
bst.insert(3)
bst.insert(6)
bst.insert(4)
bst.insert(9)
bst.insert(11)
bst.insert(2)
bst.insert(5)
bst.insert(7)

bst.remove(9)

bst.inOrder(bst.root)
// 打印結果
2
3
4
5
6
7
8
11
複製代碼

從打印結果看,須要刪除的數據被刪除了,bst能正常遍歷,中序遍歷順序也正確,說明咱們刪除程序沒有問題。自此關於二叉樹的知識點就基本講完了。有實際狀況時根據實際狀況再調整。

相關文章
相關標籤/搜索