数据结构杂谈(九)——二叉树的遍历

9 二叉树的遍历

9.1 递归函数基础


什么是递归?调用自身就是叫递归,如下所示:

void r(){
	r();
}

我们习惯借用阶梯图来帮助我们理解这些知识。如果是同一层函数体,那么习惯上在同一层阶梯中,如下所示:

image-20220523100809175

如果是像上述的方式一样来写递归函数,我们不难发现这个循环是无限的。故我们需要加一个终止条件,一般来说,我们习惯将终止条件写于函数体的最开头,当符合终止条件时,函数遍不会继续执行,如下所示:

int i = 0;

void r()
{
	if(i<3)
	{
		++i;
		r();
	}
}

当同一层阶梯中有多个执行语句,我们习惯用一个结点来表示它执行的效果,如下所示:

image-20220523101707334

多个递归

在考研中一般不考查一个递归,而是喜好考查多个递归,在这里给出代码示例和阶梯图,望仔细研究。

int i = 0;
void r()
{
	if(i<2)
	{
		++i;
		r();
		
		r();
	}
}

image-20220523102327880


9.2 深度优先遍历的实现


递归遍历

通过上面递归的学习,我想我们可以看穿下面这个恐怖的遍历代码了。

void r(BTNode  p)
{
	if(p != nullptr)
	{
		//(1)
		r(p->lChild);
		//(2)
		r(p->rChild);
		//(3)
	}
}

对于一下这颗树来说:

image-20220523103227283

结合上述代码的遍历方式,我们可以画出如下的阶梯图。

image-20220523103306637

如果想要先序遍历,结合上一讲我们讲过的原理,可知遍历方式的不同在于经过第几次时去访问该元素。如果在经过第一次时就拿出结点中的元素则为先序遍历,其他遍历懂得都懂,这里不过多赘述,不清楚的可以看看上一讲。

如果想要在代码中使用先序遍历,只需要在前面的框架中将(1)替换为visit§,其中visit()这个函数可以是自己写的访问结点元素的一个函数。

void r(BTNode  p)
{
	if(p != NULL)
	{
		visit(p)
		r(p->rChild);
		//(2)
		r(p->lChild);
		//(3)
	}
}

如果是中序则将visit()放在(2),后序则放在(3)。


先序遍历非递归

我们在栈那一讲中知道,栈的作用和递归一样。故如果不想使用递归来解决遍历的话,可以考虑用栈来解决这个问题。

入栈完读取数据后就可以出栈。左右孩子入栈时遵循先右后左,这是因为栈是后入先出,只有先右后左出栈时才能先左后右。如下:

image-20220523105745484

我们定义一个栈。则出入栈顺序应该是:

1入栈,1读取数据发现两个孩子结点,1出栈,先右后左,则3进栈后2进栈,而后读2,发现有孩子,故2出栈,5入栈后4入栈,读取4,发现4没有孩子,4出栈,而后5在栈顶,读取5,5没有孩子,5出栈,而后是3,读取3其有孩子,3出栈,7入栈后6入栈,读取6有孩子,6出栈,8入栈,8读取后没有孩子,8出栈,栈顶为7,读取7其无孩子,7出栈。至此先序遍历结束。

我们来看看代码的实现:

void preorderNonrecursion(BTNode *bt)
{
	if(bt! = NULL)
	{
		BTNode * Stack[maxSize];//定义顺序栈
		int top = -1;//定义栈顶指针
		BTNode *p = NULL;//定义遍历指针
		
		Stack[++top] = bt;//根节点入栈
		while(top != -1)
		{
			p = Stack[top--];
			Visit(p);//取元素
			if(p->rChild != NULL)
				Stack[++top] = p->rChild;
			if(p->lChild != NULL)
				Stack[++top] = p->lChild;
		}
	}
}

后序遍历非递归

先将后序遍历的原因是,后序遍历和先序遍历有一些关系。

