二叉树的遍历

二叉树的遍历的重要性毋庸置疑,是很多算法的基础。遍历二叉树可以分为深度优先遍历和广度优先遍历,其中,深度优先遍历又可以分为先序、中序、后序。

提供一个二叉树,可能写出其几种遍历方式的序列并不难,但要完全正确的用代码实现(当然不是用递归啦)可能并不是特别容易。特别是像后序遍历,能写出精简、正确的代码还是要好好思考一下的。

下面就对二叉树的几种遍历方式做以分析。(代码大部分都是之前写的,学习数据结构时)


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;
		}
	}
}


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值