手撕AVL樹(C++)

YVVT_Real發表於2023-01-11

閱讀本文前,請確保您已經瞭解了二叉搜尋樹的相關內容(如定義、增刪查改的方法以及效率等)。否則,建議您先學習二叉搜尋樹。本文假定您對二叉搜尋樹有了足夠的瞭解。

效率?

眾所周知,在平衡條件下,對二叉搜尋樹中的元素進行增刪查改,時間效率為\(O(log(n))\)

然而,理想很豐滿,現實很骨感,實際上,二叉搜尋樹並不總是能夠保持平衡狀態。

最極端的情況就是,除了葉節點之外,每個節點只有一個子節點,如圖所示:

img

這種情況下二叉搜尋樹已經退化成一個連結串列,時間複雜度達到了\(O(n)\),二叉搜尋樹在時間效率上的優勢並沒有發揮出來。

要想解決這個問題,我們就要在每次動態操作(插入、刪除)之後,採取某種措施,使二叉搜尋樹重新回到平衡狀態(重平衡)。

平衡?

最理想的狀態就是完全二叉樹,其時間複雜度為\(O(log(n))\),但是如果每次動態操作之後都要讓二叉搜尋樹處於這樣一種理想狀態的話,實現起來比較複雜,大量的時間消耗在重平衡上,最後反而得不償失。

魚和熊掌不可兼得,我們需要在“平衡狀態”和“花在重平衡上的時間成本”之間做出一個取捨,將平衡條件適當放寬。

一種可能的折中方案

這次,我們不再要求二叉搜尋樹必須是完全二叉樹,而是放寬到每一個節點的左右子樹高度差不超過1(規定空樹的高度為0)。這就是所謂的AVL樹。

引入一個新的定義:平衡因子(Balance Factor),是指一個節點的左子樹高度減去右子樹高度。所以,AVL樹的性質等價於每個節點的平衡因子的絕對值不超過1。(之所以不在平衡因子的定義里加上絕對值,是因為後面要判斷是左子樹比右子樹高還是右子樹比左子樹高)

下圖就是一棵AVL樹的例子。

img

可以看出,每個節點的左右子樹高度差不超過1。這棵樹確實不是一棵完全二叉樹,但是其時間效率依然近似為\(O(log(n))\),是可以接受的。

動態操作之後的重平衡

不難發現,對AVL樹進行動態操作,有可能破壞AVL樹的性質。因此,在每一次動態操作之後,我們都要對其進行某種措施,使其重新恢復AVL樹性質。

等價變換

我們知道,一棵二叉搜尋樹的中序遍歷序列是單調遞增的,但是中序遍歷序列相同的二叉搜尋樹的結構不一定相同,這裡我們可以將中序遍歷序列相同的二叉搜尋樹看做是等價的。比如以下這4棵二叉搜尋樹的中序遍歷序列均為1 2 3,因此這4棵二叉搜尋樹都可以看做是等價的。

img

對於發生失衡的節點,常用“旋轉”的方法使之恢復平衡。所謂“旋轉”操作,就是以下兩種結構的相互變換。

img

以左旋操作為例,令待旋轉節點為x,x的父節點為p,x的右子節點為y,y的左右子節點分別為b和c,則左旋的操作步驟為:

  1. 將b設為x的右子節點。
  2. 將x設為y的左子節點。
  3. 將y設為p的左子節點/右子節點(這取決於原先x是p的左子節點還是右子節點)。

右旋操作與此對稱,不再贅述。

幾種失衡的情況

以下是一些失衡的情況,需要做不同的處理來使之恢復平衡。

LL與RR

所謂LL,就是失衡節點的左子樹比右子樹高,且左孩子的左子樹又比右子樹高或等高,這時候我們只需要對失衡節點做一次右旋操作就可以恢復平衡。如圖:

img

RR就是失衡節點的右子樹比左子樹高,且右孩子的右子樹又比左子樹高或等高。恢復措施完全是對稱的,這裡不再贅述了。

LR與RL

