二叉树的遍历的重要性毋庸置疑,是很多算法的基础。遍历二叉树可以分为深度优先遍历和广度优先遍历,其中,深度优先遍历又可以分为先序、中序、后序。
提供一个二叉树,可能写出其几种遍历方式的序列并不难,但要完全正确的用代码实现(当然不是用递归啦)可能并不是特别容易。特别是像后序遍历,能写出精简、正确的代码还是要好好思考一下的。
下面就对二叉树的几种遍历方式做以分析。(代码大部分都是之前写的,学习数据结构时)
1. 先提供自己写的二叉树的模板
//二叉树的结点类
template<class T>
class Node
{
private:
//结点值
T data;
//左、右子树
Node<T>* left;
Node<T>* right;
public:
//构造函数,子树为空
Node(T data)
{
this->data=data;
left=NULL;
right=NULL;
}
//构造函数(指定左右子树)
Node(T data,Node<T>* left,Node<T>* right)
{
this->data=data;
this->left=left;
this->right=right;
}
//得到结点值
T getData()
{
return this->data;
}
//得到左子树
Node<T>* getLeft()
{
return this->left;
}
//得到右子树
Node<T>* getRight()
{
return this->right;
}
//设置左子树
void setLeft(Node<T>* left)
{
this->left=left;
}
//设置右子树
void setRight(Node<T>* right)
{
this->right=right;
}
};
//二叉树类
template<class T>
class binaryTree
{
private:
//根节点
Node<T>* root;
public:
//构造函数
binaryTree(Node<T>* root)
{
this->root=root;
}
//得到根节点
Node<T>* getRoot()
{
return this->root;
}
};
//访问某一结点
template<class T>
void visit(Node<T>* node)
{
cout<<node->getData()<<" ";
}
2. 二叉树的广度优先遍历
广度优先遍历很好理解,其实就是一层一层的去遍历。一般实现时都是用队列。
//广度优先遍历
template<class T>
void breadthVisit(Node<T>* root)
{
queue<Node<T>*> qu;
if(NULL==root)
{
cout<<"二叉树为空!"<<endl;
return;
}
//根节点压入队列
qu.push(root);
while(!qu.empty())
{
Node<T>* node=qu.front();
//访问一个结点,并把其非空的左右子结点压入队列
visit(node);
qu.pop();
if(node->getLeft()!=NULL)
{
qu.push(node->getLeft());
}
if(node->getRight()!=NULL)
{
qu.push(node->getRight());
}
}
}
附:有这样一个问题,经常会有笔试和面试时问。二叉树在广度遍历时,如何把层号输出。输出每个结点值时都带着各自的层号,第一层从1开始。
3. 二叉树的深度优先遍历(先序)
先序的顺序是指:先根 -> 再左 -> 再右
如果是非递归的实现,思路如下:
使用栈,先把根结点入栈。此后每次从栈中弹出一个结点,直接访问。然后,如果右结点不空,把右结点入栈。如果左结点不空,把左结点入栈。如此下去,直至栈空。
实现如下:(非递归)
//前序遍历(非递归)
//方法一:直接把两个子节点压入栈
template<class T>
void preOrderVisitNoRecursion(Node<T>* root)
{
//空树则返回
if(NULL==root){return;}
//遇到一个结点,访问。并把其非空的右子节点、左子节点压入栈
stack<Node<T>*>sta;
Node<T>* point=root;
//压入根结点
sta.push(point);
while(!sta.empty())
{
point=sta.top();
visit(point);
sta.pop();
//压入非空右子结点
if(point->getRight()!=NULL){sta.push(point->getRight());}
//压入非空左子节点
if(point->getLeft()!=NULL){sta.push(point->getLeft());}
}
}
当然,递归实现思路更为简单了。如下:
//前序遍历(递归)
template<class T>
void preOrderVisitRecursion(Node<T>* root)
{
//结点不为空
if(root!=NULL)
{
visit(root);
preOrderVisitRecursion(root->getLeft());
preOrderVisitRecursion(root->getRight());
}
}
4. 二叉树的深度优先遍历(中序)
思路也比较明晰:
对于结点P,一直往左走,并把经过的结点都压栈里面,直到最左边。想想,此时这个结点是不是应该第一个访问(因为它没有左子树了)。访问这个结点,然后,
判断它的右子树,如果不为空,则用同样的方式处理它的右子树。如果为空,那就从栈里面弹出来一个结点得了,栈为空就结束了。
代码实现可以有两种写法:
//中序遍历(非递归)
template<class T>
void mediumOrderVisitNoRecursion(Node<T>* root)
{
//遇到一非空结点,则把其压入栈中,之后遍历其左子树
//结点为空,则从栈中弹出一结点,并访问
//遍历该结点的右子树
stack<Node<T>*>sta;
Node<T>* point=root;
//空树则返回
if(root==NULL){return;}
while(!sta.empty() || point!=NULL)
{
if(point!=NULL)
{
sta.push(point);
point=point->getLeft();
}
else
{
point=sta.top();
visit(point);
sta.pop();
point=point->getRight();
}
}
}
也可以这样写:
//中序遍历(非递归)(另一种写法)
template<class T>
void mediumOrderVisitNoRecursion_1(Node<T>* root)
{
//遇到一非空结点,则把其压入栈中,往左继续走,直至为空(走到最左侧结点)
//从栈中弹出(若栈不空)一个结点访问
//遍历该结点的右子树
stack<Node<T>*>sta;
Node<T>* point=root;
//空树则返回
if(root==NULL){return;}
while(!sta.empty() || point)
{
while(point) //一直往右遍历,直到最左边的结点
{
sta.push(point);
point=point->getLeft();
}
point=NULL;
if(!sta.empty()) //栈不为空,取出栈顶值,访问。其实就是访问的最左边的结点
{
point=sta.top();
visit(point);
sta.pop();
}
point=point->getRight();
}
}
当然,递归实现中序遍历也很简单啦。
//中序遍历(递归)
template<class T>
void mediumOrderVisitRecursion(Node<T>* root)
{
if(root!=NULL)
{
mediumOrderVisitRecursion(root->getLeft());
visit(root);
mediumOrderVisitRecursion(root->getRight());
}
}
5. 在几种遍历方式中,后序遍历应该是最为复杂的一种。因为访问一个结点之前,必须保证它的两个子结点(如果有的话)都已经被访问过了。
一般较为直观的方式是:设置标志位,标志这个结点是否已经被访问。
代码实现如下:
//后续遍历(非递归)
//方法一:标志位
template<class T>
void afterOrderVisitNoRecursion(Node<T>* root)
{
//遇到一个节点,把其压入栈,进入左子树
//左子树进行不下去的时候,得到栈顶元素(并且弹出,不然栈中的节点标志位没有变化),进入其右子树
//右子树也遍历不下去的时候,就从栈中弹出一个节点访问
StackEle<T> ele;
stack<StackEle<T>> sta;
Node<T>* point=root;
//空树则返回
if(root==NULL){return;}
while(!sta.empty() || point)
{
//一直进入其左子树,直至无法遍历(为空)
while(point!=NULL)
{
//为ele赋值(即是为point加上一个标志位Left)
ele.node=point;
ele.tag=Left;
sta.push(ele);
point=point->getLeft();
}
//左子树周游结束后,从栈底得到一个元素
ele=sta.top();
sta.pop();
//若该节点还未周游右子树,则进入其右子树,并把其标志位设为Right,表示已经进入了右子树
if(Left==ele.tag)
{
ele.tag=Right;
sta.push(ele);
point=ele.node->getRight();
}
else //右子树已经周游过了
{
//弹出这个节点,并访问
visit(ele.node);
//设置point为NULL
point=NULL;
}
}
}