二叉樹的遍歷

前中后序的遞歸實現

    void ff(TreeNode* p,vector<int> &v){
        if(p==nullptr)
            return;
        v.push_back(p->val);
        ff(p->left,v);
        ff(p->right,v);
    }
    void mf(TreeNode* p,vector<int> &v){
        if(p==nullptr)
            return;
        mf(p->left,v);
        v.push_back(p->val);
        mf(p->right,v);
    }
    void bf(TreeNode* p,vector<int> &v){
        if(p==nullptr)
            return;
        bf(p->left,v);
        bf(p->right,v);
        v.push_back(p->val);
    }

前中后序的非遞歸標準實現

    void ff(TreeNode* root,vector<int>& v){
        TreeNode* p=root;
        stack<TreeNode*> s;
        while(!s.empty() || p){
            while(p){
                v.push_back(p->val);//Visit
                s.push(p);
                p=p->left;
            }
            p=s.top();
            s.pop();
            p=p->right;
        }
    }
    void mf(TreeNode* root,vector<int>& v){
        TreeNode* p=root;
        stack<TreeNode*> s;
        while(!s.empty() || p){
            while(p){
                s.push(p);
                p=p->left;
            }
            p=s.top();
            v.push_back(p->val);//Visit
            s.pop();
            p=p->right;
        }
    }
    void bf(TreeNode* root,vector<int>& v){
        TreeNode* p=root,*plast=nullptr;
        stack<TreeNode*> s;
        while(!s.empty() || p){
            while(p){
                s.push(p);
                p=p->left;
            }
            p=s.top();
            if(!p->right || plast==p->right){
                v.push_back(p->val);//Visit
                s.pop();
                plast=p;
                p=nullptr;//既然沒有右子樹或者右子樹已經被訪問,那么p就沒有用了,應該讓它失效,這樣才會從棧頂,彈出根節點
            }else
                p=p->right;
            
        }
    }

總結

整體的思路是這樣的:

  • 指針p指向root,創建棧
  • 當棧不為空或p有效時,循環:
    1. 沿著根節點的左分支一路壓入根節點(如果是前序遍歷此時就可以訪問p)
    2. 循環結束時p為空,此時p失效。
    3. 當p失效時,從棧頂彈出結點(如果是中序遍歷此時就可以訪問p)。此時彈出的一定是底層的結點。
    4. 對該結點進行訪問,并嘗試訪問其右分支。
    5. 右分支的結點視作根節點,重復以上過程。

當后序遍歷時,預先設置plast為空,以記錄上一次訪問的結點。
取棧頂元素作p,

  • 當p無右分支或p的右分支已被訪問時
    1. 訪問p
    2. 彈出棧頂
    3. 更新plast
    4. 設置p失效(p失效之后才會從棧頂彈元素)
  • 否則(當p有右分支且右分支沒有被訪問)
    p=p->right 右分支作根節點進入循環進行訪問

簡單來說,p相當于當前操作的子樹;當p失效時,彈出棧頂元素,即使當前操作的子樹的根。左邊處理完了,就索引根,通過根來索引右子樹。

層遍歷與前序遍歷的非標準實現

前序遍歷的非標準實現

流程:

  • 判斷root是否為空
  • 指針p指向root
  • 創建棧并壓入p
  • 當棧不為空時:循環
    1. 彈出棧頂元素進行訪問
    2. 如果該元素有右分支,壓入右孩子
    3. 如果該元素有左分支,壓入左孩子
    void ff2(TreeNode* root,vector<int>& v){
        if(!root)
            return;
        TreeNode* p=root;
        stack<TreeNode*> s;
        s.push(p);
        while(!s.empty()){
            p=s.top();
            s.pop();
            v.push_back(p->val);
            if(p->right)
                s.push(p->right);
            if(p->left)
                s.push(p->left);
        }
    }

層遍歷

流程:

  • 判斷root是否為空
  • 指針p指向root
  • 創建隊列并壓入p
  • 當隊列不為空時:循環
    1. 彈出隊首元素進行訪問
    2. 如果該元素有左分支,壓入左孩子
    3. 如果該元素有右分支,壓入右孩子
    void layer(TreeNode* root,vector<int> &v){
        if(!root)
            return;
        TreeNode* p=root;
        queue<TreeNode*> q;
        q.push(p);
        
        while(!q.empty()){
            p=q.front();
            v.push_back(p->val);
            q.pop();
            if(p->left)
                q.push(p->left);
            if(p->right)
                q.push(p->right);
        }
    }

擴展:層打印