所謂LR,就是失衡節點的左子樹比右子樹高,但左孩子的右子樹卻比左子樹高。這時候我們需要進行兩步旋轉操作:首先對失衡節點的左孩子進行一次左旋,使得整個結構變成LL,然後對失衡節點進行一次右旋,這樣才能平衡,如圖:

img

RL與此是完全對稱的,操作步驟也是對稱的。

插入節點後的重平衡

在插入節點後,有可能出現失衡的情況,如圖:

img

這時候就要進行相應的恢復平衡的措施。步驟如下:

  1. 上溯到最低的失衡節點。(如果上溯到根節點也沒有發現失衡節點就可以不用重平衡了)
  2. 根據失衡型別(LL、LR、RL、RR)進行相應的調整。

調整後的子樹的高度會與插入前保持一致,因此不需要進行後續的重平衡操作了。

重平衡的時間複雜度:\(O(log(n))\)。其中上溯的時間複雜度為\(O(log(n))\),旋轉的時間複雜度為\(O(1)\)

刪除節點後的重平衡

對節點進行刪除,也有可能導致出現失衡,如圖:

img

這時候的操作和插入操作一樣,先上溯到最深的失衡節點然後進行調整。

但是!調整後的子樹的高度有可能會變化,這就意味著這棵子樹雖然恢復了平衡,但是有可能反而會導致父節點的失衡,如圖:

img

因此,我們需要進一步上溯到父節點,然後進行重平衡處理。

同樣的,這樣做可能會繼續導致父節點的父節點的失衡,因此我們需要不斷地上溯、重平衡,直到上溯到的節點不再失衡為止。如果上溯到根節點依然沒有發現失衡節點,那麼自然就不需要再進行重平衡了。

重平衡的時間複雜度:\(O(log(n))\)。其中上溯的時間複雜度為\(O(log(n))\),旋轉的時間複雜度為\(O(1)\)

程式碼實現

類定義

AVL樹類框架

為了能夠讓AVL樹能夠適應不同的資料型別(intfloatlong)等,我們以模板類的方式來實現。

整個AVL樹類應該對外提供以下介面:查詢節點是否存在、插入節點、刪除節點。

為了隱藏實現細節,只暴露介面,我們將AVL樹節點類以private巢狀類的形式封裝在AVL樹內部。

另外,為了方便,我們引入一個啞結點,其左孩子為整棵樹的根節點,父節點和右孩子為nullptr

由於插入、刪除操作為動態操作,除了啞結點之外的所有節點全都是透過new運算子動態分配在堆記憶體中的,因此我們需要一種方式在AVL樹物件銷燬時將動態分配出來的節點所佔用的記憶體空間釋放掉。因為我們釋放節點時只能在左右孩子均釋放之後才能釋放當前節點(否則會因為訪問未分配的記憶體而引發程式異常退出),因此我們需要單獨定義一個函式來遞迴的釋放節點。

因此,整個AVL樹類的大體框架如下:

template<typename T>
class AVLTree {

    class AVLTreeNode {
        // ...
    };

    AVLTreeNode dummyHead;

public:
    AVLTree() : /* ... */ {}

    virtual ~AVLTree() {
        // ...
    }
protected:
    // 上述3個介面(存在、插入、刪除)均依賴於這個內部介面。
    // 返回給定資料所在的節點。如果不存在的話,就返回插入操作中所插入的節點的父節點。
    AVLTreeNode *find(T data) {
        // ...
    }

public:
    // 判斷AVL樹中是否存在指定資料所在的節點。
    bool exists(T data) {
        // ...
    }

    // 如果給定資料所在的節點不存在,則插入節點。
    void insert(T data) {
        // ...
    }

    // 如果存在給定資料所在的節點,則刪除節點。
    void remove(T data) {
        // ...
    }

protected:
    // 釋放以給定節點為根節點的子樹所佔用的記憶體空間
    void release(AVLTreeNode *node) {
        // ...
    }
};

AVL節點類框架

而AVL樹節點類有以下幾個成員變數:資料、父節點、左右孩子節點、以該節點為根節點的子樹高度(以下簡稱“該節點的高度”)。為了便於操作,類的內部也定義了一些介面,同時給AVL樹類暴露了兩個介面——插入和刪除。注意這裡AVL樹節點類並沒有定義為模板類,這是因為節點類的存放的資料型別是跟隨著AVL樹的資料型別的。

