【LeetCode & 劍指offer刷題】樹題1:二叉樹的遍歷總結(前序、中序、後序、層序、 之字形層序、垂直遍歷)
阿新 • • 發佈:2019-01-05
【LeetCode & 劍指offer 刷題筆記】目錄(持續更新中...)
二叉樹的遍歷總結
(前序、中序、後序、層序、 之字形層序、垂直遍歷)
三種遞迴遍歷
// 前序遍歷(根-左-右) void preorder ( TreeNode * root , vector < int > & path )非遞迴遍歷
前序遍歷:(根 - 左 - 右) 根據前序遍歷訪問的順序,優先訪問根結點,然後再分別訪問左結點和右結點。即對於任一結點,其可看做是根結點,因此可以直接訪問,訪問完之後,若其左結點不為空,按相同規則訪問它的左子樹;當訪問其左子樹時,再訪問它的右子樹。因此其處理過程如下: 對於任一結點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 ) {