我们将后序遍历的倒排叫做逆后序。如下图所示:

image-20220602150557070

仔细观察逆后序我们可以发现一件事。先序遍历是先遍历根,然后遍历左子树,然后遍历右子树。而逆后序是先遍历根,然后右子树,最后左子树。

得到逆序后后,我们可以使用C++容器特有resort方法,也可以将得到的元素全部压入栈中然后取出,即可得到后序遍历。

void postorderNonrecursion(BTNode *bt)
{
	if(bt! = NULL)
	{
		BTNode * Stack1[maxSize],*Stack2[maxSize];//定义顺序栈
		int top1,top2 = -1;//定义栈顶指针
		
		BTNode *p = NULL;//定义遍历指针
		
		Stack1[++top1] = bt;//根节点入栈
		while(top1 != -1)
		{
			p = Stack1[top1--];
			Stack2[++top2] = p;
			if(p->lChild != NULL)
				Stack1[++top] = p->lChild;
				
			if(p->rChild != NULL)
				Stack1[++top] = p->rChild;
		}
		while(top != -1)
		{
			p = Stack2[top2--];
			Visit(p);
		}
	}
}

中序遍历非递归化

image-20220602150628486

中序遍历是这么做的:开始直接一直往左的深处走,途中经过的结点全部压入栈,如上图,压入栈的有1,2,4。

而后,出栈并读取栈顶元素。若栈顶元素子树元素非空则入栈,则4出栈,栈顶元素为2。2读取元素并出栈,2有子树,故5入栈,栈中元素为1,5。

5读取元素并出栈,无子树。

1读取元素并出栈,有子树,3入栈。3有左子树,故一直往左子树深处走。途经6,8,故6,8入栈。此时栈中元素3,6,8,栈顶元素为8。

8读取元素并出栈,无子树。

6读取元素并出栈,无子树。

3读取元素并出栈,有子树,故7入栈。

7读取元素并出栈,无子树。

故全程中序遍历元素为:4,2,5,1,8,6,3,7

void inorderNonrecursion(BTNode *bt)
{
	if(bt! = NULL)
	{
		BTNode * Stack[maxSize];//定义顺序栈
		int top = -1;//定义栈顶指针
		
		BTNode *p = NULL;//定义遍历指针
		p = bt;//将遍历指针指向根节点
		while(top != -1 || p!= nullptr)
		{
			while(p != nullptr)
			{
				Stack[++top] = p;
				p = p->lChild;
			}
			if(top != -1)
			{
				p = Stack[top--];
				Visit(p);
				p = p->rChild;
			}
		}
	}
}

9.3 二叉树层次遍历


想要层次遍历二叉树,我们需要辅助队列。

image-20220602150910378

首先是1入队,然后1出队,访问1的左右子树。存在2和3。

2入队后3入队,然后2出队,访问2的左右子树。存在。故4,5入队。

3出队,访问3的左右子树,存在。故6,7入队。

4出队,访问4的左右子树,不存在。

5出队,访问5的左右子树,不存在。

6出队,访问6的左右子树,存在8,8入队。

7出队,访问7的左右子树,不存在。

8出队,访问8的左右子树,不存在。

至此遍历完成。

void level(BTNode * bt)
{
	if(bt != NULL)
	{
		int front,rear;
		BTNode *que[maxSize];
		front = rear = 0;
		BTNode *p;
		
		rear = (rear +1)%maxSize;
		que[rear] = bt;
		while(front != rear)
		{
			front = (front + 1) % maxSize;
			p = que[front];
			Visit(p);
			if(p->lChild != NULL)
			{
				rear = (rear + 1)%maxSize;
				que[rear] = p->lChild;
			}
			if(p->rChild != NULL)
			{
				rear = (rear + 1)%maxSize;
				que[rear] = p->rChild;
			}
		}
	}
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ArimaMisaki

如果知识有用请我喝杯咖啡

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值