大體框架如下:

class AVLTreeNode {
public:
    T data;
    AVLTreeNode *parent;
    AVLTreeNode *left;
    AVLTreeNode *right;
    std::size_t height;

public:
    explicit AVLTreeNode(T data) : /* ... */ {
        // ...
    }

protected:
    // 更新高度
    void updateHeight() {
        // ...
    }

    // 計算平衡因子
    int getBalanceFactor() {
        // ...
    }

    // 判斷平衡因子是否在可接受的範圍內
    bool isBalanceFactorLegal() {
        // ...
    }

    // 判斷是否為啞結點
    bool isDummyHead() {
        // ...
    }

    // 判斷是否為父節點的左孩子
    bool isLeftChild() {
        // ...
    }

    // 將另一個節點作為當前節點的左孩子
    void connectLeft(AVLTreeNode *child) {
        // ...
    }

    // 將另一個節點作為當前節點的右孩子
    void connectRight(AVLTreeNode *child) {
        // ...
    }

    // 右旋
    AVLTreeNode *rotateClockwise() {
        // ...
    }

    // 左旋
    AVLTreeNode *rotateCounterclockwise() {
        // ...
    }

    // 對LL的情況進行重平衡
    AVLTreeNode *reBalanceLeftLeft() {
        // ...
    }

    // 對LR的情況進行重平衡
    AVLTreeNode *reBalanceLeftRight() {
        // ...
    }

    // 對RL的情況進行重平衡
    AVLTreeNode *reBalanceRightLeft() {
        // ...
    }

    // 對RR的情況進行重平衡
    AVLTreeNode *reBalanceRightRight() {
        // ...
    }

    // 重平衡
    AVLTreeNode *reBalance() {
        // ...
    }

public:
    // 以下兩個介面雖然是public的,但是由於AVL樹節點類在外層的AVL樹類中是private的,所以這兩個介面只對AVL樹類可見,而在類外不可見。

    // 將新節點插入到當前節點的底部
    void insert(T newData) {
        // ...
    }

    // 刪除當前節點
    void remove() {
        // ...
    }
};

AVL樹節點介面實現

初始化節點物件時,我們只需要初始化資料即可,父節點、左右孩子均設為nullptr。按照此前對樹高的定義,該節點的高度為1。

explicit AVLTreeNode(T data) : data(data), parent(nullptr), left(nullptr), right(nullptr), height(1) {}

在設定左右孩子節點的時候,自身的height成員變數不會自己變,因此我們需要手動更新高度為左右子樹各自的高度的最大值加上1。

void updateHeight() {
    std::size_t leftHeight = left == nullptr ? 0 : left->height;
    std::size_t rightHeight = right == nullptr ? 0 : right->height;
    height = std::max(leftHeight, rightHeight) + 1;
}

計算平衡因子,理論上只需要用左子樹高度減去右子樹高度即可,但是實際上由於左右子樹高度都是用std::size_t儲存的,雖然std::size_t在不同的平臺上的具體實現有可能有所不同,但是都是unsigned的(可能是unsigned long longunsigned long等),因此直接相減依然會得到unsigned的資料,可能不能得到正確的結果。所以這裡需要根據左右子樹的相對大小具體處理。因為平衡因子的取值只有可能在-1、0、1之間,以及短暫的處於-2、2,因此用int儲存完全足夠了。

int getBalanceFactor() {
    std::size_t leftHeight = left == nullptr ? 0 : left->height;
    std::size_t rightHeight = right == nullptr ? 0 : right->height;
    int diff = leftHeight > rightHeight ? leftHeight - rightHeight : rightHeight - leftHeight;
    return leftHeight > rightHeight ? diff : -diff;
}

判斷平衡因子是否在-1~1之間,我們單獨定義一個函式,這樣在需要判斷的時候就不用單獨定義一個變數儲存平衡因子了:

bool isBalanceFactorLegal() {
    auto balanceFactor = getBalanceFactor();
    return balanceFactor >= -1 && balanceFactor <= 1;
}