按層打印到一個二維數組中。
該問題的難點在于如何判斷當前訪問的結點是不是某一行的行末。所以我們使用nlast指針,來記錄當前行的行末;用last指針來記錄最近壓入隊列的結點。

  • 初始時last和nlast都為root
  • 循環時,last記錄最近壓入的結點
  • 當前訪問的結點等于nlast時,表示已經訪問到了某一行的行末。這個時候做行末相關的處理,并將nlast更新為last。因為當某一行訪問完成后,新壓入的結點,一定是下一行的行尾。
    void layer(TreeNode* root,vector<vector<int> > &v){
        if(!root)
            return;
        TreeNode* p=root;
        queue<TreeNode*> q;
        q.push(p);
        TreeNode* last=root,*nlast=root; //last和nlast初始化為root
        vector<int> temp;
        while(!q.empty()){
            p=q.front();
            q.pop();
            temp.push_back(p->val);
            if(p->left){
                q.push(p->left);
                last=p->left;  //記錄last
            }
            if(p->right){
                q.push(p->right);
                last=p->right;//記錄last
            }
            if(p==nlast){//如果p指針已經到達了一行的行末,那么最新壓入棧的last一定是下一行的行末
                v.push_back(temp);
                temp.clear();
                nlast=last;
            }
        }
    }

擴展:判滿、完全二叉樹

判斷完全二叉樹

思路:按層遍歷二叉樹,直到發現第一個非滿結點(單左或葉子結點,不可以是單右)。在這個結點之后,所有的結點都是葉子結點。那么該樹就是完全二叉樹。

    bool chk(TreeNode* root) {
        //按層遍歷,找到第一個不滿結點,其后所有結點必須是葉子
        if(!root)
            return false;
        TreeNode *p=root;
        queue<TreeNode*> que;
        que.push(p);
        int find=0;
        while(!que.empty()){
            p=que.front();
            que.pop();
            if(p->left){
                que.push(p->left);
            }
            if(p->right){
                que.push(p->right);
            }
            if(!p->right){//沒有右樹,就涵蓋了葉子和單左 兩種情況
                find=1;
                continue;
            }else{//如果有右樹,看看是葉子,還是單右。單右直接返回false
                if(!p->left)
                    return false;
            }
            if(find==1 && (p->left || p->right)){//注意優先級,&&高于||
                return false;
            }
        }
        return true;
    }

判斷滿二叉樹

思路:按層遍歷二叉樹,直至發現第一個非滿結點(且必須是葉子結點)。在該結點之后的所有結點都是葉子結點,且該結點前一個節點是某行的行末。
實現:用一個plast指針來記錄上次訪問的結點。其他按照判完全二叉樹的方法來進行即可。代碼未經過驗證就不寫了。另外,nlast更新時可以按照nlast=nlast->right來更新,這樣就不用last指針了。

擴展:二叉樹的規則化構建

根節點的值是1,其左右孩子分別是1和2,每個孩子的左右孩子依然是1和2。以此來構建N層的二叉樹。
思路:首先需要保證N大于0.建立總根節點root,然后初始化p和nlast為root。然后共遍歷n-2層,每一層遍歷時都把結點添加左右孩子。比如n==3時,應該遍歷中間的一層就好。

    TreeNode* buildBt(int n){
        if(!n)
            return nullptr;
        TreeNode* root=new TreeNode(1);
        TreeNode *p=root,*nlast=root;
        queue<TreeNode*> que;
        que.push(p);
        ----n;
        while(n){
            p=que.front();
            que.pop();
            p->left=new TreeNode(1);
            p->right=new TreeNode(2);
            que.push(p->left);
            que.push(p->right);
            if(p==nlast->right){//如果發現遍歷至某一行尾,則更新n和nlast
                --n;
                nlast=p;
                if(n<=0)
                    break;
            }
        }
        return root;
    }
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • -先序遍歷: 訪問根結點,先序遍歷其左子樹,先序遍歷其右子樹;運用到遞歸void PreOrderTraversa...
    Spicy_Crayfish閱讀 2,054評論 0 0
  • 二叉樹的三種常用遍歷方式 學習過數據結構的同學都清楚,除了層序遍歷外,二叉樹主要有三種遍歷方式: 1. 先序遍歷...
    SherlockBlaze閱讀 1,247評論 0 4
  • 樹(tree)是一種抽象數據類型(ADT)或是實作這種抽象數據類型的數據結構,用來模擬具有樹狀結構性質的數據集合。...
    曾大穩丶閱讀 1,041評論 0 1
  • 數據結構和算法--二叉樹的實現 幾種二叉樹 1、二叉樹 和普通的樹相比,二叉樹有如下特點: 每個結點最多只有兩棵子...
    sunhaiyu閱讀 6,529評論 0 14
  • 現在決定把自己很久以前的一些文章重新markdown一下,發到簡書來,先從這篇二叉樹的遍歷說起的。大家都知道二叉樹...
    董成鵬閱讀 381評論 0 1