1. 程式人生 > >【LeetCode & 劍指offer刷題】樹題1:二叉樹的遍歷總結(前序、中序、後序、層序、 之字形層序、垂直遍歷)

【LeetCode & 劍指offer刷題】樹題1:二叉樹的遍歷總結(前序、中序、後序、層序、 之字形層序、垂直遍歷)

【LeetCode & 劍指offer 刷題筆記】目錄(持續更新中...)

 

二叉樹的遍歷總結

(前序、中序、後序、層序、 之字形層序、垂直遍歷)

 

三種遞迴遍歷

// 前序遍歷(根-左-右) void preorder ( TreeNode * root , vector < int > & path )
{     if ( root == nullptr ) return ;     path . push_back ( root -> val );     preorder ( root
-> left , path );     preorder ( root -> right , path ); } // 中序遍歷(左-根-右) void inorder ( TreeNode * root , vector
< int > & path ) {      if ( root == nullptr ) return ;     inorder ( root -> left , path );     path . push_back ( root -> val );     inorder ( root -> right , path ); } // 後續遍歷(左-右-根) void postorder ( TreeNode * root , vector < int > & path ) {     if ( root == nullptr ) return ;     postorder ( root -> left , path );     postorder ( root -> right , path );     path . push_back ( root -> val ); }  

非遞迴遍歷