判斷節點是否為啞結點,根據此前的定義,只需要判斷父節點是否為空:

bool isDummyHead() {
    return parent == nullptr;
}

判斷節點是否為父節點的左孩子:

bool isLeftChild() {
    return this == parent->left;
}

將新節點作為當前節點的左/右孩子,我們首先需要更新當前節點的子節點,以及子節點(如果非空)的父節點,然後更新高度:

void connectLeft(AVLTreeNode *child) {
    left = child;
    if (child != nullptr)
        child->parent = this;
    updateHeight();
}

void connectRight(AVLTreeNode *child) {
    right = child;
    if (child != nullptr)
        child->parent = this;
    updateHeight();
}

對節點進行左旋、右旋,並返回旋轉後在原來位置上新的節點:

AVLTreeNode *rotateClockwise() {
    auto l = left;
    if (l == nullptr) return this;
    auto lr = l->right;
    auto p = parent;
    bool isLeft = isLeftChild();

    // 節點之間的重連
    connectLeft(lr);
    l->connectRight(this);
    if (isLeft)
        p->connectLeft(l);
    else
        p->connectRight(l);
    return l;
}

AVLTreeNode *rotateCounterclockwise() {
    auto r = right;
    if (right == nullptr) return this;
    auto rl = right->left;
    auto p = parent;
    bool isLeft = isLeftChild();
    connectRight(rl);
    r->connectLeft(this);
    if (isLeft)
        p->connectLeft(r);
    else
        p->connectRight(r);
    return r;
}

對不同的失衡型別進行重平衡,並返回重平衡後原來位置上新的節點:

AVLTreeNode *reBalanceLeftLeft() {
    return rotateClockwise();
}

AVLTreeNode *reBalanceLeftRight() {
    left->rotateCounterclockwise();
    return rotateClockwise();
}

AVLTreeNode *reBalanceRightLeft() {
    right->rotateClockwise();
    return rotateCounterclockwise();
}

AVLTreeNode *reBalanceRightRight() {
    return rotateCounterclockwise();
}

重平衡的統一介面,失衡型別的判斷在介面內部進行:

AVLTreeNode *reBalance() {
    int balanceFactor = getBalanceFactor();
    int sonBalanceFactor;
    if (balanceFactor == 0) { // 如果平衡因子等於0,就不需要重平衡了
        return this;
    }
    AVLTreeNode *son = nullptr;
    if (balanceFactor > 0) { // 左子樹高於右子樹
        son = left;
        sonBalanceFactor = son->getBalanceFactor();
        return sonBalanceFactor < 0
               ? reBalanceLeftRight() // 對左孩子來說,其右子樹高於左子樹,為LR
               : reBalanceLeftLeft(); // 對左孩子來說,其左子樹高於右子樹或者等高,為LL
    } else { // 右子樹高於左子樹
        son = right;
        sonBalanceFactor = son->getBalanceFactor();
        return sonBalanceFactor > 0
               ? reBalanceRightLeft()   // 對右孩子來說,其左子樹高於右子樹,為RL
               : reBalanceRightRight(); // 對右孩子來說,其右子樹高於左子樹或者等高,為RR
    }
}

在當前節點的下方插入一個新的節點,步驟:

  1. 判斷要插入的資料是否與當前節點的資料相同,如果當前節點不是啞結點且新資料與存放的資料相同則不需要插入,直接退出。
  2. 判斷新資料與當前節點的資料的大小關係,並決定是插入到左邊還是右邊。如果當前節點是啞結點則無條件插入到左邊。
  3. 上溯到最深的失衡節點(邊上溯邊更新高度),直到上溯到啞結點為止。
  4. 如果上溯到啞結點了,說明沒有節點出現失衡,結束。
  5. 如果發現某個節點失衡了,那就重新平衡。

程式碼:

void insert(T newData) {
    if (!isDummyHead() && newData == data) return;
    auto cur = new AVLTreeNode(newData, this);
    
    if (isDummyHead() || newData < data) {
        connectLeft(cur);
    } else {
        connectRight(cur);
    }
    
    do {
        cur = cur->parent;
        cur->updateHeight();
    } while (!cur->isDummyHead() && cur->isBalanceFactorLegal());
    if (cur->isDummyHead()) {
        return;
    }
    
    cur->reBalance();
}

