二叉树遍历之递归与非递归遍历

对于树结构,最重要的部分莫过于遍历,因为对于树的其他操作,都离不开遍历操作,而其中最典型的遍历,便是对二叉树的遍历,说到二叉树的遍历,我们首先想到的肯定是用递归实现的先序、中序和后序遍历,因为代码简洁明了,也很容易理解,但是众所周知,递归实现的代码效率肯定不高,因此在这里,除了介绍常用的递归实现之外,还将介绍非递归实现的方法。

这里使用的是C++实现。

准备工作

首先这里使用的树结构如下所示,可以看到,这是一颗二叉排序树:

在这里插入图片描述

相应的代码如下所示:

typedef struct BiTNode {
	int data;
	struct BiTNode* lChild, * rChild;  //左右孩子指针
}BiTNode;

typedef struct {
	BiTNode* root;
}BiTree;

void insert(BiTree &tree, int data) {  //根据二叉排序树的思想插入结点
	BiTNode* node = new(BiTNode);
	node->data = data;
	node->lChild = node->rChild = nullptr;
	if (tree.root == nullptr) {
		tree.root = node;
	} else {
		BiTNode* temp = tree.root;
		while (temp != nullptr) {
			if (node->data < temp->data) {
				if (temp->lChild == nullptr) {
					temp->lChild = node;
					return;
				} else {
					temp = temp->lChild;
				}
			} else {
				if (temp->rChild == nullptr) {
					temp->rChild = node;
					return;
				} else {
					temp = temp->rChild;
				}
			}
		}
	}
}

然后在main()函数中添加上这段代码:

int data[10] = { 5, 3, 9, 2, 4, 1, 7, 10, 6, 8 };
BiTree tree;
tree.root = nullptr;
for (int i = 0; i < 10; i++)  insert(tree, data[i]);

那么接下来就进入到正题了。

递归版本实现二叉树遍历

void preOrder1(BiTNode* root) {  //先序遍历递归版
	if (root) {
		cout << root->data << " ";
		preOrder1(root->lChild);
		preOrder1(root->rChild);
	}
}

void inOrder1(BiTNode* root) {  //中序遍历递归版
	if (root) {
		inOrder1(root->lChild);
		cout << root->data << " ";
		inOrder1(root->rChild);
	}
}

void postOrder1(BiTNode* root) {  //后序遍历递归版
	if (root) {
		postOrder1(root->lChild);
		postOrder1(root->rChild);
		cout << root->data << " ";
	}
}

main()函数中显示:

int main() {
	int data[10] = { 5, 3, 9, 2, 4, 1, 7, 10, 6, 8 };
	BiTree tree;
	tree.root = nullptr;
	for (int i = 0; i < 10; i++)  insert(tree, data[i]);

	cout << "递归版先序遍历:" << endl;
	preOrder1(tree.root);
	cout << endl << "递归版中序遍历:" << endl;
	inOrder1(tree.root);
	cout << endl << "递归版后序遍历:" << endl;
	postOrder1(tree.root);

	delTree(tree.root);
	cout << endl << "释放成功!" << endl;
	return 0;
}

得到的运行结果如下:

在这里插入图片描述

非递归版本实现二叉树遍历

递归版本转换为非递归版本往往需要使用到栈结构。

非递归版本中先序遍历和中序遍历的思想是一致的:

void preOrder2(BiTNode* root) {  //先序遍历非递归版
	stack<BiTNode*> S;
	BiTNode* t = root;
	while (t || !S.empty()) {
		if (t) {  //若当前节点不为空,则先访问当前节点,入栈,再访问左子树
			cout << t->data << " ";
			S.push(t);
			t = t->lChild;
		} else {  //否则就访问栈顶结点的右子树
			t = S.top();
			S.pop();
			t = t->rChild;
		}
	}
}

void inOrder2(BiTNode* root) {  //中序遍历非递归版
	stack<BiTNode*> S;
	BiTNode* t = root;
	while (t || !S.empty()) {
		if (t) {
			S.push(t);
			t = t->lChild;
		} else {
			t = S.top();
			S.pop();
			cout << t->data << " ";
			t = t->rChild;
		}
	}
}

以先序遍历为例,首先判断当前节点是否为空,若不为空,则首先访问当前节点,之后需要将该节点入栈,这是为了后面访问该节点的右子树做准备的,然后指针指向该节点的左子树;若当前节点为空(这种情况就相当于是叶子节点的左指针,当然也可能是上一个节点只有右子树),说明上一个节点的左子树已经访问过了,那么根据先序遍历的思想,我们需要访问上一个节点的右子树,而上一个节点便是栈顶元素,因此首先需要出栈,然后指针指向其右子树,中序遍历类似。

