二叉树的遍历分为两类,一类是深度优先遍历,一类是广度优先遍历。
左孩子结点一定要在右孩子结点之前访问。
深度优先遍历
二叉树的深度优先遍历方式有三种,先根(序)遍历、中根(序)遍历、后根(序)遍历。
因为树的定义本身就是递归定义,因此采用递归的方法去实现树的三种遍历不仅容易理解而且代码很简洁。而对于树的遍历若采用非递归的方法,就要采用栈去模拟实现。在三种遍历中,前序和中序遍历的非递归算法都很容易实现,非递归后序遍历实现起来相对来说要难一点。下面一一讲解具体的递归和非递归实现。
1、先根遍历
-
递归实现
//先根递归遍历 void preOrderRecursion(BinaryTreeNode* root) { if(root==NULL) return; cout<< " " << root->m_key; // visit preOrderRecursion(root->m_pLeft); preOrderRecursion(root->m_pRight); }
-
非递归实现
根据先序遍历的顺序,首先访问根结点,然后再分别访问左孩子和右孩子。即对于任一结点,其可看做是根结点,因此可以直接访问,访问完之后,若其左孩子不为空,按相同规则访问它的左子树;当访问完左子树时,再访问它的右子树。因此其处理过程如下:
给定二叉树的根节点 R:
-
首先将根节点 R 入栈;
-
判断栈是否为空,若不为空,取栈顶元素 cur 访问并出栈。然后先将 cur 的右子节点入栈,再将 cur 的左子节点入栈;
-
重复(b)直到栈为空,则遍历结束。
// 先序非递归遍历,需要使用栈 void preOrderStack(BinaryTreeNode* root) { if(root==NULL) return; stack<BinaryTreeNode*> stack; stack.push(root); BinaryTreeNode* cur=NULL; while(!stack.empty()) { cur=stack.top(); cout<<" "<<cur->m_key; //visit stack.pop(); if(cur->m_pRight!=NULL) { stack.push(cur->m_pRight); // 先右再左 } if(cur->m_pLeft!=NULL) { stack.push(cur->m_pLeft); } } }
下面给出个中根遍历相同框架的非递归实现:
void preOrderStack(BinaryTreeNode* root) { if(root==NULL) return; stack<BinaryTreeNode*> stack; BinaryTreeNode* cur=root; while(!stack.empty() || cur!=NULL) { while(cur) { cout<<" "<<cur->m_key; //visit stack.push(cur); cur=cur->m_pLeft; } cur=stack.top(); stack.pop(); cur=cur->m_pRight; } }
-
2、中根遍历
-
递归实现
//中序递归遍历 void midOrderRecursion(BinaryTreeNode* root){ if(root==NULL) return; midOrderRecursion(root->m_pLeft); cout<<" "<<root->m_key; //visit midOrderRecursion(root->m_pRight); }
-
非递归实现
根据中序遍历的顺序,对于任一结点,先访问其左孩子,而左孩子结点又可以看做一根结点,然后继续访问其左孩子结点,直到遇到左孩子结点为空的结点才进行访问,然后按相同的规则访问其右子树。因此其处理过程如下:
对于给定的二叉树根节点 R,
- 若其左孩子不为空,循环将R以及R左子树中的所有节点的左孩子入栈;
- 取栈顶元素 cur,访问 cur 并将 cur 出栈。然后对 cur 的右子节点进行步骤(a)那样的处理;
- 重复(a)和(b)的操作,直到 cur 为空切栈为空。
// 中根非递归遍历,需要使用栈 void midOrderStack(BinaryTreeNode* root) { if(root==NULL) return; stack<BinaryTreeNode*> stack; BinaryTreeNode* cur=root; while(!stack.empty() || cur!=NULL) { while(cur) { stack.push(cur); cur=cur->m_pLeft; } cur=stack.top(); cout<<" "<<cur->m_key; //visit stack.pop(); cur=cur->m_pRight; } }
3、后根遍历
-
递归实现
// 后根递归遍历 void postOrderRecursion(BinaryTreeNode* root) { if(root==NULL) return; postOrderRecursion(root->m_pLeft); postOrderRecursion(root->m_pRight); cout << " " << root->m_key; }
-
非递归实现
要保证根结点在左孩子和右孩子访问之后才能访问,因此对于任一结点R,
- 先将R入栈。如果P不存在左孩子和右孩子,则可以直接访问它并出栈;
- 如果R存在左孩子或者右孩子,但是其左孩子和右孩子都已被访问过了,则同样可以直接访问该结点并出栈;
- 若非上述两种情况,则将R的右孩子和左孩子依次入栈,这样就保证了每次取栈顶元素的时候,左孩子在右孩子前面被访问,左孩子和右孩子都在父结点前面被访问。
/ 非递归后序遍历 void postOrderStack2(BinaryTreeNode* root) { if(root==NULL) return; stack<BinaryTreeNode*> s; BinaryTreeNode* cur; //当前结点 BinaryTreeNode* pre=NULL; //前一次访问的结点 s.push(root); while(!s.empty()) { cur=s.top(); //在判断当前结点时,左孩子和右孩子都在根结点前已经被访问 if((cur->m_pLeft==NULL&&cur->m_pRight==NULL) || (pre!=NULL&&(pre==cur->m_pLeft || pre==cur->m_pRight))) { cout<<cur->m_key<<" "; //如果当前结点没有孩子结点或者孩子节点都已被访问过 s.pop(); pre=cur; } else { if(cur->m_pRight!=NULL) s.push(cur->m_pRight); if(cur->m_pLeft!=NULL) s.push(cur->m_pLeft); } } }
广度优先遍历(层序遍历)
广度优先遍历,也就是层序遍历,按照层次从上到下,每层从左到右地访问,可以使用队列来实现。
void breadthFirstOrder(BinaryTreeNode* root)
{
if(root==NULL) return;
queue<BinaryTreeNode*> queue;
queue.push(root); // 首先根节点入栈
while(!queue.empty())
{
BinaryTreeNode* cur=queue.front();
queue.pop();
cout<<" "<<cur->m_key; //visit
if(cur->m_pLeft!=NULL) queue.push(cur->m_pLeft);
if(cur->m_pRight!=NULL) queue.push(cur->m_pRight);
}
}