首页 > 代码库 > 二叉树遍历算法
二叉树遍历算法
首先,个人认为,二叉树是很能体会递归算法思想的,因为二叉树的结构是leftTree->root<-rightTree,对于每个非叶子节点,该规律都适用,因此关于二叉树的很多算法也都能用递归思想搞定。递归的优点在于代码简洁,但效率却是问题。其次,对于各种顺序的遍历,又有着相应的非递归算法,非递归算法的代码量相对大一点,但更容易掌控,而且效率更优。
先看节点结构:
1 struct Bitree{ 2 int val; 3 Bitree *left, *right; 4 Bitree(int x):val(x), left(nullptr), right(nullptr){ 5 }; 6 };
1. 中序遍历
- 递归算法
显然,中序遍历的顺序为leftTree, root, rightTree,显然先遍历左子树,然后是根,最后右子树。中序遍历的递归算法自然也就出来了。
1 void inOrderTraverse1(Bitree *root){ 3 if(root){ 5 inOrderTraverse1(root->left); 7 visit(root); 9 inOrderTraverse1(root->right); 11 } 13 }
- 非递归算法1.
非递归算法的思想也比较简单,按从左到右进行访问。先指针往左探寻到底,然后观察最后一个非空节点是否有右节点,若有,将该右节点作为新的探寻起点,再进行下一轮的探寻。显然,“一探到底”的思路需要使用stack来帮助缓存之前的节点。
1 void inOrderTraverse2(Bitree *root){ 2 stack<Bitree *> S; 3 S.push(root); 4 Bitree *p = root; 5 while(!S.empty()){ 6 p = S.top(); 7 while(p){ 8 S.push(p->left); 9 p = p->left; 10 } 11 S.pop();//pop out the nullptr 12 if(!S.empty()){ 13 p = S.top(); 14 visit(p); 15 S.pop(); 16 S.push(p->right);//push its right child into the stack 17 } 18 } 19 }
- 非递归算法2
1 void inOrderTraverse3(Bitree *root){ 2 stack<Bitree *> S; 3 Bitree *p = root; 4 while(p || !S.empty()){ 5 if(p){ 6 S.push(p); 7 p = p->left; 8 }else{ 9 p = S.top(); 10 visit(p); 11 S.pop(); 12 p = p->right; 13 } 14 } 15 }
个人认为,虽然两种非递归算法的思路完全一样,但非递归算法2比非递归算法1代码要更为简洁,更值得推荐。
2. 前序遍历
- 递归算法
前序遍历的顺序为root,leftTree,rightTree,直接上代码
1 void preOrderTraverse1(Bitree *root){ 2 if(root){ 3 visit(root); 4 preOrderTraverse1(root->left); 5 preOrderTraverse1(root->right); 6 } 7 }
- 非递归算法
对于前序遍历的非递归算法,和中序遍历的非递归算法非常相似,不过是在进栈时就访问该节点,而不是之后再访问。由于代码相似,只给出一种
1 void preOrderTraverse2(Bitree *root){ 2 stack<Bitree *> S; 3 Bitree *p = root; 4 while(p || !S.empty()){ 5 if(p){ 6 visit(p); 7 S.push(p); 8 p = p->left; 9 }else{ 10 p = S.top(); 11 S.pop(); 12 p = p->right; 13 } 14 } 15 }
3. 后续遍历
- 递归算法
后续遍历的顺序是leftTree,rightTree和root,因此递归算法也自然出来了
1 void postOrderTraverse1(Bitree *root){ 2 if(root){ 3 postOrderTraverse1(root->left); 4 postOrderTraverse1(root->right); 5 visit(root); 6 } 7 }
- 非递归算法
和之前中序和前序算法不同,后续遍历的root节点要最后才能被访问,因此,我们若想访问某节点,那么我们需要知道该节点的右节点是否已经被访问过。只有该节点的右节点为null,或者已被访问过,那么该节点才能被访问;否则需要先将右节点访问完。为了判断该节点的右节点是否已经被访问过,需另外设一个记录指针last来指示已经访问过的节点,如果之前访问过的节点last恰为该节点的右节点,说明其右子树已经访问完,应该访问该节点。
1 void postOrderTraverse2(Bitree *root){ 2 Bitree *last = nullptr; 3 Bitree *p = root; 4 stack<Bitree *> S; 5 while(p || !S.empty()){ 6 while(p){ 7 S.push(p); 8 p = p->left; 9 } 10 p = S.top(); 11 if(p->right && p->right != last){ 12 p = p->right; 13 }else{ 14 visit(p); 15 S.pop(); 16 last = p; 17 p = nullptr;//p needs to be updated to null for next loop 18 } 19 } 20 }
tip 1:后续遍历中,root节点最后才能被访问到,因此,栈能记录每一个节点的路径,包括叶子节点。这一点性质可用于求解和树的路径有关的问题。
4. 层序遍历
层序遍历的思想和之前三种遍历方式不同,需要借助queue来对节点进行缓存,先进队列的节点需要先离开。这和图的BFS思想一样,毕竟树本质上也是一种特殊的图。
1 void levelOrderTraverse(Bitree *root){ 2 if(!root){ 3 return; 4 } 5 queue<Bitree *> Q; 6 Bitree *p = nullptr; 7 Q.push(root); 8 while(!Q.empty()){ 9 p = Q.front(); 10 Q.pop(); 11 visit(p); 12 if(p->left){ 13 Q.push(p->left); 14 } 15 if(p->right){ 16 Q.push(p->right); 17 } 18 } 19 }
Tip 2: 层序遍历思想简单,利用queue来一层一层输出。因此,可用于求解数的宽度和高度。
二叉树遍历算法