二叉树的三种递归遍历与非递归遍历-C++实现

     二叉树是一种很重要的数据结构,所以了解并掌握它的一些特点尤其重要。本文介绍二叉树的一些特点,并用C++实现其创建与几种遍历方式。

    二叉树的特点:

  1. 在二叉树的第i层至多有2^(i-1)个结点(i >= 1)
  2. k层二叉树至多有2^k - 1个结点
  3. 如果二叉树度为2的节点为n,则其叶子结点数为n+1
  4. 具有n个节点的完全二叉树的深度为[log 2 n] + 1([x]表示不超过x的最大整数)

二叉树也有顺序存储结构和链式存储结构,但是习惯上用链式存储来表示二叉树,也就是常说的二叉链表。
二叉链表的节点定义:
template<class T>
struct BTreeNode
{
	T data;
	struct BTreeNode<T> *lchild, *rchild;
};

二叉树的基本操作(BTree.h):
/*
二叉树(二叉链表)的C++实现
phlixce 2017.8.26
*/

#ifndef BTREE_H
#define BTREE_H

#include <iostream>
#include <stack>
#include <queue>

using namespace std;

//二叉树的节点,包括一个data,左孩子,右孩子
//左右孩子采用递归定义的方式
template<class T>
struct BTreeNode
{
	T data;
	struct BTreeNode<T> *lchild, *rchild;
};

//二叉链表
template<class T>
class BTree
{
public:
	BTree();                                                  //二叉树构造函数,调用私有成员CreateBTree()实现
	BTreeNode<T>* GetRoot();				  //返回二叉树的根节点,根节点指向一个二叉链表,所以其返回值就是由多个二叉树节点组成的链表。

	void PreOrder();									 //二叉树前序遍历,使用非递归方式实现(借助于栈)
	void InOrder();                                     //二叉树中序遍历,使用非递归方式实现(借助于栈)
	void PostOrder();                                 //二叉树后序遍历,使用非递归方式实现(借助于栈)
	void LevelOrder();                               //二叉树前序遍历,使用非递归方式实现(借助于队列)

	//递归与非递归使用函数重载实现(输入参数不同)
	void PreOrder(BTreeNode<T>* node);      //二叉树前序遍历,递归实现,所以需要传入根节点。
	void InOrder(BTreeNode<T>* node);        //二叉树中序遍历,递归实现,所以需要传入根节点。
	void PostOrder(BTreeNode<T>* node);    //二叉树后序遍历,递归实现,所以需要传入根节点。
	

	int BTreeSize(BTreeNode<T>* node);       //返回二叉树的节点个数,递归实现
	int BTreeLeaves(BTreeNode<T>* node);  //返回二叉树的叶子节点个数,递归实现

private:
	BTreeNode<T>* root;                               //二叉树根节点,节点指向一个二叉链表
	BTreeNode<T>* CreateBTree();               //二叉树的创建,由用户输入实现
};


template<class T>
BTree<T>::BTree()
{
	cout << "请按前序遍历的方式输入二叉树,叶子节点用#代替!" << endl;
	root = new BTreeNode<T>;
	root = CreateBTree();
}

template<class T>
BTreeNode<T>* BTree<T>::GetRoot()
{
	return root;
}

//注意该创建思路是根据前序遍历的思路创建的,所以输入的时候要按前序遍历的顺序输入,并且每个叶子后面都要跟两个#。这样才能输入结束。
template<class T>
BTreeNode<T>* BTree<T>::CreateBTree()
{
	T input;
	cin >> input;
	BTreeNode<T>* node;
	if(input == '#')
	{
		node = NULL;   //输入为‘#’,就使当前节点为空
	}
	else
	{
		node = new BTreeNode<T>;
		node->data = input;    //赋值给当前节点
		node->lchild = CreateBTree(); //创建左孩子
		node->rchild = CreateBTree(); //创建右孩子
	}
	return node;  //将二叉链表node返回给root
}


template<class T>
void BTree<T>::PreOrder(BTreeNode<T>* node)
{
	if(NULL == node)
	{
		//cout << "二叉树是一棵空树" << endl;
	}
	else
	{
		cout << node->data << " ";  //前序,先输出根节点
		PreOrder(node->lchild);        //输出左孩子
		PreOrder(node->rchild);       //输出右孩子
	}
	//cout << endl;
}

