本周小結!(二叉樹)

2021-02-20 代碼隨想錄

以後每周加上一個本周小結怎麼樣?

❞本周小結

發現大家周末的時候貌似都不在學習狀態,周末的文章瀏覽量和打卡情況照工作日差很多呀,可能是本周日是工作日了,周六得好好放鬆放鬆,哈哈,理解理解,但我還不能不更啊,還有同學要看呢。

所以呢,「周日我做一個針對本周的打卡留言疑問以及在刷題群裡的討論內容做一下梳理吧。」,這樣也有助於大家補一補本周的內容,消化消化。

「注意這個周末總結和系列總結還是不一樣的(二叉樹還遠沒有結束),這個總結是針對留言疑問以及刷題群裡討論內容的歸納。」

周一

本周我們開始講解了二叉樹,在關於二叉樹,你該了解這些!中講解了二叉樹的理論基礎。

有同學會把紅黑樹和二叉平衡搜索樹弄分開了,其實紅黑樹就是一種二叉平衡搜索樹,這兩個樹不是獨立的,所以C++中map、multimap、set、multiset的底層實現機制是二叉平衡搜索樹,再具體一點是紅黑樹。

對於二叉樹節點的定義,C++代碼如下:

struct TreeNode {
    int val;
    TreeNode *left;
    TreeNode *right;
    TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};

對於這個定義中TreeNode(int x) : val(x), left(NULL), right(NULL) {} 有同學不清楚幹什麼的。

這是構造函數,這麼說吧C語言中的結構體是C++中類的祖先,所以C++結構體也可以有構造函數。

構造函數也可以不寫,但是new一個新的節點的時候就比較麻煩。

例如有構造函數,定義初始值為9的節點:

TreeNode* a = new TreeNode(9);

沒有構造函數的話就要這麼寫:

TreeNode* a = new TreeNode(); 
a->val = 9; 
a->left = NULL;
a->right = NULL;  

在介紹前中後序遍歷的時候,有遞歸和迭代(非遞歸),還有一種牛逼的遍歷方式:morris遍歷。

morris遍歷是二叉樹遍歷算法的超強進階算法,morris遍歷可以將非遞歸遍歷中的空間複雜度降為O(1),感興趣大家就去查一查學習學習,比較小眾,面試幾乎不會考。我其實也沒有研究過,就不做過多介紹了。

周二

在二叉樹:一入遞歸深似海,從此offer是路人中講到了遞歸三要素,以及前中後序的遞歸寫法。

文章中我給出了leetcode上三道二叉樹的前中後序題目,但是看完二叉樹:一入遞歸深似海,從此offer是路人,依然可以解決n叉樹的前後序遍歷,在leetcode上分別是

大家可以再去把這兩道題目做了。

周三

在二叉樹:聽說遞歸能做的,棧也能做!中我們開始用棧來實現遞歸的寫法,也就是所謂的迭代法。

細心的同學發現文中前後序遍歷空節點是入棧的,其實空節點入不入棧都差不多,但感覺空節點不入棧確實清晰一些,符合文中動畫的演示。

前序遍歷空節點不入棧的代碼:(注意注釋部分,和文章中的區別)

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        stack<TreeNode*> st;
        vector<int> result;
        if (root == NULL) return result;
        st.push(root);
        while (!st.empty()) {
            TreeNode* node = st.top();                       // 中
            st.pop();
            result.push_back(node->val);
            if (node->right) st.push(node->right);           // 右(空節點不入棧)
            if (node->left) st.push(node->left);             // 左(空節點不入棧)
        }
        return result;
    }
};

後序遍歷空節點不入棧的代碼:(注意注釋部分,和文章中的區別)

class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) {
        stack<TreeNode*> st;
        vector<int> result;
        if (root == NULL) return result;
        st.push(root);
        while (!st.empty()) {
            TreeNode* node = st.top();
            st.pop();
            result.push_back(node->val);
            if (node->left) st.push(node->left); // 相對於前序遍歷,這更改一下入棧順序 (空節點不入棧)
            if (node->right) st.push(node->right); // 空節點不入棧
        }
        reverse(result.begin(), result.end()); // 將結果反轉之後就是左右中的順序了
        return result;
    }
};

