二叉树----第一篇(构造,遍历,求节点,求叶子)

一:二叉树结构

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

using namespace std;

struct BiData//节点结构
{
	char data;
	BiData *lChild, *rChild;
};

class BiTree
{
public:
	BiData *root;//根节点
	int leafNum = 0;//叶子数,如果此处报错,请将0去掉,并将leafNum的初始化移到构造函数中,此程序在VisualStudio2015编译运行通过

	BiTree();//构造函数,构造一个二叉树

	BiData * Create();

	void PreOrder(BiData * _root);//前序遍历递归
	void PreOrderNonRec(BiData * _root);//前序遍历非递归

	void InOrder(BiData * _root);//中序遍历递归
	void InOrderNonRec(BiData * _root);// 中序遍历非递归

	void PostOrder(BiData * _root);//后序遍历递归
	void PostOrderNonRec(BiData * _root);//后序遍历非递归

	void LevelOrder(BiData * _root);//层次遍历

	int Count(BiData * _root);//计算节点

	int CountLeaf(BiData * _root);//计算叶子的个数(叶子就是度为0的节点)

};


int main()
{
	BiTree myTree;
	//1.
	cout << "前序遍历递归与非递归\n";
	myTree.PreOrder(myTree.root); cout << endl;
	myTree.PreOrderNonRec(myTree.root); cout << endl<<endl;
	//2.
	cout << "中序遍历递归与非递归\n";
	myTree.InOrder(myTree.root); cout << endl;
	myTree.InOrderNonRec(myTree.root); cout << endl<<endl;
	//3.
	cout << "后序遍历递归与非递归\n";
	myTree.PostOrder(myTree.root); cout << endl;
	myTree.PostOrderNonRec(myTree.root); cout << endl << endl;;

	//4.
	cout << "层次遍历\n";
	myTree.LevelOrder(myTree.root); cout << endl << endl;;

	//5.
	cout << "该二叉树的节点有";
	cout << myTree.Count(myTree.root);
	cout << "个\n\n";

	//6.
	cout << "该二叉树的叶子有";
	cout << myTree.CountLeaf(myTree.root);
	cout << "个\n\n";

	return 0;
}

上面的代码实现如下图:




上图myTree变量二叉树如下图




二:具体实现

1.二叉树的构造

        对一个二叉树的构造,我使用递归的方法。二叉树的构造和遍历是比较考验对递归的理解的。如果你对递归的理解不到位,那么下面的二叉树三种非递归可能理解会吃力。

BiData * BiTree::Create()
{
	char x;
	cin >> x;
	BiData *p;

	if (x == '0')//输入0表示下一个结点结束
		p = nullptr;
	else
	{
		p = new BiData;
		p->data = x;
		//分别构造左子树和右子树
		p->lChild = Create();
		p->rChild = Create();
	}
	return p;
}

BiTree::BiTree()//构造二叉树
{
	root = Create();
}

2.二叉树遍历-----前序遍历递归与非递归

      递归版本的不用多讲了,就是按照“根节点---左孩子----右孩子”的顺序进行遍历。

     主要是非递归, 对于任一结点P:
     1)访问结点P,并将结点P入栈;
     2)判断结点P的左孩子是否为空,若为空,则取栈顶结点并进行出栈操作,并将栈顶结点的右孩子置为当前的结点P,循环至1);若不为空,则将P的左孩子置为当前的结点P;
     3)直到P为nullptr并且栈为空,则遍历结束。

     模拟递归,借助容器stack来实现。优先访问根节点,然后接着左子树和右子树。其实对于每一个节点都可以看作根节点。对于上图的myTree,首先访问和输出根节点(也就是1),然后,左子树(也就是2---4---6----7),接着右子树(也就是3----5)。当访问和输出1后,接着访问和输出左孩子2,因为每一个节点都可以看作根节点,因此此时的2变为一个根节点,接着访问和输出2的左孩子(如果2有左孩子的话,如图来看,2是有左孩子的),依次访问到4,此时没有左孩子了,那就要看右孩子6了,访问6的左孩子,发现没有,接着6的右孩子7,此时7已经是结尾了,既没有左孩子也没有右孩子,至此2的左子树(就是4---6--7)已经遍历完毕了,要开始遍历2的右子树,发现2是没有右子树的,那就结束,自此,1的左子树也遍历完毕。好,接下来就是1的右子树了,依次推,3---5,结束。

