二叉树的非递归遍历(面试常考)

二叉树的非递归遍历

  • 前言
  • 例子
  • 前序遍历(栈实现)
  • 中序遍历(栈实现)
  • 后序遍历(栈实现)
  • 层次遍历(队列实现)

目录 目录
顺序表 单链表(不带附加头结点)
双链表(带附加头结点) 栈(顺序表实现)
队列(链式,不带附加头结点) 二叉树
二叉树的非递归遍历

前言

通常,对于二叉树的实现,面试官并不会去问你递归怎么实现,看了我前面写的二叉树,你就知道递归写起来比较简单。
对于二叉树,面试官一般会问你非递归算法是怎么实现的,因为非递归算法需要用到栈、队列,所以问起来比较有意义,可以联系一系列知识。
ps:不知道栈、队列的同学先去看看我写的文章哦!

例子

为了使大家更加明白,这里树简化了许多结点,只有五个结点,ABCDE

前序:ABDCE(根左右)
中序:BDAEC(左根右)
后序:DBECA(左右根)

二叉树的非递归遍历(面试常考)_第1张图片

前序遍历(栈实现)

  1. 首先访问根结点,每次访问一个结点后,在向左子树继续遍历之前,先用栈记录该结点的右孩子,这样可以在左子树退回时,取得根结点的地址,在继续右子树的遍历

栈的源码在我的博客中有写过,找不到的朋友可以翻上面的目录
栈主要的变化:
二叉树的非递归遍历(面试常考)_第2张图片

void PreOrder(BinTree* T)
{
     
	assert(T);// 断言检查是否为空树
	Stack s;
	StackInit(&s);
	StackPush(&s, T);// 先访问根结点
	while (!StackEmpty(&s)) // 栈非空才继续访问
	{
     
		T = StackTop(&s);//访问元素
		StackPop(&s);// 访问了就删除元素
		printf("%c ", T->data);// 打印访问的元素
		if (T->rightChild != NULL) // 先将右孩子保存在栈中
			StackPush(&s, T->rightChild);
		if (T->leftChild != NULL)
			StackPush(&s, T->leftChild);// 先访问左孩子
	}
}

中序遍历(栈实现)

  1. 先访问中序下的第一个结点,从根结点开始沿leftChild 走到最左下角的结点,该结点的leftChild 为NULL,访问它之后,在遍历该结点的右子树,重复执行上面的过程,直到该子树遍历完

前序跟中序都是使用栈进行遍历的,栈的变化点是一样的

void InOrder(BinTree* T)
{
     
	assert(T);
	Stack s;
	StackInit(&s);
	while (T || !StackEmpty(&s)) // 结点非空或者栈非空才继续循环
	{
     
		while (T != NULL)
		{
     
			StackPush(&s, T); // 先将根结点到左边的左孩子集体入栈
			T = T->leftChild; // 先将左孩子入栈
		}
		if (!StackEmpty(&s))
		{
     
			T = StackTop(&s);//访问左孩子
			StackPop(&s);// 左孩子出栈
			printf("%c ", T->data); // 左孩子和根访问完了,在将右入栈
			T = T->rightChild;
		}
	}
}

后序遍历(栈实现)

  1. 后序相对于前序和中序来说比较麻烦一点,后序遍历访问顺序是:左右根,这也就意味着先要暂存根结点的地址,所以这里必须表明是在左子树(L)还是在右子树(R)中。
  2. 先用栈暂存根结点地址,在遍历左子树,此时根结点的标记 tag = L,当访问完左子树结点之后,还要去访问右子树,此时修改根结点的标记 tag = R,右子树访问完在访问根结点。

这里用到了其他结构体,与前序、中序有所不同,读者注意区分
二叉树的非递归遍历(面试常考)_第3张图片

void PostOrder(BinTree* T)
{
     
	if (T != NULL)
	{
     
		Stack s;
		StackInit(&s);
		StkNode w;//标记结构体
		do
		{
     
			while (T != NULL)
			{
     
				w.p = T;
				w.tag = L;
				StackPush(&s, w);
				T = T->leftChild;//一直将左孩子入栈
			}
			bool flag = true; // 继续循环标记,用于 R
			while (flag && !StackEmpty(&s))
			{
     
				w = StackTop(&s);
				StackPop(&s);
				T = w.p;
				switch (w.tag) // 判断栈顶的 tag 标记
				{
     
				case L:
					w.tag = R; // 修改根结点的标签,这样才能访问右孩子
					StackPush(&s, w);
					flag = false; // 修改循环标记
					T = T->rightChild; // 遍历右孩子,入栈
					break;
				case R:
					printf("%c ", T->data); // 左右孩子出栈
					break;
				default:
					break;
				}
			}
		} while (!StackEmpty(&s)); // 循环遍历
	}
}

层次遍历(队列实现)

  1. 在访问二叉树的某一结点时,把下一层的结点记忆在队列中,每当访问一个结点时,将它的子女依次加到队列的队尾,然后再去访问队头的结点,依次循环

二叉树的非递归遍历(面试常考)_第4张图片

用队列实现的,写了博客的,在目录里面,不理解的结合看
二叉树的非递归遍历(面试常考)_第5张图片

void LevelOrder(BinTree* T)
{
     
	Queue q;
	QueueInit(&q);
	if (T != NULL)
	{
     
		QueuePush(&q, T); // 根结点先入队访问
	}
	while (!QueueEmpty(&q))
	{
     
		T = QueueFront(&q);
		QueuePop(&q); printf("%c", T->data); // 访问队头元素
		if(T->leftChild)
			QueuePush(&q, T->leftChild); // 记录左孩子
		if (T->rightChild)
			QueuePush(&q, T->rightChild); // 记录右孩子
	}	
}

ps:涉及到栈和队列的内容,需读者先掌握着两块的知识,才能更好理解二叉树的非递归遍历

制作不易,记得三连!!!

你可能感兴趣的:(数据结构,数据结构)