在實現迭代法的過程中,有同學問了:遞歸與迭代究竟誰優誰劣呢?

從時間複雜度上其實迭代法和遞歸法差不多(在不考慮函數調用開銷和函數調用產生的堆棧開銷),但是空間複雜度上,遞歸開銷會大一些,因為遞歸需要系統堆棧存參數返回值等等。

遞歸更容易讓程式設計師理解,但收斂不好,容易棧溢出。

這麼說吧,遞歸是方便了程式設計師,難為了機器(各種保存參數,各種進棧出棧)。

「在實際項目開發的過程中我們是要儘量避免遞歸!因為項目代碼參數、調用關係都比較複雜,不容易控制遞歸深度,甚至會棧溢出。」

周四

在二叉樹:前中後序迭代方式的寫法就不能統一一下麼?中我們使用空節點作為標記,給出了統一的前中後序迭代法。

此時又多了一種前中後序的迭代寫法,那麼有同學問了:前中後序迭代法是不是一定要統一來寫,這樣才算是規範。

其實沒必要,還是自己感覺哪一種更好記就用哪種。

但是「一定要掌握前中後序一種迭代的寫法,並不因為某種場景的題目一定要用迭代,而是現場面試的時候,面試官看你順暢的寫出了遞歸,一般會進一步考察能不能寫出相應的迭代。」

周五

在二叉樹:層序遍歷登場!中我們介紹了二叉樹的另一種遍歷方式(圖論中廣度優先搜索在二叉樹上的應用)即:層序遍歷。

看完這篇文章,去leetcode上怒刷五題,文章中 編號107題目的樣例圖放錯了(原諒我匆忙之間總是手抖),但不影響大家理解。

只有同學發現leetcode上「515. 在每個樹行中找最大值」,也是層序遍歷的應用,依然可以分分鐘解決,所以就是一鼓作氣解決六道了,哈哈。

「層序遍歷遍歷相對容易一些,只要掌握基本寫法(也就是框架模板),剩下的就是在二叉樹每一行遍歷的時候做做邏輯修改。」

周六

在二叉樹:你真的會翻轉二叉樹麼?中我們把翻轉二叉樹這麼一道簡單又經典的問題,充分的剖析了一波,相信就算做過這道題目的同學,看完本篇之後依然有所收穫!

「文中我指的是遞歸的中序遍歷是不行的,因為使用遞歸的中序遍歷,某些節點的左右孩子會翻轉兩次。」

如果非要使用遞歸的中序的方式寫,也可以,如下代碼就可以避免節點左右孩子翻轉兩次的情況:

class Solution {
public:
    TreeNode* invertTree(TreeNode* root) {
        if (root == NULL) return root;
        invertTree(root->left);         // 左
        swap(root->left, root->right);  // 中
        invertTree(root->left);         // 注意 這裡依然要遍歷左孩子,因為中間節點已經翻轉了
        return root;
    }
};

代碼雖然可以,但這畢竟不是真正的遞歸中序遍歷了。

但使用迭代方式統一寫法的中序是可以的。

代碼如下:

class Solution {
public:
    TreeNode* invertTree(TreeNode* root) {
        stack<TreeNode*> st;
        if (root != NULL) st.push(root);
        while (!st.empty()) {
            TreeNode* node = st.top();
            if (node != NULL) {
                st.pop();
                if (node->right) st.push(node->right);  // 右
                st.push(node);                          // 中
                st.push(NULL);
                if (node->left) st.push(node->left);    // 左

            } else {
                st.pop();
                node = st.top();
                st.pop();
                swap(node->left, node->right);          // 節點處理邏輯
            }
        }
        return root;
    }
};


為什麼這個中序就是可以的呢,因為這是用棧來遍歷,而不是靠指針來遍歷,避免了遞歸法中翻轉了兩次的情況,大家可以畫圖理解一下,這裡有點意思的。

總結

「本周我們都是講解了二叉樹,從理論基礎到遍歷方式,從遞歸到迭代,從深度遍歷到廣度遍歷,最後再用了一個翻轉二叉樹的題目把我們之前講過的遍歷方式都串了起來。」