刪除當前節點,步驟:

  1. 按照二叉搜尋樹刪除節點的步驟刪除該節點。
  2. 上溯到第一個出現失衡的節點。
  3. 重平衡,繼續上溯並重平衡,直到不再遇到失衡節點為止。
  4. 釋放該節點所佔用的記憶體空間。

程式碼:

void remove() {
    bool hasLeftChild = left != nullptr;
    bool hasRightChild = right != nullptr;
    AVLTreeNode *cur = parent;

    // 按照當前節點的子節點數量分情況討論
    if (hasLeftChild) {
        if (hasRightChild) { // 左右孩子節點都有,用當前節點的中序遍歷後繼替換掉,然後轉而刪除後繼節點
            auto successor = right;
            for (; successor->left != nullptr; successor = successor->left);
            data = successor->data;
            successor->remove();
            return;
        } else { // 只有左子節點,將父節點的子節點設為當前節點的左子節點
            if (isLeftChild()) {
                parent->connectLeft(left);
            } else {
                parent->connectRight(left);
            }
        }
    } else {
        if (hasRightChild) { // 只有右子節點,將父節點的子節點設為當前節點的右子節點
            if (isLeftChild()) {
                parent->connectLeft(right);
            } else {
                parent->connectRight(right);
            }
        } else { // 沒有孩子節點,將父節點的子節點置空
            if (isLeftChild()) {
                parent->connectLeft(nullptr);
            } else {
                parent->connectRight(nullptr);
            }
        }
    }
    
    while (!cur->isDummyHead() && cur->isBalanceFactorLegal()) {
        cur = cur->parent;
        cur->updateHeight();
    }

    for (; !cur->isDummyHead() && !cur->isBalanceFactorLegal(); cur = cur->parent) {
        cur = cur->reBalance();
    }

    delete this;
}

AVL樹介面實現

初始化:

AVLTree() : dummyHead(T()) {}

find內部介面,按照二叉樹查詢節點的方法,查詢給定資料所在的節點。如果不存在的話,就返回插入操作中所插入的節點的父節點。

AVLTreeNode *find(T data) {
    auto cur = dummyHead.left;
    if (cur == nullptr) return &dummyHead;
    while (cur->data != data) {
        if (data < cur->data) {
            if (cur->left == nullptr) {
                return cur;
            }
            cur = cur->left;
        } else {
            if (cur->right == nullptr) {
                return cur;
            }
            cur = cur->right;
        }
    }
    return cur;
}

判斷給定的資料是否存在,透過判斷find介面返回的節點儲存的資料是否為給定的資料來實現。(如果整棵樹沒有節點,那麼find將會返回啞結點,因此還需額外判斷返回的是否為啞結點)

bool exists(T data) {
    AVLTreeNode *found = find(data);
    return found != &dummyHead && found->data == data;
}

插入節點:(由於在節點的insert介面內判斷了新的資料與當前節點的資料是否相等,因此樹的插入節點的介面無需再判斷了)

void insert(T data) {
    AVLTreeNode *found = find(data);
    found->insert(data);
}

刪除節點:

void remove(T data) {
    AVLTreeNode *found = find(data);
    if (found != &dummyHead && found->data == data) found->remove();
}

記憶體釋放:

void release(AVLTreeNode *node) {
    if (node == nullptr) return;
    release(node->left);
    release(node->right);
    delete node;
}

virtual ~AVLTree() {
    release(dummyHead.left);
}

完整程式碼

由於巢狀類看起來非常冗長,不好看,因此我們將AVL樹類和AVL樹節點類單獨放在兩個檔案內,AVL樹類裡面#include對應的檔案。

AVLTreeNode.cpp檔案內定義節點類:

