學習JavaScript數據結構與算法 — AVL樹

AVL樹

普通二叉搜索樹可能出現一條分支有多層,而其餘分支卻只有幾層的狀況,如圖1所示,這會致使添加、移除和搜索樹具備性能問題。所以提出了自平衡二叉樹的概念,AVL樹(阿德爾森-維爾斯和蘭迪斯樹)是自平衡二叉樹的一種,AVL樹的任一子節點的左右兩側子樹的高度之差不超過1,因此它也被稱爲高度平衡樹。node

圖1

clipboard.png

要將不平衡的二叉搜索樹轉換爲平衡的AVL樹須要對樹進行一次或屢次旋轉,旋轉方式分爲左單旋、右單旋、左-右雙旋、右-左雙旋。segmentfault

左單旋

對某一節點B(圖2)作左單旋,處理過程至關於,斷開B與父節點A的鏈接,將B的右子節點D與A鏈接,將B做爲D的左子節點,將D的左子節點E做爲B的右子節點。以圖1的二叉樹爲例,對鍵值爲15的節點作左單旋,首先斷開15與11的鏈接,再將20與11鏈接,將15做爲20的左子節點,最後將18做爲15的右子節點;能夠想象爲以15爲中心作了必定的逆時針旋轉。結果如圖3。函數

圖2

clipboard.png

圖3

clipboard.png

再看圖2,根據搜索二叉樹的性質,確定有D>B>A,E>B,所以旋轉事後,可以保證 右子節點 > 父節點 > 左子節點,不會破壞樹的結構。
能夠看到,一次左單旋將右側子樹的高度減少了1,而左側子樹的高度增長了1。實現代碼以下:性能

function roateLeft(AvlNode) {
        var node = AvlNode.right; // 保存右子節點
        AvlNode.right = node.left; // node的左子節點鏈接到AvlNode成爲其右子節點
        node.left = AvlNode; // AvlNode鏈接到node成爲其左子節點
        return node; // 返回node,鏈接到AvlNode最初的父節點
}

右單旋

右單旋與左單選相似,以某一節點B(圖4)作右單旋,首先斷開B與其父節點A的鏈接,將B的左子節點C與A鏈接,將C的右子節點F做爲B的左子節點。一樣的,由於有C>A,B>F>C,所以旋轉事後,不會破壞樹的結構。能夠看到,一次右單旋使節點的左側子樹高度減少了1,而右側子樹的高度增長了1。spa

圖4

clipboard.png

實現代碼以下:3d

function roateRight(AvlNode) {
        var node = AvlNode.left; // 保存左子節點
        AvlNode.left = node.right; // 將node的右子節點鏈接到AvlNode成爲其左子節點
        node.right = AvlNode; // AvlNode鏈接到node,成爲其右子節點
        return node; // 返回node鏈接到AvlNode最初的父節點
}

左-右雙旋

左單旋、右單旋在某些狀況下是不能達到平衡樹的目的的。如圖4,對B進行右單旋,須要左子樹C的右子樹F的高度小於等於左子樹E的高度,不然不能達到平衡的效果,只是把不平衡性從左邊轉移到了右邊。圖5演示了這種狀況。一樣的,左單旋也有這個問題。code

圖5

clipboard.png

所以爲了達到目的,須要先對旋轉節點的左子節點作左單旋,再對旋轉節點作右單旋。如圖6所示,先對節點B的左子節點C作左單旋,能夠看到,這個操做,至關於將節點C的不平衡性從右側轉移到了左側,從而知足了上述右單旋的條件;最後再對B節點作右單旋操做,最終達到了平衡的目的。blog

圖6

clipboard.png

實現代碼以下:ip

function roateLeftRight(AvlNode) {
        AvlNode.right = roateLeft(AvlNode.right); // 對右子節點作左單旋
        return roateRight(AvlNode); // 作右單旋
}

右-左雙旋

同理,如圖2,對B進行左單旋時,須要右子樹D的右子樹F的高度大於等於左子樹E的高度,不然須要進行雙旋;即先對B的右子節點D作右單旋,再對B作左單旋。實現代碼以下:get

function roateRightLeft(AvlNode) {
        AvlNode.left = roateRight(AvlNode.left); // 對左子節點作右單旋
        return roateLeft(AvlNode); // 作左單旋
}

實現樹的平衡

首先實現獲取樹高度的函數:

function getAvlTreeHeight(node) {
        if (node == null) {
            // node不存在返回0
            return 0;
        } else {
            var leftHeight = getAvlTreeHeight(node.left);
            var rightHeight = getAvlTreeHeight(node.right);
            // 返回左子樹、右子樹中的最大高度
            return (leftHeight > rightHeight ? leftHeight : rightHeight) + 1;
        }
}

實現平衡樹的函數:

function balance(node) {
    if (node == null) {
        return node;
    }
    // 左子樹高度比右子樹高度大1以上
    if (getAvlTreeHeight(node.left) - getAvlTreeHeight(node.right) > 1) {
        if (getAvlTreeHeight(node.left.left) >= getAvlTreeHeight(node.left.right)) {
            // 若是左子樹的左子樹高度大於等於左子樹的右子樹高度
            // 直接進行右單旋
            node = roateRight(node);
        } else {
            // 不然須要右-左雙旋
            node = roateRightLeft(node);
        }
        // 右子樹高度比左子樹高度大1以上
    } else if (getAvlTreeHeight(node.right) - getAvlTreeHeight(node.left) > 1) {
        if (getAvlTreeHeight(node.right.right) >= getAvlTreeHeight(node.right.left)) {
            // 若是右子樹的右子樹高度大於等於右子樹的左子樹高度
            // 直接進行左單旋
            node = roateLeft(node);
        } else {
            // 不然須要左-右雙旋
            node = roateLeftRight(node);
        }
    }
    return node;
}

二叉搜索樹的基礎上,每次插入節點,都須要作一次樹的平衡處理:

var insertNode = function(node, newNode){
    if (newNode.key < node.key){
        if (node.left === null){
            node.left = newNode;
            // 插入節點後,作樹的平衡處理
            node.left = balance(node.left);
        } else {
            insertNode(node.left, newNode);
        }
    } else {
        if (node.right === null){
            node.right = newNode;
            // 插入節點後,作樹的平衡處理
            node.right = balance(node.right);
        } else {
            insertNode(node.right, newNode);
        }
    }
}

綜上,一顆自平衡AVL樹的原理及實現就完成了。

相關文章
相關標籤/搜索