二叉树的各种遍历算法
二叉树的各种遍历方法有 前序遍历 中序遍历 后序遍历 层序遍历。其中前三种遍历有递归程序可以实现,但是我们也有必要掌握其非递归版本的算法实现。正好在leetcode中遇到了遍历二叉树的问题,今天在这里一并总结了。
首先,引用leetcode中关于二叉树节点的定义。
1 // Definition for binary tree 2 struct TreeNode { 3 int val; 4 TreeNode *left; 5 TreeNode *right; 6 TreeNode(int x) : val(x), left(NULL), right(NULL) {} 7 }; 8
注意,以下所有代码遵照leetcode要求,将遍历结果储存在 vector<int> end 中。
1.前序遍历
(1)递归实现
1 class Solution { 2 public: 3 vector<int> preorderTraversal(TreeNode *root) { 4 preorder(root); 5 return end; 6 } 7 void preorder(TreeNode *r){ 8 if(!r) 9 return; 10 end.push_back(r->val);//先处理父节点 11 postorder(r->left);//再处理左子节点 12 postorder(r->right);//最后处理右子节点 13 14 } 15 16 vector<int> end; 17 };
(2)非递归实现
这里解释一下非递归版本实现的算法。将root节点压入堆栈。当堆栈非空时,取出栈顶的节点,将其值输出,再依次压入其右子节点,左子节点。这个压入顺序很重要。因为前序遍历首先输出父节点,再输出左子节点,右子节点。而堆栈具有后入先出的特性,所以谁首先被访问,谁就应该被后压入堆栈,这个道理很清晰。
1 class Solution { 2 public: 3 vector<int> preorderTraversal(TreeNode *root) { 4 preorder(root); 5 return end; 6 } 7 void preorder(TreeNode *r){ 8 if(!r) 9 return ; 10 stack<TreeNode *>t; 11 t.push(r); 12 while(!t.empty()){ 13 TreeNode *root=t.top(); 14 t.pop(); 15 end.push_back(root->val); 16 if(root->right) 17 t.push(root->right);//要改一起改! 18 if(root->left)//堆栈是后入先出。如果要先遍历左子数,应该先把右节点压入堆栈! 19 t.push(root->left); 20 21 } 22 } 23 24 vector<int> end; 25 };
2.后序遍历
(1)递归实现
1 class Solution { 2 public: 3 vector<int> postorderTraversal(TreeNode *root) { 4 postorder(root); 5 return end; 6 } 7 void postorder(TreeNode *r){ 8 if(!r) 9 return; 10 postorder(r->left); 11 postorder(r->right); 12 end.push_back(r->val); 13 } 14 15 vector<int> end; 16 }
(2)非递归实现
后序遍历比前序遍历实现起来稍微复杂一点。这里用到了两个堆栈s,t.堆栈t用于储存正确的节点输出顺序,堆栈s帮助我们建立正确的t。后序遍历的输出顺序是先输出左子节点,再输出右子节点,最后输出父节点。在给定root的情况下,我们要先找到最左下边的子节点,输出其值,再输出其右兄弟节点的值,然后输出其父节点的值,依此类推。所以root要首先压入t,以保证其被最后输出。依此顺序要求,还要使得右子节点被首先压入t,左子节点最后压入t。根据堆栈后入先出的特性,应该首先将左子节点压入s,以保证其在右子节点之后被压入t,从而在右子节点之前输出。
1 class Solution { 2 public: 3 vector<int> postorderTraversal(TreeNode *root) { 4 postorder(root); 5 return end; 6 } 7 void postorder(TreeNode *r){ 8 if(!r) 9 return; 10 stack <TreeNode *>s,t; 11 TreeNode *root; 12 s.push(r); 13 while(!s.empty()){ 14 root=s.top(); 15 s.pop(); 16 t.push(root); 17 if(root->left) 18 s.push(root->left); 19 if(root->right) 20 s.push(root->right); 21 } 22 while(!t.empty()){ 23 end.push_back(t.top()->val); 24 t.pop(); 25 } 26 } 27 28 vector<int> end; 29 };
3.中序遍历
(1)递归实现
1 class Solution { 2 public: 3 vector<int> inorderTraversal(TreeNode *root) { 4 postorder(root); 5 return end; 6 } 7 void inorder(TreeNode *r){ 8 if(!r) 9 return; 10 inorder(r->left); 11 end.push_back(r->val); 12 inorder(r->right); 13 } 14 15 vector<int> end; 16 };
(2)非递归实现
1、先设一个栈t和一个指向树根的指针r,用r指指向结点的lchild并顺其而下直到r==NULL跳出循环,在这一过程中把每个节点入栈,则此时的p指向的是树的最左结点。
2、栈顶元素出栈以访问最左结点。(此步很重要,是为了实现按栈内元素的顺序后入先出访问结点访问最左结点的根结点。栈内元素逐一退栈即为中序遍历的元素顺序。)
3、访问最左结点的根结点。
4、由于将右子树理解为一个子树,对其的遍历也是采用中序遍历的方法,故将右子树的根结点理解为开始遍历树时的根结点,故可用语句p=p->rchild,则又开始了对一个树的遍历,r指针又会走遍右子树的每一个结点。
1 class Solution { 2 public: 3 vector<int> inorderTraversal(TreeNode *root) { 4 inorder(root); 5 return end; 6 } 7 void inorder(TreeNode *r){ 8 if(!r)//ROOT为空,返回 9 return ; 10 stack<TreeNode *>t; 11 while(r||!t.empty()){ 12 while(r){ 13 t.push(r); 14 r=r->left;//将左子树压入 15 } 16
17 r=t.top(); 18 t.pop();//输出最左下边的左子节点 19 end.push_back(r->val); 20 r=r->right;//转换到输出左子节点的右子节点 21
22 } 23 } 24 25 vector<int> end; 26 };
4.层序遍历
算法解释:
利用队列先进先出的特性。首先将根节点入队。每次循环将队头节点的值输出,再将队头节点的左右子节点入队,直至队列为空结束循环。这样就能按照层次的顺序输出二叉树节点,满足层序遍历的要求。
1 class Solution { 2 public: 3 vector<int> levelTraversal(TreeNode *root) { 4 level(root); 5 return end; 6 } 7 void level(TreeNode *r){ 8 if(!r) 9 return ; 10 queue<TreeNode *>q; 11 q.push(r); 12 while(!q.empty()){ 13 r=q.front(); 14 end.push_back(r->val); 15 q.pop(); 16 if(r->left) 17 q.push(r->left); 18 if(r->right) 19 q.push(r->right); 20 } 21 } 22 23 vector<int> end; 24 };