非递归实现的后序遍历就要稍微复杂一点了,因为根节点是最后才访问的:

void postOrder2(BiTNode* root) {  //后序遍历非递归版
	stack<BiTNode*> S;
	BiTNode* t = root;
	BiTNode* r = nullptr;
	while (t || !S.empty()) {
		if (t) {
			S.push(t);
			t = t->lChild;
		} else {
			t = S.top();  //这里只是得到栈顶元素并未出栈
			if (t->rChild && t->rChild != r) {  //若有右子树且右子树未被访问过
				t = t->rChild;
			} else {
				S.pop();  //因为左右子树都已经被访问过了,所以栈顶元素应该被弹出栈了
				cout << t->data << " ";
				r = t;  //记录最近访问过的节点
				t = nullptr;  //节点访问完以后,需要将指针重置
			}
		}
	}
}

对于后序遍历,这里可能会有些难以理解,所以我们以前面的二叉树为例,大致地走一下流程:

首先根节点是5,根据代码我们可以知道,开始的几步都是进入if语句中,依次入栈的节点分别为5、3、2、1,然后1入栈之后,由于1是叶子节点,因此它的左子树为空,于是进入到else语句中,然后得到栈顶元素即1,注意这里并没有弹出栈顶元素,因为到这一步仅仅只是该节点的左子树遍历完了,再判断它的右子树是否存在且未被访问过,因为右子树为空,所以进入到else语句中,到了这里就说明对于以当前节点为根节点的子树,它的左右子树都已经遍历过了,所以需要将栈顶节点出栈,然后用r记录节点1,表示最近访问过的节点,同时我们需要将t指向nullptr,后面以此类推。

问题一、为什么在访问过节点t之后,需要将t指向nullptr?

这是因为若不将t指向nullptr,则接下来就会进入到if语句中。如上面介绍过的,t指向节点1,访问完节点1之后不指向nullptr的话,那么又会进入到if语句中,然后又是将节点1入栈,访问它的左子树,之后又进入到else语句中,访问节点1,以此往复,没错,进入死循环了!所以这里必须将t指向nullptr。

问题二、为什么要添加一个指针r来记录最近访问过的节点?

我们同样用实例来说明,在访问完1、2以后,我们便通过else语句进入到了节点3,由于右子树存在,于是我们进入到了节点4,由于节点4是叶子节点,因此访问完节点4之后,我们重新通过else语句进入到节点3,但是此时的节点3左右子树都已经访问过了,因此我们应该访问的节点应该是节点3,但是若没有指针r记录最近访问的节点的话,我们又进入到了节点4,于是乎,我们又进入死循环了!所以我们必须有一个指针r来记录最近访问的节点。

由上面的两个问题我们也可以发现,后序遍历的非递归实现确实要比先序遍历、中序遍历的非递归实现难一些。

main()函数中显示:

int main() {
	int data[10] = { 5, 3, 9, 2, 4, 1, 7, 10, 6, 8 };
	BiTree tree;
	tree.root = nullptr;
	for (int i = 0; i < 10; i++)  insert(tree, data[i]);
    
	cout << "非递归版先序遍历:" << endl;
	preOrder2(tree.root);
	cout << endl << "非递归版中序遍历:" << endl;
	inOrder2(tree.root);
	cout << endl << "非递归版后序遍历:" << endl;
	postOrder2(tree.root);

	delTree(tree.root);
	cout << endl << "释放成功!" << endl;
	return 0;
}

运行结果如下:

在这里插入图片描述

层序遍历

那么我们接下来再来介绍一下二叉树的层序遍历,层序遍历其实就是树结构中的BFS,因此层序遍历也要用到队列,还是以上面的二叉树为例:

层序遍历部分的代码:

void levelOrder(BiTNode* root) {
	queue<BiTNode*> Q;
	BiTNode* t;
	Q.push(root);
	while (!Q.empty()) {
		t = Q.front();
		Q.pop();
		cout << t->data << " ";
		if (t->lChild) {
			Q.push(t->lChild);
		}
		if (t->rChild) {
			Q.push(t->rChild);
		}
	}
}

在main()函数中调用:

