資料結構之「二叉搜尋樹」

清塵閒聊發表於2019-03-29

二叉搜尋樹

二叉搜尋樹也叫二叉查詢樹或者二叉排序樹,它要麼是一顆空樹,要麼滿足以下幾點:
1.若任意節點的左子樹不空,則左子樹上所有節點的值均小於它的根節點的值。
2.若任意節點的右子樹不空,則右子樹上所有節點的值均大於它的根節點的值。
3.任意節點的左、右子樹也分別為二叉搜尋樹。
4.沒有鍵值相等的節點。

資料結構之「二叉搜尋樹」

二叉搜尋樹的實現

1.二叉搜尋樹的儲存結構

public class BinarySearchTree {
    public static  Node root;
    public BinarySearchTree(){
        this.root = null;
    }
}
class Node{
    int data;
    Node left;
    Node right;
    public Node(int data){
        this.data = data;
        left = null;
        right = null;
    }
}
複製程式碼

2.二叉搜尋樹的插入
a.迴圈二分查詢到需要插入的地方。
b.假如插入的值小於當前的值,並且當前左節點為空,那麼左節點就指向新節點。
c.假如插入的值大於當前的值,並且當前右節點為空,那麼右節點就指向新節點。

public void insert(int id){
    Node newNode = new Node(id);
    if(root == null){
        root = newNode;
        return;
    }
    Node current = root;
    Node parent = null;
    while(true){
        parent = current;
        if(id < current.data){
            current = current.left;
            if(current == null){
                parent.left = newNode;
                return;
            }
        } else {
            current = current.right;
            if(current == null){
                parent.right = newNode;
                return;
            }
        }
    }
}
複製程式碼

3.二叉搜尋樹的刪除
a.當刪除節點為葉子節點時,直接刪除節點。
b.當刪除節點只有左子樹時,重接左子樹。
c.當刪除節點只有右子樹時,重接右子樹。
d.當刪除節點既有左子樹,又有右子樹時,先找一個可以替換刪除節點的節點。由於二叉樹的性質,左子樹的值小於根節點的值,右子樹的值大於根節點的值。所以右子樹的最左的節點就是替換刪除的節點,然後在重接右子樹。
第 d 點的圖例:

資料結構之「二叉搜尋樹」

public boolean delete(int id) {
    Node parent = root;
    Node current = root;
    boolean isLeftChild = false;
    while (current.data != id) {
        parent = current;
        if (current.data > id) {
            isLeftChild = true;
            current = current.left;
        } else {
            isLeftChild = false;
            current = current.right;
        }
        if (current == null) {
            return false;
        }
    }
    //刪除的節點既沒左節點,也沒右節點
    if (current.left == null && current.right == null) {
        if (current == root) {
            root = null;
        }
        if (isLeftChild == true) {
            parent.left = null;
        } else {
            parent.right = null;
        }
    }
    //刪除的節點只有左節點
    else if (current.right == null) {
        if (current == root) {
            root = current.left;
        } else if (isLeftChild) {
            parent.left = current.left;
        } else {
            parent.right = current.left;
        }

    }
    //刪除的節點只有右節點
    else if (current.left == null) {
        if (current == root) {
            root = current.right;
        } else if (isLeftChild) {
            parent.left = current.right;
        } else {
            parent.right = current.right;
        }
    }
    //刪除的節點既有左節點,又有右節點
    else if (current.left != null && current.right != null) {
        //找到右子樹的最左節點
        Node successor = getSuccessor(current);
        if (current == root) {
           root = successor;
            } else if (isLeftChild) {
                parent.left = successor;
            } else {
                parent.right = successor;
            }
            successor.left = current.left;
        }
        return true;
    }

    public Node getSuccessor(Node deleleNode) {
        Node successsor = null;
        Node successsorParent = null;
        Node current = deleleNode.right;
        while (current != null) {
            successsorParent = successsor;
            successsor = current;
            current = current.left;
        }
        if (successsor != deleleNode.right) {
            successsorParent.left = successsor.right;
            successsor.right = deleleNode.right;
        }
        return successsor;
    }
複製程式碼

4.二叉搜尋樹的查詢

public boolean find(int id) {
    Node current = root;
    while (current != null) {
        if (current.data == id) {
            return true;
        } else if (current.data > id) {
            current = current.left;
        } else {
            current = current.right;
        }
    }
    return false;
    }
複製程式碼

總結

由於它是一顆有序的樹,就可以進行折半查詢,每一次查詢,假如不是匹配的值,都可以排除一半的值。所以一般的時間複雜度是 O(log n)。假如這棵樹退化為斜樹,就差不多是線性表了,它的時間複雜度就是 O(n)。

雖然二叉搜尋樹的最壞時間複雜度是 O(n),但通過一些改進可以把最壞時間複雜度降至 O(log n),比如 AVL樹、紅黑樹等。紅黑樹不需要絕對的平衡,所以插入和刪除效率上要高,在 JDK1.8 中雜湊表儲存大於等於 8 個節點的連結串列就是採用的紅黑樹。

所以二叉搜尋樹在查詢上是非常快的,在一些需要很高查詢效率上推薦使用。

PS:
清山綠水始於塵,博學多識貴於勤。
我有酒,你有故事嗎?
公眾號:「清塵閒聊」。
歡迎一起談天說地,聊程式碼。

資料結構之「二叉搜尋樹」

相關文章