下周依然是二叉樹,大家加油!

相關焦點

  • 平衡二叉樹專題
    力扣關於平衡二叉樹的題目還是有一些的,並且都非常經典,推薦大家練習。今天給大家精選了 4 道題,如果你徹底搞明白了這幾道題,碰到其他的平衡二叉樹的題目應該不至於沒有思路。當你領會了我的思路之後, 建議再找幾個題目練手,鞏固一下學習成果。110.
  • 什麼是平衡二叉樹(AVL)
    1 為什麼要有平衡二叉樹二叉搜索樹一定程度上可以提高搜索效率,但是當原序列有序時,例如序列 A = {1,2,3,4,5,6},構造二叉搜索樹如圖 1.1。依據此序列構造的二叉搜索樹為右斜樹,同時二叉樹退化成單鍊表,搜索效率降低為 O(n)。圖 1.1在此二叉搜索樹中查找元素 6 需要查找 6 次。
  • 二叉樹的應用—二叉樹遍歷的應用
    =NULL) return(Search(bt->lchild,x));/*在bt->lchild 為根結點指針的二叉樹中查找數據元素x*/if (bt->rchild!(SqBiTree bt,int k){/*一維數組bt[2k-1]為二叉樹存儲結構,k 為二叉樹深度,函數值為葉子數。
  • 數據結構(八)--平衡二叉樹
    該來的總會來,平衡二叉樹果然又來了....出現背景前文已經研究過普通的二叉樹,為什麼要用二叉樹呢?因為二叉樹的結構可以實現二分法查找的效果。你比如前文介紹的滿二叉樹:如下圖所示,如果你想要查找4號元素,你只需要遍歷3次即可。所以在理想情況下,二叉樹可以優化遍歷。
  • 二叉查找樹 Java實現
    二叉查找樹(Binary Search Tree),又被稱為二叉搜索樹。
  • 數據結構 | 四種平衡二叉樹介紹
    即使是一般的樹也可以轉化為二叉樹,並且二叉樹的存儲結構和算法相對而言都比較簡單。因此研究二叉樹對於研究數據結構有著重要的意義。基於二叉樹在數據結構中的重要作用,本篇文章中將探討二叉樹的一個重要應用,即作為二叉搜索樹時能夠發揮的作用。
  • 騰訊面試題:有了二叉查找樹、平衡樹為啥還需要紅黑樹?
    1、二叉查找樹的缺點二叉查找樹,相信大家都接觸過,二叉查找樹的特點就是左子樹的節點值比父親節點小,而右子樹的節點值比父親節點大,如圖基於二叉查找樹的這種特點,我們在查找某個節點的時候,可以採取類似於二分查找的思想,快速找到某個節點。n 個節點的二叉查找樹,正常的情況下,查找的時間複雜度為 O(logn)。
  • 五分鐘帶你玩轉平衡二叉樹
    通過上一篇二叉查找樹的文章,相信大家已經掌握了二叉查找樹的相關概念和操作,如果忘了可以通過下方的連結進行學習。帶你玩轉二叉查找樹本文呢,我們將在二叉查找樹的基礎上,繼續學習一種新的樹狀結構-平衡二叉樹。
  • 二叉查找樹之 Java的實現【上】
    二叉查找樹(Binary Search Tree),又被稱為二叉搜索樹。它是特殊的二叉樹:對於二叉樹,假設x為二叉樹中的任意一個結點,x節點包含關鍵字key,節點x的key值記為key[x]。如果y是x的左子樹中的一個結點,則key[y] <= key[x];如果y是x的右子樹的一個結點,則key[y] >= key[x]。那麼,這棵樹就是二叉查找樹。如下圖所示:
  • LeetCode 例題精講 | 11 二叉樹轉化為鍊表:二叉樹遍歷中的相鄰結點
    Convert Binary Tree to Sorted Doubly Linked List 二叉樹轉化為鍊表(Medium)本文將介紹二叉樹問題中一個特殊的技巧:「在二叉樹的前/中/後序遍歷時對相鄰結點進行操作」。這種方法不適用於大多數題目,但在一些特定的題目中使用這個技巧,能起到「秒殺」的效果。
  • 一文讀懂平衡二叉樹|技術頭條
    作者 | 宋廣澤責編 | 伍杏玲出品 | CSDN(ID:CSDNnews)平衡二叉樹,又稱AVL樹,指的是左子樹上的所有節點的值都比根節點的值小,而右子樹上的所有節點的值都比根節點的值大,且左子樹與右子樹的高度差最大為1。因此,平衡二叉樹滿足所有二叉排序(搜索)樹的性質。
  • Scratch-二叉樹繪畫
    [定義]二叉樹是一種特殊的樹,它具有以下特點:(1)樹中每個節點最多只能有兩棵樹,即每個節點的度最多為2。 (3)二叉樹即使只有一個子樹時,也要區分是左子樹還是右子樹。
  • 深入理解樹(二叉、二叉搜索樹)
    (B的高度為2,B—F—J);樹的高度:是樹中所有結點高度的最大值,樹的深度是樹中所有結點深度的最大值,對於同一棵樹,其深度和高度是相同的,但是對於各個結點,其深度和高度不一定相同;二叉樹如果一棵樹中的每個結點有0,1或者2個孩子結點,那麼這棵樹就稱為二叉樹;空樹也是一顆有效的二叉樹,一顆二叉樹可以看做是由根節點和兩棵不相交的子樹
  • 平衡二叉樹 AVL樹結構詳解 [Java實現]
    作者NeroJings來源https://blog.csdn.net/zhang6622056/article/details/82698859本文思維導圖簡述先不說平衡二叉樹,我們單開來說,這樣比較方便理解。
  • 一個套路搞定二叉樹遍歷
    遍歷是對樹的一種最基本運算
  • 「算法與數據結構」二叉樹之美
    我們可以根據維基百科的依據來作為分類的標準👇無序樹:樹中任意節點的子節點之間沒有順序關係,這種樹稱為無序樹,也稱為自由樹;有序樹:樹中任意節點的子節點之間有順序關係,這種樹稱為有序樹;完全二叉樹:對於一顆二叉樹,假設其深度為d(d>1)。
  • 二叉樹詳解,還包含C代碼
    給定一個二叉查找樹中的結點,找出在中序遍歷下它的後繼和前驅;1 前序遍歷,中序遍歷,後序遍歷;1.1 前序遍歷對於當前結點,先輸出該結點,然後輸出它的左孩子,最後輸出它的右孩子。Morris 提出了二叉樹線索化,解決了這個問題。(根據這個概念我們又提出了一個新的數據結構,即線索二叉樹,因線索二叉樹不是本文要介紹的內容,所以有興趣的朋友請移步線索二叉樹)前序,中序,後序遍歷,不管是遞歸版本還是非遞歸版本,都用到了一個數據結構--棧,為何要用棧?
  • 二叉樹遍歷就是這麼簡單(必殺)
    看起來很刺激,不要謊,讓我慢慢道來。什麼是二叉樹學習二叉樹我們想,為什麼叫做二叉樹呢?類比樹,我們這個結構是不是有樹根樹杈呢?然後,我們再聲明一個二維指針,用來指向樹的根節點BinTreeNode** t;當這個樹雛形出來了後,我們需要做的就是完善這個二叉樹
  • 數據結構+算法(第11篇)玩平衡二叉樹就像蹺蹺板一樣簡單!
    二分查找樹》中提到了:平衡二叉樹的目的就是使得平均查找長度最短。那麼這裡就引出兩個問題:什麼是平衡二叉樹?為什麼平衡二叉樹的平均查找長度最短?如何將非平衡二叉樹調整成平衡二叉樹?1. 平衡二叉樹是什麼鬼?
  • 平衡二叉樹 - leetcode 劍指offer系列
    準時更新公眾號 每日精選算法題, 大家記得關注哦~ 另外在公眾號裡回復 offer 就能看到劍指 offer 系列當前連載的所有文章了題目描述輸入一棵二叉樹的根節點,判斷該樹是不是平衡二叉樹如果某二叉樹中任意節點的左右子樹的深度相差不超過 1,那麼它就是一棵平衡二叉樹。