void BiTree::PreOrder(BiData * _root)//前序遍历递归
{
	if (_root == nullptr)
		return;
	else
	{
		cout << _root->data << " ";
		PreOrder(_root->lChild);
		PreOrder(_root->rChild);
	}
}


void BiTree::PreOrderNonRec(BiData * _root)//前序遍历非递归
{
	stack<BiData*> s;//模拟一个栈
	while (_root != nullptr || !s.empty())
	{
		while (_root != nullptr)
		{
			cout << _root->data << " ";
			s.push(_root);
			_root = _root->lChild;
		}
		if (!s.empty())
		{
			_root = s.top()->rChild;
			s.pop();
		}
	}
}

3.二叉树遍历----中序遍历递归与非递归

        非递归,对于任一结点P,
       1)若其左孩子不为空,则将P入栈并将P的左孩子置为当前的P,然后对当前结点P再进行相同的处理;
       2)若其左孩子为空,则取栈顶元素并进行出栈操作,访问该栈顶结点,然后将当前的P置为栈顶结点的右孩子;
       3)直到P为nullptr并且栈为空则遍历结束。

       中序非递归版本其实就是需要遍历完一个节点的左子树,然后输出根节点,接着右子树。对于myTree,先遍历1的左子树,访问到4时,4没有左子树,输出4,再看其右孩子6.再开始遍历6的左子树,6没有左子树,那就遍历右子树7,至此,2的左子树遍历完成,开始2的右子树,发现为空,此时1的左子树遍历完成,输出1,再开始1的右子树。          其实只要记住先左遍历到底,再根,然后右,其中对于每一个节点,都是有左和右的。这点要记住,就像遍历到4时,4没有左,但是有右,对于4的右孩子6,我们还要遍历6的左孩子,是需要一直访问到底的,但是因为6没有左子树,故而直接转向右子树7了,对于7,我们依旧要遍历7的左子树一直遍历到底,因为其没有左子树,因此看右子树,7没有右子树,结束。

        其实就这么简单。

void BiTree::InOrder(BiData * _root)//中序遍历递归
{
	if (_root == nullptr)
		return;
	else
	{
		InOrder(_root->lChild);
		cout << _root->data << " ";
		InOrder(_root->rChild);
	}
}


void BiTree::InOrderNonRec(BiData * _root)//中序遍历非递归
{
	stack<BiData*> s;
	while (_root != nullptr || !s.empty())
	{
		while (_root != nullptr)
		{
			s.push(_root);
			_root = _root->lChild;
		}
		if (!s.empty())
		{
			cout << s.top()->data << " ";
			_root = s.top()->rChild;
			s.pop();
		}
	}
}

4.二叉树遍历-----后序遍历递归与非递归

        非递归,对于每一个节点,如果我们想要输出它,只有它既没有左孩子也没有右孩子或者它有孩子但是它的孩子(左孩子和右孩子,如果它有的话)已经被输出了,那就可以输出它了。若非上述两种情况,则将P的右孩子和左孩子依次入栈,这样就保证了每次取栈顶元素的时候,左孩子和右孩子都在根结点前面被访问。也就是先依次遍历左子树和右子树,然后输出根节点。

         依旧对于上图中的myTree,因为对于一个节点,如果我们想要输出它,只有它既没有左孩子也没有右孩子或者它有孩子但是它的孩子(左孩子和右孩子,如果它有的话)已经被输出了。那么对于1的左子树来说,一直入栈,直到7,发现没有左子树和右子树了,输出7,此时对于6来说,6没有左子树,而它的右子树也输出完成,因此输出6,再到4,4没有左子树,右子树输出完成,所以输出4,至此,2的左子树输出完成,而2没有右子树,所以输出2,至此,1的左子树完成,那就要开始1的右子树了,同理,先操作完一个节点的左子树和右子树才可以输出它自己,因此直到5,输出5,然后3,此时1的右子树完成,终于可以输出1了。

