二叉树的遍历——递归和非递归
二 叉树是一种非常重要的数据结构,很多其它数据结构都是基于二叉树的基础演变而来的。对于二叉树,有前序、中序以及后序三种遍历方法。因为树的定义本身就是 递归定义,因此采用递归的方法去实现树的三种遍历不仅容易理解而且代码很简洁。而对于树的遍历若采用非递归的方法,就要采用栈去模拟实现。在三种遍历中, 前序和中序遍历的非递归算法都很容易实现,非递归后序遍历实现起来相对来说要难一点。
一.前序遍历
前序遍历按照“根结点-左孩子-右孩子”的顺序进行访问。
1.递归实现
- void pre_order(BTree *root)
- {
- if(root != NULL)//必不可少的条件,递归的出口
- {
- printf("%2c",root->key); //访问根结点
- pre_order(root->lchild); //前序遍历左子树
- pre_order(root->rchild); //前序遍历右子树
- }
- }
2.非递归实现
根据前序遍历访问的顺序,优先访问根结点,然后再分别访问左孩子和右孩子。即对于任一结点,其可看做是根结点,因此可以直接访问,访问完之后,若其左孩子不为空,按相同规则访问它的左子树;当访问其左子树时,再访问它的右子树。因此其处理过程如下:
对于任一结点P:
1)访问结点P,并将结点P入栈;
2)判断结点P的左孩子是否为空,若为空,则取栈顶结点并进行出栈操作,并将栈顶结点的右孩子置为当前的结点P,循环至1);若不为空,则将P的左孩子置为当前的结点P;
3)直到P为NULL并且栈为空,则遍历结束。
二.中序遍历
中序遍历按照“左孩子-根结点-右孩子”的顺序进行访问。
1.递归实现
- void in_order(BTree* root)
- {
- //必不可少的条件,递归的出口
- if(root != NULL)
- {
- in_order(root->lchild);
- printf("%2c",root->data);
- in_order(root->rchild);
- }
- }
2.非递归实现
根据中序遍历的顺序,对于任一结点,优先访问其左孩子,而左孩子结点又可以看做一根结点,然后继续访问其左孩子结点,直到遇到左孩子结点为空的结点才进行访问,然后按相同的规则访问其右子树。因此其处理过程如下:
对于任一结点P,
1)若其左孩子不为空,则将P入栈并将P的左孩子置为当前的P,然后对当前结点P再进行相同的处理;
2)若其左孩子为空,则取栈顶元素并进行出栈操作,访问该栈顶结点,然后将当前的P置为栈顶结点的右孩子;
3)直到P为NULL并且栈为空则遍历结束
- //非递归中序遍历
- void in_order(BTree *root)
- {
- stack<BTree*> s;
- BTree *p = root;
- while (p != NULL || !s.empty()) {
- while(p != NULL) {
- s.push(p);
- p = p->lchild;
- }
- if (!s.empty()) {
- p = s.top();
- cout<<p->data<<" ";
- s.pop();
- p = p->rchild;
- }
- }
- }
三.后序遍历
后序遍历按照“左孩子-右孩子-根结点”的顺序进行访问。
1.递归实现
- void post_order(BTree* root)
- {
- //必不可少的条件,递归的出口
- if(root != NULL)
- {
- post_order(root->lchild);
- post_order(root->rchild);
- printf("%2c",root->data);
- }
- }
2.非递归实现
后序遍历的非递归实现是三种遍历方式中最难的一种。因为在后序遍历中,要保证左孩子和右孩子都已被访问并且左孩子在右孩子前访问才能访问根结点,这就为流程的控制带来了难题。下面介绍两种思路。
第一种思路:对于任一结点P,将其入栈,然后沿其左子树一直往下搜索,直到搜索到没有左孩子的结点,此时该结点出现在栈顶,但是此时不能将其出栈并访问, 因此其右孩子还未被访问。所以接下来按照相同的规则对其右子树进行相同的处理,当访问完其右孩子时,该结点又出现在栈顶,此时可以将其出栈并访问。这样就 保证了正确的访问顺序。可以看出,在这个过程中,每个结点都两次出现在栈顶,只有在第二次出现在栈顶时,才能访问它。因此需要多设置一个变量标识该结点是 否是第一次出现在栈顶。
方 法:要保证根结点在左孩子和右孩子访问之后才能访问,因此对于任一结点P,先将其入栈。如果P不存在左孩子和右孩子,则可以直接访问它;或者P存在左孩子 或者右孩子,但是其左孩子和右孩子都已被访问过了,则同样可以直接访问该结点。若非上述两种情况,则将P的右孩子和左孩子依次入栈,这样就保证了每次取栈 顶元素的时候,左孩子在右孩子前面被访问,左孩子和右孩子都在根结点前面被访问。
- //非递归后序遍历
- void post_order(BTree* root)
- {
- stack<BTree*> s;
- //当前结点
- BTree *cur = NULL;
- //前一次访问的结点
- BTree *pre = NULL;
- s.push(root);
- while(!s.empty()) {
- cur = s.top();
- if( (cur->lchild == NULL && cur->rchild == NULL) ||
- (pre != NULL && (pre == cur->lchild || pre == cur->rchild)))
- {
- //如果当前结点没有孩子结点或者孩子节点都已被访问过
- cout<<cur->data<<" ";
- s.pop();
- pre = cur;
- } else {
- if(cur->rchild != NULL)
- s.push(cur->rchild);
- if(cur->lchild!=NULL)
- s.push(cur->lchild);
- }
- }
- }
四、层次遍历
//采用STL中的queue处理
- #include <queue>
- void layerOrder(BTree *tree)
- {
- if (tree == NULL)
- return;
- queue<BTree *> q;
- q.push(tree);
- BTree *p = NULL;
- while (!q.empty())
- {
- p = q.front();
- visit(p);
- q.pop();
- if (p->lchild != NULL)
- q.push(p->lchild);
- if (p->rchild != NULL)
- q.push(p->rchild);
- }
- }
五.二叉树的其他一些应用
1.求二叉树的深度
若一棵二叉树为空,则它的深度为0,否则它的深度等于左子树和右子树中的最大深度加1. 设nLeft为左子树的深度,nRight为右子树的深度,
则二叉树的深度为:max(nLeft , nRight)+1.
//树的深度 int TreeDepth(BTree* root) { int nLeft, nRight; if(root == NULL)//必不可少的条件,递归的出口 return 0; nLeft = TreeDepth(root->lchild); nRight = TreeDepth(root->rchild); return (nLeft > nRight) ? (nLeft + 1):(nRight + 1); }
2.从二叉树中查找值为x的结点。若存在,则由x带回完整值并返回真,否则返回假
该算法类似于前序遍历,若树为空则返回false结束递归,若树根结点的值就等于x的值,则把结点值赋给x后返回true结束递归,否则先向左子树查找,若找到则返回true结束递归,否则再向右子树查找,若找到则返回true结束递归,若左,右子树均未找到则返回false结束递归。
struct BTreeNode { ElemType data; //结点值域 BTreeNode *left; //指向左孩子结点的值域 BTreeNode *right; //指向右孩子结点的值域 }
bool FindBTree(BTreeNode *BT , ElemType &x) { if(BT == NULL) //树为空返回假 return false; if(BT->data == x) //树根结点的值等于x则由x带回结点值并返回真 { x = BT->data; return true; } else { //向左子树查找,若成功则继续返回真 if(FindBTree(BT->left , x)) return true; //向右子树查找,若成功则继续返回真 if(FindBTree(BT->right , x)) return true; //左,右子树查找均失败则返回假 return false; } }
3.统计出二叉树中等于给定值x的结点个数,结果由函数返回。
此算法也是一个递归过程,若树为空则返回0结束递归,若树根结点的值等于x的值则返回左、右两棵子树中等于x结点的个数加1,否则只应返回左、右两棵子树中等于x结点的个数。
int CountX(BTreeNode *BT , ElemType &x) { if(BT == NULL) //空树返回0 return 0; if(BT->data == x) return CountX(BT->left , x)+CountX(BT->right , x) + 1; //返回1加上两子树中的x结点数 else return CountX(BT->left , x)+CountX(BT->right , x); //返回两子树中的x结点数 }
4.返回x结点所处的层号,若不存在值为x的结点则返回0.
int NodeLevel(BTreeNode *BT , ElemType &x) { //空树的层号为0 if(BT == NULL) return 0; //根结点的层号为1 if(BT->data == x) return 1; else { //求出x在左子树中的层号,返回该层号加1 int c1 = NodeLevel(BT->left , x); if(c1 >= 1) return c1+1; //求出x在右子树中的层号,返回该层号加1 int c2 = NodeLevel(BT->right , x); if(c2 >= 1) return c2+1; //在左、右子树中都不存在x结点则返回0 else return 0; } }
5.从二叉树中找出所有结点的最大值并返回,若为空树则返回0.
ElemType MaxValue(BTreeNode *BT) { if(BT == NULL) return 0; //空树返回0 ElemType k1 , k2; k1 = MaxValue(BT->left); //求出左子树中的最大值 k2 = MaxValue(BT->right); //求出右子树中的最大值 if(k1 < k2) k1 = k2; //两子树的最大值赋给k1 if(k1 > BT->data) return k1; else return BT->data; }
6.求二叉树中所有结点数。
int BTreeCount(BTreeNode *BT) { if(BT == NULL) return 0; else return BTreeCount(BT->left) + BTreeCount(BT->right) + 1; }
7.求二叉树中所有叶子结点数
int BTreeLeafCount(BTreeNode *BT) { if(BT == NULL) return 0; if(BT->left == NULL && BT->right == NULL) return 1; else return BTreeLeafCount(BT->left) + BTreeLeafCount(BT->right); }
微信公众号:
猿人谷
如果您认为阅读这篇博客让您有些收获,不妨点击一下右下角的【推荐】
如果您希望与我交流互动,欢迎关注微信公众号
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。