int main() {
	int data[10] = { 5, 3, 9, 2, 4, 1, 7, 10, 6, 8 };
	BiTree tree;
	tree.root = nullptr;
	for (int i = 0; i < 10; i++)  insert(tree, data[i]);

	cout << "层序遍历:" << endl;
	levelOrder(tree.root);

	delTree(tree.root);
	cout << endl << "释放成功!" << endl;
	return 0;
}

运行结果如下:

在这里插入图片描述

完整代码

#include<iostream>
#include<stack>
using namespace std;

typedef struct BiTNode {
	int data;
	struct BiTNode* lChild, * rChild;  //左右孩子指针
}BiTNode;

typedef struct {
	BiTNode* root;
}BiTree;

void insert(BiTree &tree, int data) {  //根据二叉排序树的思想插入结点
	BiTNode* node = new(BiTNode);
	node->data = data;
	node->lChild = node->rChild = nullptr;
	if (tree.root == nullptr) {
		tree.root = node;
	} else {
		BiTNode* temp = tree.root;
		while (temp != nullptr) {
			if (node->data < temp->data) {
				if (temp->lChild == nullptr) {
					temp->lChild = node;
					return;
				} else {
					temp = temp->lChild;
				}
			} else {
				if (temp->rChild == nullptr) {
					temp->rChild = node;
					return;
				} else {
					temp = temp->rChild;
				}
			}
		}
	}
}

void delTree(BiTNode* root) {
	if (root == nullptr) return;
	delTree(root->lChild);
	delTree(root->rChild);
	delete(root);
}

void preOrder1(BiTNode* root) {  //先序遍历递归版
	if (root) {
		cout << root->data << " ";
		preOrder1(root->lChild);
		preOrder1(root->rChild);
	}
}

void inOrder1(BiTNode* root) {  //中序遍历递归版
	if (root) {
		inOrder1(root->lChild);
		cout << root->data << " ";
		inOrder1(root->rChild);
	}
}

void postOrder1(BiTNode* root) {  //后序遍历递归版
	if (root) {
		postOrder1(root->lChild);
		postOrder1(root->rChild);
		cout << root->data << " ";
	}
}

void preOrder2(BiTNode* root) {  //先序遍历非递归版
	stack<BiTNode*> S;
	BiTNode* t = root;
	while (t || !S.empty()) {
		if (t) {  //若当前节点不为空,则先访问当前节点,入栈,再访问左子树
			cout << t->data << " ";
			S.push(t);
			t = t->lChild;
		} else {  //否则就访问栈顶结点的右子树
			t = S.top();
			S.pop();
			t = t->rChild;
		}
	}
}

void inOrder2(BiTNode* root) {  //中序遍历非递归版
	stack<BiTNode*> S;
	BiTNode* t = root;
	while (t || !S.empty()) {
		if (t) {
			S.push(t);
			t = t->lChild;
		} else {
			t = S.top();
			S.pop();
			cout << t->data << " ";
			t = t->rChild;
		}
	}
}

void postOrder2(BiTNode* root) {  //后序遍历非递归版
	stack<BiTNode*> S;
	BiTNode* t = root;
	BiTNode* r = nullptr;
	while (t || !S.empty()) {
		if (t) {
			S.push(t);
			t = t->lChild;
		} else {
			t = S.top();  //这里只是得到栈顶元素并未出栈
			if (t->rChild && t->rChild != r) {  //若有右子树且右子树未被访问过
				t = t->rChild;
			} else {
				S.pop();  //因为左右子树都已经被访问过了,所以栈顶元素应该被弹出栈了
				cout << t->data << " ";
				r = t;  //记录最近访问过的节点
				t = nullptr;  //节点访问完以后,需要将指针重置
			}
		}
	}
}

int main() {
	int data[10] = { 5, 3, 9, 2, 4, 1, 7, 10, 6, 8 };
	BiTree tree;
	tree.root = nullptr;
	for (int i = 0; i < 10; i++)  insert(tree, data[i]);

	cout << "递归版先序遍历:" << endl;
	preOrder1(tree.root);
	cout << endl << "递归版中序遍历:" << endl;
	inOrder1(tree.root);
	cout << endl << "递归版后序遍历:" << endl;
	postOrder1(tree.root);
	 
	cout << endl;

	cout << "非递归版先序遍历:" << endl;
	preOrder2(tree.root);
	cout << endl << "非递归版中序遍历:" << endl;
	inOrder2(tree.root);
	cout << endl << "非递归版后序遍历:" << endl;
	postOrder2(tree.root);

	delTree(tree.root);
	cout << endl << "释放成功!" << endl;
	return 0;
}

运行结果如下:

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

花无凋零之时

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值