template<class T>
void BTree<T>::InOrder(BTreeNode<T>* node)
{
	if(NULL == node)
	{
		//cout << "二叉树是一棵空树" << endl;
	}
	else
	{
		InOrder(node->lchild);           //先找到左孩子输出
		cout << node->data << " ";  //输出根节点
		InOrder(node->rchild);          //最后输出右孩子
	}
	//cout << endl;
}

template <class T>
void BTree<T>::PostOrder(BTreeNode<T>* node)
{
	if(NULL == node)
	{
		//cout << "二叉树是一棵空树" << endl;
	}
	else
	{
		PostOrder(node->lchild);      //先输出左孩子
		PostOrder(node->rchild);      //再输出右孩子
		cout << node->data << " "; //最后输出根节点
	}
	//cout << endl;
}

/*
层序遍历:以输入1 2 4 # # 5 # # 3 6 # # 7 # #为例,则其层序遍历为1 2 3 4 5 6 7
第一次while:s指向1,将1出队列,输出1,1的左右孩子都存在,所以推入左右孩子2和3,队列为3 2(2代表队头,3代表队尾);
第二次while:s指向2,将2出队列,输出2,2的左右孩子都存在,所以推入左右孩子4和5,队列为5 4 3;
第三次while:s指向3,将3出队列,输出3,3的左右孩子都存在,所以推入左右孩子6和7,队列为7 6 5 4;
第四次while:s指向4,将4出队列,输出4,4的左右孩子均不存在,所以剩下队列为7 6 5;
第五次while:s指向5,将5出队列,输出5,5的左右孩子均不存在,所以剩下队列为7 6;
第六次while:s指向6,将6出队列,输出6,6的左右孩子均不存在,所以剩下队列为7;
第七次while:s指向7,将7出队列,输出7,7的左右孩子均不存在,所以剩下队列为空;
下一次队列为空,循环结束。
输出结果 1 2 3 4 5 6 7
*/
template <class T>
void BTree<T>::LevelOrder()
{
	queue<BTreeNode<T>*> q;    //创建一个队列,队列中的每个元素都是一个二叉链表
	BTreeNode<T>* s = root;        //二叉链表s指向root
	if(NULL == s)
	{
		return; //如果原二叉树为空,返回
	}
	q.push(s); //将根节点推入队列
	while(!q.empty())
	{
		s = q.front();  //s指向队列的第一个元素,第一次即指向root
		q.pop();         //将队列第一个元素弹出,
		cout << s->data << " ";
		if(s->lchild)
		{
			q.push(s->lchild); //左孩子存在,推入左孩子
		}
		if(s->rchild)
		{
			q.push(s->rchild); //右孩子存在,推入右孩子
		}
	}
}

/*
前序遍历(非递归):以输入1 2 4 # # 5 # # 3 6 # # 7 # #为例,则其前序遍历为1 2 4 5 3 6 7。
进入while前:p指向root,栈为空。
第一次while:
while:p指向root,不为NULL,输出root的值1,将1压入栈中,p指向左孩子2;循环,输出2,压入2,指向4;输出4,压入4,指向4的左孩子NULL,while结束。
if:      由于栈不空,p指向4,出栈4,p指向4的右孩子NULL。
结果:  输出 1 2 4,栈中元素为1 2(1为栈低,2为栈顶)

第二次while:
while:p为空,不执行。
if      :p指向2,出栈2,p指向2的右孩子5。
结果 :无输出,栈中元素为1

第三次while:
while:p指向5,不空,输出5,将5压栈,p指向5的左孩子NULL,while结束。
if:p指向5,出栈5,p指向5的右孩子NULL。
结果:输出5,进栈出栈5,栈元素为1.

第四次while:
while:p指向NULL,不执行。
if:p指向1,出栈1,p指向1的右孩子3。
结果:无输出,栈元素为空。

第五次while:(p不为空)
while:p指向3,不空,输出3,压入3,指向3的左孩子6;输出6,压入6,指向6的左孩子NULL,结束。
if:p指向6,出栈6,p指向6的右孩子NULL。
结果:输出 3 6,栈元素为3。

第六次while:
while:p指向NULL,不执行。
if:p指向3,出栈3,p指向3的右孩子7。
结果:无输出,栈元素为空。

第七次while:
while:p不为空,输出7,压入7,p指向7的左孩子NULL。
if:p指向7,出栈7,p指向7的右孩子NULL。
结果:输出7,压栈出栈7,栈元素为空。

下一次p指向空,栈为空,循环结束。

输出结果 1 2 4 5 3 6 7
*/
template<class T>
void BTree<T>::PreOrder()
{
	stack<BTreeNode<T>*> s;
	BTreeNode<T>* p = root;
	if(NULL == p)
	{
		return;
	}
	while(p != NULL || !s.empty())
	{
		while(p != NULL)
		{
			cout << p->data <<" ";
			s.push(p);
			p = p->lchild;
		}
		if(!s.empty())
		{
			p = s.top();
			s.pop();
			p = p->rchild;
		}
	}
}

