1. 程式人生 > 其它 >c/c++補完計劃(五):平衡二叉樹和二叉搜尋樹

c/c++補完計劃(五):平衡二叉樹和二叉搜尋樹

技術標籤:c++c++資料結構

目錄

前言

來看維基的說明:
AVL樹:是最早被髮明的自平衡二叉查詢樹。在AVL樹中,任一節點對應的兩棵子樹的最大高度差為1,因此它也被稱為高度平衡樹。查詢、插入和刪除在平均和最壞情況下的時間複雜度都是 O ( log ⁡ n ) {\displaystyle O(\log {n})} O(logn)。增加和刪除元素的操作則可能需要藉由一次或多次樹旋轉,以實現樹的重新平衡。
從查詢樹的角度來看, 還是非常實用的結構, 面試也很喜歡考, 我回想了一下, 在3家以上公司遇到了, 當然有一次是因為我不會紅黑樹, 被降級要求寫AVL樹, 是我不配(手動無奈).

平衡二叉樹判斷

自頂向下

思路是, 左右子樹都要是平衡二叉樹, 且左右子樹的高度差小於2. 核心程式碼也很簡單, 基本就是把思路用程式碼寫出來.

bool isBalanced(TreeNode *root) {
    if (root == nullptr) {
        return true;
    }

    return isBalanced(root->left) && isBalanced(root->right) &&
           (abs(height(root->left) - height(root->right)) < 2);

}

然後就是高度的獲取, 當前節點的高度, 就是, 左右子樹的高度大的那個+1. 這裡你可以用系統的max函式, 也可以自己寫一個lambda, 建議自己寫一個.

int height(TreeNode *node) {
    if (node == nullptr) {
        return -1;
    }

//    return max(height(node->left), height(node->right)) + 1;
    auto max = [](int left, int right) { return left < right ? right : left; };
    return max(height(node->left), height(node->right)) + 1;
}

放到力庫跑一下, 看到效果還行.

image

自底向上

但是很明顯有很多重複計算, 而且思路是自頂向下的. 那麼考慮一個自底向上的. 也不用維護資料結構那麼複雜, 考慮傳入一個height引用.

bool isBalancedCore(TreeNode *root, int &h) {
    if (root == nullptr) {
        h = -1;
        return true;
    }

    int l, r;
    if (isBalancedCore(root->left, l) && isBalancedCore(root->right, r)
        && abs(l - r) < 2) {
        auto max = [=] { return l > r ? l : r; };
        h = max() + 1;
        return true;
    }
    return false;
}

bool isBalanced(TreeNode *root) {
    int height;
    return isBalancedCore(root, height);
}

思路是其實是後序遍歷, 先判斷左, 後判斷右, 最後是當前節點, 也就是中. 當某次不滿足時, 會直接返回false, 然後一路返回到頂, 所以複雜度肯定要優於之前的.

image

你可能會覺得有點彆扭, 因為這裡的true和false似乎可以用height搞定, 那為啥不簡化一下.

int isBalancedHelper(TreeNode *root) {
    if (root == nullptr) {
        return 0;
    }

    int left = isBalancedHelper(root->left);
    if (left == -1) return -1;
    int right = isBalancedHelper(root->right);
    if (right == -1) return -1;

    auto max = [](int l, int r) { return l > r ? l : r; };
    return abs(right - left) < 2 ? max(left, right) + 1 : -1;
}

bool isBalanced(TreeNode *root) {
    return isBalancedHelper(root) != -1;
}

可以看到, 等於是把之前的與判斷拆分了, 當返回-1, 就意味著當前子樹不是平衡樹, 然後一路返回.

image

二叉搜尋樹的最近公共祖先

這個題思路很重要, 不是難題, 一個暴力做法, 我直接儲存兩個查詢的路徑, 然後比對, 但是問題是什麼?

  • 要維護一個數組記錄路徑
  • 沒有利用起二叉搜尋樹的特性, 人家幫你弄好了左小右大的樹, 你當一般樹, 不是很搞笑嗎?

那思路其實很簡單, 當兩個節點都小於當前節點, 說明還未分叉, 繼續找; 如果一個大於當前節點, 一個小於當前節點, 就是終止條件了. 當然, 可以先排序兩個節點, 這樣判斷就少了, 不用每次判斷2個.

TreeNode *lowestCommonAncestor(TreeNode *root, TreeNode *p, TreeNode *q) {
    if (p->val > q->val) {
        auto tmp = p;
        p = q;
        q = tmp;
    }

    while (root) {
        if (root->val < p->val) {
            root = root->right;
        } else if (root->val > q->val) {
            root = root->left;
        } else {
            break;
        }
    }

    return root;
}

image

最後

這裡沒有說到AVL左旋和右旋的問題, 但是實際上不是很難, 還是要自己動動手, 才能記得住.