前序遍歷:(根 - 左 - 右)  根據前序遍歷訪問的順序,優先訪問根結點,然後再分別訪問左結點和右結點。即對於任一結點,其可看做是根結點,因此可以直接訪問,訪問完之後,若其左結點不為空,按相同規則訪問它的左子樹;當訪問其左子樹時,再訪問它的右子樹。因此其處理過程如下:      對於任一結點P:      1)訪問結點P並將結點P入棧;      2)判斷結點 P的左孩子 是否為空, 若為空 ,則取棧頂結點並進行 出棧 操作,並將棧頂結點的 右孩子置為當前的結點P ,迴圈至1); 若不為空 ,則將P的 左孩子置為當前的結點P ;      3)直到P為NULL並且棧為空,則遍歷結束。   // 非遞迴前序遍歷 class Solution { public :     vector < int > preorderTraversal ( TreeNode * root )     {         vector < int > path ;         if ( root == nullptr ) return path ;                 stack < TreeNode *> s ;         TreeNode * p = root ;         while(p || !s.empty())         {             if (p ) // 當左結點不為空時             {                 path.push_back(p->val); //訪問當前結點(父結點)                 s . push ( p ); // 入棧                   p = p -> left ; // 指向下一個左結點             }             else              // 當左結點為空時             {                 p = s . top ();                 s . pop ();         // 出棧                   p = p -> right ;     // 指向右結點             }         }         return path ;     } };   中序遍歷:(左 - 根 - 右) 根據中序遍歷的順序,對於任一結點,優先訪問其左孩子,而左孩子結點又可以看做一根結點,然後繼續訪問其左孩子結點,直到遇到左孩子結點為空的結點才進行訪問,然後按相同的規則訪問其右子樹。因此其處理過程如下:    對於任一結點P,   1)若其左孩子不為空,則將P入棧並將P的左孩子置為當前的P,然後對當前結點P再進行相同的處理;   2)若其 左孩子為空,則取棧頂元素 並進行出棧操作, 訪問該棧頂結點 ,然後將當前的 P置為棧頂結點的右孩子   3)直到P為NULL並且棧為空則遍歷結束 /**  * Definition for a binary tree node.  * struct TreeNode {  *     int val;  *     TreeNode *left;  *     TreeNode *right;  *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}  * };  */ // 非遞迴中序遍歷 class Solution { public :     vector < int > inorderTraversal ( TreeNode * root )     {         vector < int > path ;         if ( root == nullptr ) return path ;                 stack < TreeNode *> s ;         TreeNode * p = root ;         while (p  || ! s . empty ())         {             if (p ) // 當左結點不為空時             {                 s . push ( p ); // 入棧                   p = p -> left ; // 指向下一個左結點             }             else              // 當左結點為空時             {                 p = s . top ();                  path.push_back(p->val); //訪問棧頂元素(父結點)                 s . pop ();         // 出棧                                 p = p -> right ;     // 指向右結點             }         }         return path ;     } };   後序遍歷:(左 - 右 - 根) 要保證根結點在左孩子和右孩子訪問之後才能訪問,因此對於任一結點P,先將其入棧。 (1) 如果P不存在左孩子和右孩子 ,則可以 直接訪問 它; (2) 或者P存在左孩子或者右孩子 但是 其左孩子和右孩子都 已被訪問過了 ,則同樣可以 直接訪問該結點 (3) 若非上述兩種情況 ,則將 P的右孩子和左孩子依次入棧 ,這樣就保證了每次取棧頂元素的時候,左孩子在右孩子前面被訪問,左孩子和右孩子都在根結點前面被訪問。   vector < int > postorderTraversal ( TreeNode * root ) {     vector < int > result ;     stack < TreeNode *> s ;      if ( root == nullptr ) return result ;          TreeNode * p ; // 當前結點指標     TreeNode * pre = nullptr ; // 用於記錄上一次訪問的結點     s .push(root);            //根結點指標入棧      while(!s.empty()) //不為空時才會入棧,故p不可能為nullptr,無需像之前加p的判斷      {         p = s.top();   // 指向棧頂元素          bool temp1 = p -> left == nullptr && p -> right == nullptr ; // 如果當前結點為葉子結點          bool temp2 = pre != nullptr && ( pre == p -> left || pre == p -> right ); // 或者當前結點的左結點和右結點都已被訪問過了(若 pre=p->left 說明右結點為空,因為棧中按照根右左這樣的順序入棧,根左這種結構才能出現這種情況                  if (! temp1 && ! temp2 )//如果不是上面兩種情況,直接入棧          {              // 先將右結點入棧,再將左結點入棧,這樣可以保證之後訪問時先訪問左結點在訪問右結點              if ( p -> right ) s . push ( p -> right ); // 右結點入棧              if ( p -> left ) s . push ( p -> left );    // 左結點入棧          }         else          {                result.push_back(p->val); //訪問順序:左、右、根               s . pop ();             pre = p ; // 儲存剛剛訪問過的結點          }        }           return result ;      }   如果只是產生後序遍歷序列可以用以下方法: (學習連結串列用於頭部插入的技巧) 嚴格來說該方法不是按照後序遍歷的順序去訪問各結點的 vector < int > postorderTraversal ( TreeNode * root ) {     list <int> temp; //開闢臨時連結串列     stack < TreeNode *> s ; // 儲存各結點指標     TreeNode * p = root ;           while (p  || ! s . empty ())      {          while (p ) // 右結點不為空時          {             s . push ( p );             temp . push_front ( p -> val ); // 在頭部插入元素,用連結串列比較好,和前序遍歷相反             p = p -> right ; // 指向下一個右結點,和前序遍歷相反          }          if (! s . empty ()) // 右結點為空時          {             p = s . top ();             s . pop (); // 出棧             p = p -> left ; // 指向左結點,和前序遍歷相反          }      }          vector < int > result ;     copy ( temp . begin (), temp . end (), back_inserter ( result )); // list 中元素複製到 vector      return result ; }   參考: 《更簡單的非遞迴遍歷二叉樹的方法》 二叉樹的非遞迴遍歷leetcode: Preorder, Inorder, and Postorder Iteratively Summarization  

其他遍歷方式:層序遍歷、 之字形層序遍歷、垂直遍歷