/*
中序遍历(非递归):以输入1 2 4 # # 5 # # 3 6 # # 7 # #为例,则中序遍历为4 2 5 1 6 3 7。
进入while前:p指向root,栈为空。
第一次while:
while:p指向root,不为NULL;将1压入栈中,p指向左孩子2;循环,压入2,指向4;压入4,指向4的左孩子NULL,while结束。
if:由于栈不空,p指向4,输出4,出栈4,p指向4的右孩子NULL。
结果:  输出4,栈中元素为1 2(1为栈低,2为栈顶)

第二次while:
while:p为空,不执行。
if      :p指向2,输出2,出栈2,p指向2的右孩子5。
结果 :输出2,栈中元素为1

第三次while:
while:p指向5,不空;将5压栈,p指向5的左孩子NULL,while结束。
if:p指向5,输出5,出栈5,p指向5的右孩子NULL。
结果:输出5,进栈出栈5,栈元素为1.

第四次while:
while:p指向NULL,不执行。
if:p指向1,输出1,出栈1,p指向1的右孩子3。
结果:输出1,栈元素为空。

第五次while:(p不为空)
while:p指向3,不空;压入3,指向3的左孩子6;输出6,压入6,指向6的左孩子NULL,结束。
if:p指向6,输出6,出栈6,p指向6的右孩子NULL。
结果:输出6,栈元素为3。

第六次while:
while:p指向NULL,不执行。
if:p指向3,输出3,出栈3,p指向3的右孩子7。
结果:输出3,栈元素为空。

第七次while:
while:p不为空,压入7,p指向7的左孩子NULL。
if:p指向7,输出7,出栈7,p指向7的右孩子NULL。
结果:输出7,压栈出栈7,栈元素为空。

下一次p指向空,栈为空,循环结束。

输出结果,4 2 5 1 6 3 7
*/
template <class T>
void BTree<T>::InOrder()
{
	stack<BTreeNode<T>*> s;   //创建一个元素为二叉链表的栈
	BTreeNode<T>* p = root;
	if(NULL == p)
	{
		return;  //如果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 2 4 # # 5 # # 3 6 # # 7 # #为例,则后层序遍历为4 5 2 6 7 3 1。
进入while前:p指向root,栈中含有一个元素root,pre=NULL,cur未初始化。
第一次while:
cur指向1,cur左右孩子均不为空,pre为空,所以if语句不执行;执行else压入右孩子3和左孩子2。
结果:无输出,栈中元素为1 3 2(3为栈底,2为栈顶)

第二次while:
cur指向2,2左右孩子不为空,pre=NULL,if语句不执行;执行else压入右孩子5与左孩子4.
结果:无输出,栈中元素为1 3 2 5 4

第三次while:
cur指向4,cur左右孩子均为空,执行if,输出4,出栈4,pre指向4。
结果:输出4,栈中元素为1 3 2 5

第四次while:
cur指向5,cur左右孩子均为空,执行if,输出5,出栈5,pre指向5。
结果:输出5,栈中元素为1 3 2

第五次while:(p不为空)
cur指向2,2左右孩子不为空,pre不为空,pre是cur的右孩子,执行if,输出2,出栈2,pre指向2。
结果:输出2,栈中元素为1 3。

第六次while:
cur指向3,3的左右孩子均不为空,pre虽不为空,但是pre(2)既不是3的左孩子也不是右孩子,if不执行,执行else,压入3的右孩子7与左孩子6。
结果:无输出,栈中元素为1 3 7 6。

第七次while:
cur指向6,执行if,输出6,出栈6,pre指向6。
结果:输出6,栈中元素为1 3 7。

第八次while:
cur指向7,执行if,输出7,出栈7,pre指向7。
结果:输出7,栈中元素为1 3。

第九次while:
cur指向3,pre不为空,pre是cur的右孩子,执行if,输出3,出栈3,pre指向3。
结果:输出3,栈中元素为1。

第十次while:
cur指向1,pre不空,pre是cur的右孩子,指向if,输出1,出栈1,pre指向1。
结果:输出1,栈中元素为空。

下一次栈为空,循环结束。

输出结果,4 5 2 6 7 3 1
*/
template <class T>
void BTree<T>::PostOrder()
{
	stack<BTreeNode<T>*> s;
	BTreeNode<T>* p = root;
	if(NULL == p)
	{
		return;
	}
	s.push(p);
	BTreeNode<T>* pre = NULL; //上一个访问的节点
	BTreeNode<T>* cur; //当前节点
	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)
			{
				s.push(cur->rchild);
			}
			if(cur->lchild)
			{
				s.push(cur->lchild);
			}
		}
	}
}