// AVLTreeNode.cpp
class AVLTreeNode {
public:
    T data;
    AVLTreeNode *parent;
    AVLTreeNode *left;
    AVLTreeNode *right;
    std::size_t height;

public:
    explicit AVLTreeNode(T data) : data(data), parent(nullptr), left(nullptr), right(nullptr), height(1) {}

protected:
    // 更新高度
    void updateHeight() {
        std::size_t leftHeight = left == nullptr ? 0 : left->height;
        std::size_t rightHeight = right == nullptr ? 0 : right->height;
        height = std::max(leftHeight, rightHeight) + 1;
    }

    // 計算平衡因子
    int getBalanceFactor() {
        std::size_t leftHeight = left == nullptr ? 0 : left->height;
        std::size_t rightHeight = right == nullptr ? 0 : right->height;
        int diff = leftHeight > rightHeight ? leftHeight - rightHeight : rightHeight - leftHeight;
        return leftHeight > rightHeight ? diff : -diff;
    }

    // 判斷平衡因子是否在可接受的範圍內
    bool isBalanceFactorLegal() {
        auto balanceFactor = getBalanceFactor();
        return balanceFactor >= -1 && balanceFactor <= 1;
    }

    // 判斷是否為啞結點
    bool isDummyHead() {
        return parent == nullptr;
    }

    // 判斷是否為父節點的左孩子
    bool isLeftChild() {
        return this == parent->left;
    }

    // 將另一個節點作為當前節點的左孩子
    void connectLeft(AVLTreeNode *child) {
        left = child;
        if (child != nullptr)
            child->parent = this;
        updateHeight();
    }

    // 將另一個節點作為當前節點的右孩子
    void connectRight(AVLTreeNode *child) {
        right = child;
        if (child != nullptr)
            child->parent = this;
        updateHeight();
    }

    // 右旋
    AVLTreeNode *rotateClockwise() {
        auto l = left;
        if (l == nullptr) return this;
        auto lr = l->right;
        auto p = parent;
        bool isLeft = isLeftChild();

        // 節點之間的重連
        connectLeft(lr);
        l->connectRight(this);
        if (isLeft)
            p->connectLeft(l);
        else
            p->connectRight(l);
        return l;
    }

    // 左旋
    AVLTreeNode *rotateCounterclockwise() {
        auto r = right;
        if (right == nullptr) return this;
        auto rl = right->left;
        auto p = parent;
        bool isLeft = isLeftChild();
        connectRight(rl);
        r->connectLeft(this);
        if (isLeft)
            p->connectLeft(r);
        else
            p->connectRight(r);
        return r;
    }

    // 對LL的情況進行重平衡
    AVLTreeNode *reBalanceLeftLeft() {
        return rotateClockwise();
    }

    // 對LR的情況進行重平衡
    AVLTreeNode *reBalanceLeftRight() {
        left->rotateCounterclockwise();
        return rotateClockwise();
    }

    // 對RL的情況進行重平衡
    AVLTreeNode *reBalanceRightLeft() {
        right->rotateClockwise();
        return rotateCounterclockwise();
    }

    // 對RR的情況進行重平衡
    AVLTreeNode *reBalanceRightRight() {
        return rotateCounterclockwise();
    }

    // 重平衡
    AVLTreeNode *reBalance() {
        int balanceFactor = getBalanceFactor();
        int sonBalanceFactor;
        if (balanceFactor == 0) { // 如果平衡因子等於0,就不需要重平衡了
            return this;
        }
        AVLTreeNode *son = nullptr;
        if (balanceFactor > 0) { // 左子樹高於右子樹
            son = left;
            sonBalanceFactor = son->getBalanceFactor();
            return sonBalanceFactor < 0
                ? reBalanceLeftRight() // 對左孩子來說,其右子樹高於左子樹,為LR
                : reBalanceLeftLeft(); // 對左孩子來說,其左子樹高於右子樹或者等高,為LL
        } else { // 右子樹高於左子樹
            son = right;
            sonBalanceFactor = son->getBalanceFactor();
            return sonBalanceFactor > 0
                ? reBalanceRightLeft()   // 對右孩子來說,其左子樹高於右子樹,為RL
                : reBalanceRightRight(); // 對右孩子來說,其右子樹高於左子樹或者等高,為RR
        }
    }

public:
    // 以下兩個介面雖然是public的,但是由於AVL樹節點類在外層的AVL樹類中是private的,所以這兩個介面只對AVL樹類可見,而在類外不可見。

