1. 程式人生 > 實用技巧 >樹(Tree)解題總結

樹(Tree)解題總結

定義

樹是一種抽象資料型別(ADT)或是實現這種抽象資料型別的資料結構,用來模擬具有樹狀結構性質的資料集合。它是由 n(n>0) 個有限節點組成一個具有層次關係的集合。。

二叉搜尋樹(Binary Search Tree,簡稱 BST)是一種很常用的的二叉樹。它的定義是:一個二叉樹中,任意節點的值要大於等於左子樹所有節點的值,且要小於等於右邊子樹的所有節點的值。

如下就是一個符合定義的 BST:

演算法模板

void traverse(TreeNode root) {
    // root 需要做什麼?在這做。
    // 其他的不用 root 操心,拋給框架
    traverse(root.left);
    traverse(root.right);
}

在二叉樹框架之上,擴展出一套 BST 遍歷框架:

void BST(TreeNode root, int target) {
    if (root.val == target)
        // 找到目標,做點什麼
    if (root.val < target) 
        BST(root.right, target);
    if (root.val > target)
        BST(root.left, target);
}

前序遍歷(遞迴):

List<int> preorder(TreeNode root) {
     List<int> ans = new List<int>();
     if (root == null) {
         return ans;
     }
     
     ans.Add(root.Val);
     ans.AddRange(preorder(root.Left));
     ans.AddRange(preorder(root.Right));
     return ans;
}

中序遍歷(遞迴):

List<int> inorder(TreeNode root) {
     List<int> ans = new List<int>();
     if (root == null) {
         return ans;
     }
     
     ans.AddRange(preorder(root.Left));
     ans.Add(root.Val);
     ans.AddRange(preorder(root.Right));
     return ans;
}

後序遍歷(遞迴):

List<int> postorder(TreeNode root) {
     List<int> ans = new List<int>();
     if (root == null) {
         return ans;
     }
     
     ans.AddRange(preorder(root.Left));
     ans.AddRange(preorder(root.Right));
     ans.Add(root.Val);
     return ans;
}

前序遍歷(迭代/棧):

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> res;  //儲存結果
        stack<TreeNode*> call;  //呼叫棧
        if(root!=nullptr) call.push(root);  //首先介入root節點
        while(!call.empty()){
            TreeNode *t = call.top();
            call.pop();  //訪問過的節點彈出
            if(t!=nullptr){
                if(t->right) call.push(t->right);  //右節點先壓棧,最後處理
                if(t->left) call.push(t->left);
                call.push(t);  //當前節點重新壓棧(留著以後處理),因為先序遍歷所以最後壓棧
                call.push(nullptr);  //在當前節點之前加入一個空節點表示已經訪問過了
            }else{  //空節點表示之前已經訪問過了,現在需要處理除了遞迴之外的內容
                res.push_back(call.top()->val);  //call.top()是nullptr之前壓棧的一個節點,也就是上面call.push(t)中的那個t
                call.pop();  //處理完了,第二次彈出節點(徹底從棧中移除)
            }
        }
        return res;
    }
};

中序遍歷(迭代/棧):

class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> res;
        stack<TreeNode*> call;
        if(root!=nullptr) call.push(root);
        while(!call.empty()){
            TreeNode *t = call.top();
            call.pop();
            if(t!=nullptr){
                if(t->right) call.push(t->right);
                call.push(t);  //在左節點之前重新插入該節點,以便在左節點之後處理(訪問值)
                call.push(nullptr); //nullptr跟隨t插入,標識已經訪問過,還沒有被處理
                if(t->left) call.push(t->left);
            }else{
                res.push_back(call.top()->val);
                call.pop();
            }
        }
        return res;
    }
};

後序遍歷(迭代/棧):

class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) {
        vector<int> res;
        stack<TreeNode*> call;
        if(root!=nullptr) call.push(root);
        while(!call.empty()){
            TreeNode *t = call.top();
            call.pop();
            if(t!=nullptr){
                call.push(t);  //在右節點之前重新插入該節點,以便在最後處理(訪問值)
                call.push(nullptr); //nullptr跟隨t插入,標識已經訪問過,還沒有被處理
                if(t->right) call.push(t->right);
                if(t->left) call.push(t->left);
            }else{
                res.push_back(call.top()->val);
                call.pop();
            }
        }
        return res;   
    }
};

要點

  • 二叉樹演算法設計的總路線:把當前節點要做的事做好,其他的交給遞迴框架,不用當前節點操心。
  • 如果當前節點會對下面的子節點有整體影響,可以通過輔助函式增長引數列表,藉助引數傳遞資訊。
  • 刪除二叉搜尋樹的節點有三種情況:
    1. A 恰好是末端節點,兩個子節點都為空,那麼它可以當場去世了
    2. A 只有一個非空子節點,那麼它要讓這個孩子接替自己的位置。
    3. A 有兩個子節點,麻煩了,為了不破壞 BST 的性質,A 必須找到左子樹中最大的那個節點,或者右子樹中最小的那個節點來接替自己。

實戰題目

1.樹的遍歷

2.樹的操作

參考資料