//二叉树节点个数
template<class T>
int BTree<T>::BTreeSize(BTreeNode<T>* node)
{
	BTreeNode<T>* p = node;
	if(NULL == p)
	{
		return 0; //空树,返回0。
	}
	else
	{
		return 1+BTreeSize(p->lchild) + BTreeSize(p->rchild); //非空,返回左子树节点数+右子树结点数+根节点数1
	}	
}

//二叉树叶子节点个数
template <class T>
int BTree<T>::BTreeLeaves(BTreeNode<T>* node)
{
	BTreeNode<T>* p = node;
	if(NULL == p)
	{
		return 0; //空树返回0
	}
	else if(p->lchild == NULL && p->rchild == NULL)
	{
		return 1; //节点的左右孩子均为NULL,则加1
	}
	else
	{
		return BTreeLeaves(p->lchild) + BTreeLeaves(p->rchild); //返回左子树的叶子节点数+右子树的叶子节点
	}
}

#endif

测试程序(BTreeTest.cpp):
#include "BTree.h"
#include <iostream>

using namespace std;
int main()
{
	BTree<char> my_btree;
	cout << "--------二叉树前序遍历(递归)--------" << endl;
	my_btree.PreOrder(my_btree.GetRoot());
	cout << endl << endl;
	cout << "--------二叉树中序遍历(递归)--------" << endl;
	my_btree.InOrder(my_btree.GetRoot());
	cout << endl << endl;
	cout << "--------二叉树后序遍历(递归)--------" << endl;
	my_btree.PostOrder(my_btree.GetRoot());
	cout << endl << endl;
	cout << "--------二叉树层序遍历(非递归)--------" << endl;
	my_btree.LevelOrder();
	cout << endl << endl;
	cout << "--------二叉树前序遍历(非递归)--------" << endl;
	my_btree.PreOrder();
	cout << endl << endl;
	cout << "--------二叉树中序遍历(非递归)--------" << endl;
	my_btree.InOrder();
	cout << endl << endl;
	cout << "--------二叉树后序遍历(非递归)--------" << endl;
	my_btree.PostOrder();
	cout << endl << endl;
	cout << "--------二叉树节点数--------" << endl;
	int size = my_btree.BTreeSize(my_btree.GetRoot());
	cout << size << endl << endl;
	cout << "--------二叉树叶节点数--------" << endl;
	int leaves = my_btree.BTreeLeaves(my_btree.GetRoot());
	cout << leaves << endl << endl;
	system("pause");
	return 0;
}

最后结果:


遍历方式的递归过程比较简单,但是非递归有点麻烦,所以写了一些注解,水平有限,如有错误,望见谅。在编写过程中本想写一个BTree.cpp实现类中的函数,而不是放在同一个头文件中,可是VS总是报error Link2019 无法解析的外部符号,不知各位有什么解决办法。
参考链接:
http://blog.csdn.net/iqrocket/article/details/8266365
http://blog.csdn.net/pi9nc/article/details/13008511/

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值