資料結構之「AVL樹」

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

前言

二叉搜尋樹在一般情況下它的查詢時間複雜度是 O(log n)。但在一些特殊的情況下,它會退化為斜樹變成線性結構,導致查詢效率大大降低,根本發揮不出折半查詢的優勢。因此,就需要一種平衡的二叉搜尋樹來達到我們期望查詢時間複雜度為 O(log n) 的資料結構,那就是平衡二叉搜尋樹

平衡二叉搜尋樹

平衡二叉搜尋樹又叫 AVL樹,它具有以下性質:
1.任一節點對應的兩棵子樹的最大高度差為 1。
2.兩棵子樹都是平衡二叉樹。

通過保證上面 2 條規則,因此它也被稱為高度平衡樹。查詢、插入和刪除在平均和最壞情況下的時間複雜度都是O(log n)。因此,它是一顆相對穩定的樹。

為了保證左右 2 棵子樹的最大高度差為 1,我們需要在插入和刪除時,對樹進行旋轉操作,以保證樹的平衡性。有 2 種基本的操作,那就是左旋右旋

資料結構之「AVL樹」
在進行左右旋轉時,會出現四種可能性:
1.左左形態
當 x 是 y 的左節點,y 又是 z 的左節點時,這個時候只需要把 y 節點向右旋轉即可。如下圖:
資料結構之「AVL樹」
2.左右形態
當 x 是 y 的右節點,而 y 又是 z 的左節點時,需要先把 y 左旋,變成 左左形態,然後再把 z 右旋。如下圖:
資料結構之「AVL樹」
3.右右形態
當 x 是 y 的右節點, y 又是 z 的右節點時,這時候只需要把 y 節點向左旋轉即可。如下圖:
資料結構之「AVL樹」
4.右左形態
當 x 是 y 的左節點,而 y 又是 z 的右節點時,需要先把 y 右旋,變成 右右形態,然後再把 z 左旋。如下圖:
資料結構之「AVL樹」

平衡二叉樹的實現

public class AVLTree {

    //獲取左右節點的高度差
    public int getBalance(Node n) {
        if (n != null) {
            return (getHeight(n.left) - getHeight(n.right));
        }
        return 0;
    }
    //獲取節點的高度
    public int getHeight(Node n) {
        if (n != null) {
            return n.height;
        }
        return 0;
    }

    //右旋
    public Node rightRotate(Node y) {
        Node x = y.left;
        Node T2 = x.right;

        // 旋轉
        x.right = y;
        y.left = T2;

        // 更新節點的高度
        x.height = Math.max(getHeight(x.left), getHeight(x.right)) + 1;
        y.height = Math.max(getHeight(y.left), getHeight(y.right)) + 1;

        return x;
    }

    //左旋
    public Node leftRotate(Node x) {
        Node y = x.right;
        Node T2 = y.left;

        // 旋轉
        y.left = x;
        x.right = T2;

        // 更新節點的高度
        x.height = Math.max(getHeight(x.left), getHeight(x.right)) + 1;
        y.height = Math.max(getHeight(y.left), getHeight(y.right)) + 1;

        return y;
    }

    public Node insert(Node node, int data) {
        if (node == null) {
            return (new Node(data));
        }
        if (node.data > data) {
            node.left = insert(node.left, data);
        } else {
            node.right = insert(node.right, data);
        }
        // 更新節點的高度
        node.height = Math.max(getHeight(node.left), getHeight(node.right)) + 1;

        int balDiff = getBalance(node);

        // 右旋
        if (balDiff > 1 && data < node.left.data) {
            return rightRotate(node);
        }

        // 左旋
        if (balDiff < -1 && data > node.right.data) {
            return leftRotate(node);
        }

        // 先左旋在右旋
        if (balDiff > 1 && data > node.left.data) {
            node.left = leftRotate(node.left);
            return rightRotate(node);
        }

        // 先右旋在左旋
        if (balDiff < -1 && data < node.right.data) {
            node.right = rightRotate(node.right);
            return leftRotate(node);
        }

        return node;
    }
}
class Node {
    int data;
    Node left;
    Node right;
    int height;

    public Node(int data) {
        this.data = data;
        height = 1;
    }
}
複製程式碼

總結

二叉搜尋樹的不穩定性,就有可能退化為近似鏈或鏈,查詢時間複雜度就退化為 O(n)。當 n 很大的時候,效能就大大降低,達不到折半的效果。因此,我們需要一個平衡的二叉搜尋樹。

平衡二叉樹是通過對樹節點的左旋和右旋來保證樹的平衡性,也就是保證左右子樹的高度差不超過 1。

在對樹進行左旋和右旋時,有四種形態分別是:左左左右右右右左

因此,平衡二叉樹的查詢、插入和刪除在平均和最壞情況下的時間複雜度都是O(log n)。

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

資料結構之「AVL樹」

相關文章