void BiTree::PostOrder(BiData * _root)//后序遍历递归
{
	if (_root == nullptr)
		return;
	else
	{
		PostOrder(_root->lChild);
		PostOrder(_root->rChild);
		cout << _root->data << " ";
	}
}

void BiTree::PostOrderNonRec(BiData * _root)//后序遍历非递归
{
	if (_root == nullptr)
		return;

	stack<BiData*> s;//栈
	BiData * pre = nullptr;//上一次访问的节点
	BiData * cur;//现在访问的节点

	s.push(_root);

	while (!s.empty())
	{
		cur = s.top();

		if ((cur->lChild == nullptr&&cur->rChild == nullptr) ||
			(pre != nullptr) && (pre == cur->lChild || pre == cur->rChild))
		{
			cout << cur->data << " ";
			pre = cur;
			s.pop();
		}
		else
		{
			if (cur->rChild)
				s.push(cur->rChild);
			if (cur->lChild)
				s.push(cur->lChild);
		}
	}
}

5.二叉树遍历-----层次遍历

       层次遍历就是对于每一层(例如上面的myTree来说,1是第一层;2,3是第二层,4,5是第三层,依次下去),从左到右依次输出,这里需要用到队列,而不是栈,注意队列和栈的区别。对于根节点,先进队列,然后输出该节点,接着将根节点出队列,然后将节点的左右孩子(如果它有左右孩子的话)依次入队列,然后然后只要队列不为空,就取出第一个元素,输出该节点,并将该节点的左右孩子再依次入队列,依次下去。

void BiTree::LevelOrder(BiData * _root)//层次遍历
{
	if (_root == nullptr)
		return;

	queue<BiData*> q;//队列
	q.push(_root);
	BiData * temp = _root;

	while (!q.empty())
	{
		temp = q.front();
		q.pop();
		cout << temp->data << " ";

		if (temp->lChild)
			q.push(temp->lChild);
		if (temp->rChild)
			q.push(temp->rChild);
	}
}

6.计算节点数

int BiTree::Count(BiData * _root)//求节点数
{
	if (_root == nullptr)
		return 0;
	else
		return Count(_root->lChild) + Count(_root->rChild) + 1;
}

7.计算叶子数

        这里在类里定义了一个leafNum用来记录叶子数

int BiTree::CountLeaf(BiData * _root)//求叶子数
{
	if (_root == NULL)
		;
	else
	{
		if (!_root->lChild && !_root->rChild)
			leafNum++;
		else
		{
			CountLeaf(_root->lChild);
			CountLeaf(_root->rChild);
		}
	}
	return leafNum;
}



三:总结

       二叉树的实现主要是对递归的理解,仔细观察上面的实现,发现它们的实现都用到了递归。可是呢,大多数同学对递归的理解也只是模模糊糊的。对于递归的理解,网上的博客也是有很多,个人觉得,画图是最好的学习与认知方法,画出递归的具体运行情形,这样再遇到递归时能够在脑海构造出递归。

        这里要做出一些补充,关于二叉树的三种非递归遍历,如果大家仔细的话应该能发现这三种实现方式只有前序,中序的实现大致相似,后序相比前两个较为复杂。对于这方面,我也在百度上找到了统一的写法,链接是  更简单的非递归遍历二叉树的方法

         

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值