二叉查找樹概念及實現

起手詩

從今天起,作一個牛X的人,早起,健身,修煉算法
從今天起,關心代碼質量,我有一個夢想,朝九晚五,年薪百萬
從今天起,和每個親人通訊,告訴他們個人決心
那成功的天使告訴個人
我想告訴每個人
給每個文件、每個變量取一個溫暖的名字
陌生人,我也爲你祝福
願你有一個燦爛的前程
願你的頭髮再也不減小
願你明天還是公司棟樑
而我,只願朝九晚五,年薪百萬
javascript

前言

      書接上文從今天起,天天詳解一道算法題,今天實現一個二叉查找樹。java

基本概念

      二叉查找樹是二叉樹的一種特殊狀況,二叉查找樹的左邊節點的值老是比右邊的小,先看下定義:node

二叉查找樹(Binary Search Tree),(又:二叉搜索樹,二叉排序樹)它或者是一棵空樹,或者是具備下列性質的二叉樹: 若它的左子樹不空,則左子樹上全部結點的值均小於它的根結點的值; 若它的右子樹不空,則右子樹上全部結點的值均大於它的根結點的值; 它的左、右子樹也分別爲二叉查找樹。算法

      節點:包含一個數據元素及若干指向子樹的分支;
      根節點:第一層的節點(圖中的8);
      葉子節點:結點的子樹的根稱爲該結點的葉子節點,這是一個相對概念;
      結點層:根結點的層定義爲1;根的孩子爲第二層結點,依此類推;
      樹的深度:樹中最大的結點層;
      結點的度:結點子樹的個數(二叉樹爲2);
      樹的度:樹中最大的結點度(二叉樹爲2);
bash

代碼實現

      實現二叉樹,先要實現上面最重要的一個概念--節點:
      它有三個屬性,值、左節點的指針、右節點的指針:數據結構

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

      下面咱們實現二叉查找樹的類:
      咱們能夠想向到它應該有一個根節點,還應該有一些操做該類的方法,我會分別解釋,先看代碼(爲了不代碼太長,我把各個方法分開寫了,他們應該都在BST類裏的):
學習

insert方法:

      該方法負責向該二叉樹中插入一條數據,實現的思路是:
      一、經過Node類建立包含該條數據的一個節點;
      二、判斷若是該實例的根節點root爲null,則把這個節點設置爲根節點;
      三、若是根節點不爲null,則建立一個指向當前節點的指針(第一次指向根節點),而後比較data是否大於當前節點的data;
      四、若是小於當前節點的data,則說明這個節點應該往左邊放,不然說明應該往右邊放;
      五、經過循環比較,最終當前節點會找到null,這時候把新節點插入到這裏(也就是讓當parentNode的子節點指向新節點);
ui

class BST {
    constructor() {
        this.root = null
    }
    insert(data) {
        let newNode = new Node(data, null, null)
        if (root == null) {
            this.root = newNode
        } else {
            let currentNode = this.root
            let parentNode
            while (true) {
                parentNode = currentNode
                if (data < currentNode.data) {
                    currentNode = currentNode.left
                    if (currentNode == null) parentNode.left = newNode
                    break
                } else {
                    currentNode = currentNode.right
                    if (currentNode == null) parentNode.right = newNode
                    break
                }
            }
        }
    }
}
複製代碼

遍歷方法:

      樹的遍歷分爲3種類型:中序、先序、後序,其中「中」、「先」、「後」指的是訪問根節點的時機。
      下面代碼中的each方法中有三個console,從上至下他們分別對應先序、後序、中序。他們分別表明訪問根節點(2)的時機。也就是一個在最早,一個在最後,一個在中間。this

each(node) {
        node = node || this.root
        //console.log(node.data) // 2,1,3
        if (node.left) this.each(node.left)
        //console.log(node.data) // 1,2,3
        if (node.right) this.each(node.right)
        //console.log(node.data) // 1,3,2
    }
複製代碼

查找最大最小值

      這兩個方法比較簡單,最大值就是遍歷右子樹,找到最後一個;最小值就是遍歷左子樹找到最後一個
      注:max和min方法有參數,這是爲了查找制定節點的最大最小值,下面的remove方法中用到了min方法查找制定節點的最小值spa

max(node) {
    let currentNode = node || this.root
    while (currentNode.right) {
        currentNode = currentNode.right
    }
    return currentNode.data
}
min(node) {
    let currentNode = node || this.root
    while (currentNode.left) {
        currentNode = currentNode.left
    }
    return currentNode.data
}
複製代碼

查找給定值的節點

      has方法和上面的insert方法的思路相似,都是建立一個指針指向根節點,比較傳入的data值,若是比根節點大就把指針向右移,不然向左。
      一旦找到相等的值就返回ture,若是遍歷到最後一個節點都沒有找到,返回false。

has(data) {
    let currentNode = this.root
    while (currentNode) {
        if (data == currentNode.data) {
            return true
        } else if (data < currentNode.data) {
            currentNode = currentNode.left
        } else {
            currentNode = currentNode.right
        }
    }
    return false
}
複製代碼

刪除給定值的節點

      刪除方法相對較複雜,思路爲:
      一、定義remove方法,方法中把根節點更改成一個遞歸方法removeNode的返回值
      二、removeNode是一個被刪除了制定節點的新樹,它的實現須要考慮如下幾種狀況:
          a:若是輸入節點爲null,則返回null
          b:若是data與node的data相等,說明這個節點就是須要被刪除的節點(這種狀況又分爲幾種狀況,下面單獨說)
          c:若是data比node的data小,則說明須要刪除的節點在node的左邊,那麼就須要遞歸它左邊這個節點,直到找到相等的或者null
          d:若是data比node的data大,思路同c

      接下來講b(命中相等節點)的狀況:
      一、若是這個節點沒有子節點,那麼直接刪除便可
      二、若是這個節點只有一個節點,那麼它的這個子節點應該取代它的位置(代碼中第2、三行註釋下的代碼)
      三、若是這個節點有左右兩個節點,那麼咱們找到它的右子樹的最小值minData,這個節點的值就應該爲minData,同時讓它的右子樹爲它以前的右子樹(刪除最小值以後的)。

remove(data) {
    this.root = removeNode(this.root,data)
}
removeNode(node, data) {
        if (node == null) {
            return null
        }
        if (data == node.data) {
            //沒有節點,返回null
            if (node.left == null && node.right == null) {
                return null
            }
            //沒有左節點,右節點替換當前節點
            if (node.left == null) {
                return node.right
            }
            //沒有右節點,左節點替換當前節點    
            if (node.right == null) {
                return node.left
            }
            //左右節點都存在
            let minData = this.min(node.right)
            node.data = minData
            node.right = this.removeNode(node.right, minData)
            return node
        } else if (data < node.data) {
            node.left = this.removeNode(node.left, data)
            return node
        } else if (data > node.data) {
            node.right = this.removeNode(node.right, data)
            return node
        }
    }
複製代碼

參考資料

《數據結構與算法-javascript描述》 《學習javascript數據結構與算法》

相關文章
相關標籤/搜索