    // 將新節點插入到當前節點的底部
    void insert(T newData) {
        if (!isDummyHead() && newData == data) return;
        auto cur = new AVLTreeNode(newData, this);
        
        if (isDummyHead() || newData < data) {
            connectLeft(cur);
        } else {
            connectRight(cur);
        }
        
        do {
            cur = cur->parent;
            cur->updateHeight();
        } while (!cur->isDummyHead() && cur->isBalanceFactorLegal());
        if (cur->isDummyHead()) {
            return;
        }
        
        cur->reBalance();
    }

    // 刪除當前節點
    void remove() {
        bool hasLeftChild = left != nullptr;
        bool hasRightChild = right != nullptr;
        AVLTreeNode *cur = parent;

        // 按照當前節點的子節點數量分情況討論
        if (hasLeftChild) {
            if (hasRightChild) { // 左右孩子節點都有,用當前節點的中序遍歷後繼替換掉,然後轉而刪除後繼節點
                auto successor = right;
                for (; successor->left != nullptr; successor = successor->left);
                data = successor->data;
                successor->remove();
                return;
            } else { // 只有左子節點,將父節點的子節點設為當前節點的左子節點
                if (isLeftChild()) {
                    parent->connectLeft(left);
                } else {
                    parent->connectRight(left);
                }
            }
        } else {
            if (hasRightChild) { // 只有右子節點,將父節點的子節點設為當前節點的右子節點
                if (isLeftChild()) {
                    parent->connectLeft(right);
                } else {
                    parent->connectRight(right);
                }
            } else { // 沒有孩子節點,將父節點的子節點置空
                if (isLeftChild()) {
                    parent->connectLeft(nullptr);
                } else {
                    parent->connectRight(nullptr);
                }
            }
        }
        
        while (!cur->isDummyHead() && cur->isBalanceFactorLegal()) {
            cur = cur->parent;
            cur->updateHeight();
        }

        for (; !cur->isDummyHead() && !cur->isBalanceFactorLegal(); cur = cur->parent) {
            cur = cur->reBalance();
        }

        delete this;
    }
};

AVLTree.cpp檔案內定義AVL樹類:

// AVLTree.cpp
template<typename T>
class AVLTree {
    #include "AVLTreeNode.cpp"

    AVLTreeNode dummyHead;

public:
    AVLTree() : dummyHead(T()) {}

    virtual ~AVLTree() {
        release(dummyHead.left);
    }
protected:
    // 上述3個介面(存在、插入、刪除)均依賴於這個內部介面。
    // 返回給定資料所在的節點。如果不存在的話,就返回插入操作中所插入的節點的父節點。
    AVLTreeNode *find(T data) {
        auto cur = dummyHead.left;
        if (cur == nullptr) return &dummyHead;
        while (cur->data != data) {
            if (data < cur->data) {
                if (cur->left == nullptr) {
                    return cur;
                }
                cur = cur->left;
            } else {
                if (cur->right == nullptr) {
                    return cur;
                }
                cur = cur->right;
            }
        }
        return cur;
    }

public:
    // 判斷AVL樹中是否存在指定資料所在的節點。
    bool exists(T data) {
        AVLTreeNode *found = find(data);
        return found != &dummyHead && found->data == data;
    }

    // 如果給定資料所在的節點不存在,則插入節點。
    void insert(T data) {
        AVLTreeNode *found = find(data);
        found->insert(data);
    }

    // 如果存在給定資料所在的節點,則刪除節點。
    void remove(T data) {
        AVLTreeNode *found = find(data);
        if (found != &dummyHead && found->data == data) found->remove();
    }

protected:
    // 釋放以給定節點為根節點的子樹所佔用的記憶體空間
    void release(AVLTreeNode *node) {
        if (node == nullptr) return;
        release(node->left);
        release(node->right);
        delete node;
    }
};

PS:事實上,實際的程式碼實現比這還要複雜,還要考慮到過載賦值運算子、傳參的時候傳入的是左值引用還是右值引用等等,這裡主要是為了講述原理就不搞那麼複雜了。

相關文章