102 .   Binary Tree Level Order Traversal Given a binary tree, return the   level order   traversal of its nodes' values. (ie, from left to right, level by level). For example: Given binary tree  [3,9,20,null,null,15,7] , 3 / \ 9 20     / \     15 7 return its level order traversal as: [ [3], [9,20], [15,7] ]   /**  * Definition for a binary tree node.  * struct TreeNode {  *     int val;  *     TreeNode *left;  *     TreeNode *right;  *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}  * };  */ //問題:按層次遍歷,每層存於vector中 //方法一:遞迴法 O(n), O(1) //實際上就是用的前序遍歷的思想(前序遍歷對每一層而言,靠左的先訪問,滿足層序遍歷規律),區別在於每次遞迴傳入了level資訊 class Solution { public :     vector < vector < int >> levelOrder ( TreeNode * root )     {         vector < vector < int >> result ; // 建立可以存放 vector 的空容器         traverse ( root , 1 , result );     // 從第一層開始遍歷         return result ;     }        void traverse ( TreeNode * root , int level , vector < vector < int >>& result )     {                if ( root == NULL ) return ; // 遞迴的出口(包括遞迴子函式的出口)         if ( level > result.size() )         {             result . push_back ( vector < int >()); //在下一層時,增加空容器(因為事先不知道樹的層數,故要一邊遍歷,一邊增加容器大小)         }                result [ level -  1 ]. push_back ( root -> val ); // 將元素值 push 進第 level 層的容器(索引從 0 開始)         traverse ( root -> left , level + 1 , result );         traverse ( root -> right , level + 1 , result ); // 最後一個語句 return 之後,整個遞迴函式才結束            } };   /* 迭代法,O(n),O(1) 掌握 層序遍歷二叉樹是典型的廣度優先搜尋 BFS 的應用,但是這裡稍微複雜一點的是,我們要把各個層的數分開,存到一個二維向量裡面 用佇列實現 1 )首先根結點入隊 2 )訪問隊首元素,隊首元素出隊,若子結點不為空,子結點(下一層的所有結點)入隊 3 )一層一層的訪問,直至佇列清空 */ class Solution { public :     vector < vector < int > > levelOrder ( TreeNode * root )     {         vector < vector < int > > res ;         if ( root == nullptr ) return res ;           queue <TreeNode*> q;         q . push ( root ); // 根結點入隊                 while (! q . empty ())         {             vector < int > level ;             int size = q . size (); //當前層的結點數,會隨著每層結點的push,長度會變化                         for ( int i = 0 ; i <   size ; ++ i ) //遍歷該層結點,並將下一層結點入隊             {                 TreeNode * node = q . front ();                 level .push_back(node->val); //訪問當前結點                 q . pop (); // 出隊                                 // 將當前結點的左右子結點入隊                 if ( node -> left ) q . push ( node -> left );                 if ( node -> right ) q . push ( node -> right );                 // 下一層的結點排在上一層結點之後             }                         res .push_back(level);         }         return res ;     } };   103 .   Binary Tree Zigzag Level Order Traversal   Given a binary tree, return the   zigzag level order   traversal of its nodes' values. (ie, from left to right, then right to left for the next level and alternate between). For example: Given binary tree  [3,9,20,null,null,15,7], 3 / \ 9 20     / \     15 7 return its zigzag level order traversal as: [ [3], [20,9], [15,7] ]
  /**  * Definition for a binary tree node.  * struct TreeNode {  *     int val;  *     TreeNode *left;  *     TreeNode *right;  *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}  * };  */ //分析:廣度優先遍歷(??感覺遍歷順序是先序遍歷,為深度優先遍歷),用一個bool記錄是從左到右還是從右到左,每一層結束就翻轉一下 //用level-order遍歷,用奇數層偶數層判斷,偶數層時反向存數 class Solution { public :     vector < vector < int >> zigzagLevelOrder ( TreeNode * root )     {         vector < vector < int >> result ; // 建立包含子容器的容器         traverse ( root , 1 , result );         return result ;     }        // 遞迴函式的功能:按 Zigzag Level Order 掃描某一層(第 level 層)的元素,存在一個 vector     void traverse ( TreeNode * root , int level , vector < vector < int >>& result )     {