遍历二叉树的非递归算法并不难

文中提到的相关知识所在专栏:《数据结构与课程设计》

前言

本文将会详细介绍链式存储二叉树的非递归遍历算法,共有三种,分别是前序、中序和后序。并用这些遍历算法加一些扩展来完成经典题目,例如求树高、逆序遍历等等。

目录

1、递归算法回顾

简单回顾链式存储二叉树的递归遍历方法:

给出二叉树存储结构的代码:

cpp 复制代码
typedef struct Node {
	char val;
	Node* lchild; // 左子树
	Node* rchild; // 右子树
}BiNode,*BiTree;  // 重命名

前序遍历算法访问结点的次序为:根、左、右。

递归代码写为:

cpp 复制代码
void PreOrder(BiTree T) {
	if (T) {
		printf("%c", T->val);// 根
		PreOrder(T->lchild);// 左
		PreOrder(T->rchild);// 右
	}
}

那类似的,中序的结点访问次序为:左、根、右。

代码表示为:

cpp 复制代码
void InOrder(BiTree T) {
	if (T) {
		InOrder(T->lchild);// 左
		printf("%c", T->val);// 根
		InOrder(T->rchild);// 右
	}
}

后序的结点访问次序为:左、右、根。

cpp 复制代码
void PostOrder(BiTree T) {
	if (T) {
		PostOrder(T->lchild);
		PostOrder(T->rchild);
		printf("%c", T->val);
	}
}

大家如果清楚函数调用过程就会知道二叉树的这种递归遍历使用了函数的工作栈,栈内存放着函数的返回地址并为函数内的形式参数分配内存。所以对于非递归算法我们可以使用 来操作。

2、非递归遍历算法

使用非递归遍历需要借助栈,因此先定义栈的相关操作。

2.1、链栈的定义与相关操作

我这里使用链栈,给出代码定义:

cpp 复制代码
typedef struct SNode {
	BiNode* node; // 存放二叉树结点
	SNode* next;  // 指针域
}Stack,*TreeStack;

栈的特点是先入先出,在单链表中就对应着头插和头删,写成PushPop函数:

cpp 复制代码
// 压栈
void Push(TreeStack& S,BiTree &T) {
	Stack* pnew = new Stack(); // 创建结点
	pnew->node = T; // 赋值
	pnew->next = S; // 与下面一行结合为头插
	S = pnew;
}
// 弹栈
void Pop(TreeStack& S, BiTree &T) {
	TreeStack p = S; // 记录栈顶结点
	T = p->node; // T记录栈顶结点并返回
	S = S->next; // 栈顶结点指向相邻结点
	free(p); // 删除栈顶结点
}

判空函数 IsEmpty:

cpp 复制代码
bool IsEmpty(TreeStack S) {
	if (S == NULL) return true;
	else return false;
}

2.2、非递归前序遍历

  • 算法思想:

    • 从根结点开始把二叉树的左子树全部访问并依次压栈,直到最左边子树为空
    • 弹栈得到最左边的非空结点,对其右子树进行判断
    • 若为空,继续弹栈,若不为空,对该结点的左子树判断
    • 算法流程就是先访问根结点的左侧,等弹栈得到结点为根结点时,才会访问其右侧
  • 算法代码:

    c 复制代码
    void NoRecursionPreOrder(BiTree T) {
      TreeStack S = NULL;
      BiTree p = T;
      // 结点非空或栈非空执行循环
      while (p || !IsEmpty(S)) {
      	if (p) {
      		printf("%c", p->val);
      		Push(S, p);
      		p = p->lchild;
      	}
      	else {
      		Pop(S, p);
      		p = p->rchild;
      	}
      }
    }

2.3、非递归中序遍历

  • 算法思想:

    • 中序遍历最先访问的是最左下方的结点,因此从根结点开始依次让左子树入栈
    • 访问栈顶结点并出栈,判断其有无右子树
    • 若无右子树,弹栈访问并继续判断右子树的存在情况
    • 若有右子树,则入栈执行前面的步骤
    • 代码与先序不同的仅是负责执行访问代码的位置
  • 代码实现:

    c 复制代码
    void NoRecursionInOrder(BiTree T) {
      TreeStack S = NULL;
      BiTree p = T;
      // 结点非空或栈非空执行循环
      while (p || !IsEmpty(S)) {
      	if (p) {
      		Push(S, p);
      		p = p->lchild;
      	}
      	else {
      		Pop(S, p);
      		printf("%c", p->val);
      		p = p->rchild;
      	}
      }
    }

2.4、非递归后序遍历

算法思想:

后序遍历的访问顺序是 左右根,根结点最后访问,因此在结点出栈时要先判断右子树是否存在,若存在必须要先处理右子树。

其次要注意不要重复压栈右子树,因此可以设置辅助变量来指向刚刚弹栈的结点。

算法代码:

cpp 复制代码
void NoRecursionPostOrder(BiTree T) {
	Stack* S = NULL;
	BiNode* p = T, * r = NULL; // r 记录最近出栈的结点
	while (p || !IsEmpty(S)) {
		if (p) {
			Push(S, p);
			p = p->lchild;
		}
		else {
			p = S->node; // p 取栈顶结点
			if (p->rchild && p->rchild != NULL) {
				p = p->rchild;
			}
			else {
				Pop(S, p);
				printf("%c", p->val); // 访问
				r = p;
				p = NULL;
			}
		}
	}
}

3、求二叉树的高度

思路一:利用非递归的后序遍历算法,程序运行过程中栈的最大深度就是树高。

这是因为后序遍历只有在处理完任一结点的所有子树后才开始弹栈,因此可以设置一个变量来记录站内结点的数量,最大值即是该树的高度。

思路二:采用后序遍历的递归算法。

给出代码:

cpp 复制代码
int getH(BiTree T) {
	if (!T) return 0;
	int m = getH(T->lchild);
	int n = getH(T->rchild);
	return m > n ? m + 1 : n + 1;
}

递归算法的思考过程很考验对程序的理解,推荐大家动脑自行思考。

相关推荐
Liusp2 分钟前
数据结构和算法之树形结构B+树(7)
数据结构·b树·算法
上海物联网3 分钟前
MySql中索引为什么用B+树,他有什么特点?时间复杂度是多少?能存多少数据?是不是只能三层?他与B-树有什么不同?还有其它的树你是是否知道?
数据结构·b树
不脱发的牧码人6 分钟前
C#实现傅里叶变换算法
人工智能·算法·机器学习·c#·傅里叶分析
攻城狮7号1 小时前
【5.5】指针算法-三指针解决颜色分类
c++·算法
OKkankan1 小时前
单链表的实现(数据结构)
java·c语言·数据结构·c++
gkdpjj1 小时前
二叉树中的深搜
c++·算法
Lyqfor1 小时前
Redis学习:BitMap/HyperLogLog/GEO案例 、布隆过滤器BloomFilter、缓存预热+缓存雪崩+缓存击穿+缓存穿透
java·数据库·redis·学习·算法·缓存·1024程序员节
passer__jw7671 小时前
【LeetCode】【算法】146. LRU缓存
算法·leetcode
醉颜凉1 小时前
【NOIP普及组】明明的随机数
java·c语言·数据结构·c++·算法
yannan201903132 小时前
【算法】(Python)贪心算法
算法·贪心算法