【知识点2】二叉树的遍历⭐⭐⭐⭐⭐

在这里插入图片描述
常见的二叉树的遍历一共有四种:

  • 先序遍历
  • 中序遍历
  • 后序遍历
  • 层次遍历

对于前三种遍历,对于一个根结点root,它的左子树总是先于右子树被遍历。其中的“先中后”是指根结点root在遍历中的位置,因此:

  • 先序遍历:根结点->左子树->右子树
  • 中序遍历:左子树->根结点->右子树
  • 后序遍历:左子树->右子树->根结点

1. 先序遍历

先序遍历:根结点->左子树->右子树
在这里插入图片描述

实现

为了实现递归的先序遍历,需要得到两样东西:递归式递归边界

  • 递归式:由先序遍历的定义直接得到,即先访问根结点,再递归访问左子树,最后递归访问右子树。
  • 递归边界:在递归访问子树时,如果碰到子树为空,那么就说明到达了死胡同。

代码:

void preorder(node* root){
	if(root == NULL){
		return;	//到达空树,递归边界 
	}
	//访问根结点root,例如将其数据域输出
	printf("%d\n",root->data); 
	//访问左子树
	preorder(root->lchild);
	preorder(root->rchild); 
} 

示例

在这里插入图片描述

性质

  • 对一棵二叉树的先序遍历序列,序列的第一个一定是根结点。

2. 中序遍历

中序遍历:左子树->根结点->右子树

实现

void inorder(node* root){
	if(root == NULL){
		return;	//到达空树,递归边界 
	}
	//访问左子树
	inorder(root->lchild);
	//访问根结点root,例如将其数据域输出
	printf("%d\n",root->data);
	//访问右子树
	inorder(root->rchild); 
}

示例

在这里插入图片描述

性质

  • 只要知道根结点,就可以通过根结点在中序遍历序列中的位置区分出左子树和右子树。

3. 后序遍历

后序遍历:左子树->右子树->根结点

实现

void postorder(node* root){
	if(root == NULL){
		return;	//到达空树,递归边界
	}
	//访问左子树
	postorder(root->lchild); 
	//访问右子树
	postorder(root->rchild);
	//访问根结点root,例如将其数据域输出
	printf("%d\n",root->data);
}

示例

在这里插入图片描述

性质

  • 对后序遍历序列来说,序列的最后一个一定是根结点。
  • 无论是先序遍历序列还是后序遍历序列,都必须知道中序遍历序列才能唯一地确定一棵树。

4. 层序遍历

在这里插入图片描述

实现

层序遍历是指按层次的顺序从根结点向下逐层进行遍历,且对同一层的结点为从左到右遍历。

层次遍历就相当于是对二叉树从根结点开始的广度优先搜索,其基本思想如下:

  1. 将根结点root加入队列q;
  2. 取出队首结点,访问它;
  3. 如果该结点有左孩子,将左孩子入队;
  4. 如果该结点有右孩子,将右孩子入队;
  5. 返回2,直到队列为空。
    代码如下:
void layerOrder(node* root){
	queue<node*> q;	//注意队列里存的是地址
	q.push(root)//将根结点地址入队
	while(!q.empty()){
		node* now = q.front();	//取出队首元素
		q.pop();
		printf("%d",now->data);	//访问队首元素
		if(now->lchild != NULL) q.push(now->lchild);	//左子树非空
		if(now->rchild != NULL) q.push(now->rchild);	//右子树非空 
	} 
}

注意:队列中的元素是node*型而不是node型。

另外,很多题目当中要求计算出每个结点所处的层次,这时就需要在二叉树结点的定义中添加一个记录层次layer的变量:

struct node{
	int data;
	int layer;
	node* lchild;
	node* rchild;
}; 
//层序遍历
void layerOrder(node* root){
	queue<node*> q;
	root->layer = 1;
	q.push(root);
	while(!q.empty()){
		node* now = q.front();
		q.pop();
		printf("%d",now->data);
		if(now->lchild != NULL){
			now->lchild->layer = now->layer + 1;
			q.push(now->lchild);
		}
		if(now->rchild != NULL){
			now->rchild->layer = now->layer + 1;
			q.push(now->rchild);
		}
	}
} 

5. 给定一棵二叉树的先序遍历序列和中序遍历序列,重建这颗二叉树⭐⭐⭐⭐⭐

在这里插入图片描述
事实上,如果递归过程中当前先序序列的区间为[preL,preR],中序序列的区间为[inL,inR],那么左子树的结点个数为numLeft=k-inL。这样左子树的先序序列区间就是[preL+1,preL+numLeft],左子树的中序序列区间为[inL,k-1];右子树的先序序列区间是[preL+numLeft+1,preR],右子树的中序序列区间是[k+1,inR],如图9-10所示。
在这里插入图片描述
那么,如果一直这么递归下去,什么时候是尽头呢?这个答案很显然,因为只要先序序列的长度的长度小于等于0时,当前二叉树就不存在了,于是就能以这个条件得到递归边界。
代码如下:

//当前先序序列区间为[preL,preR],中序序列区间为[inL,inR],返回根结点地址
node* create(int preL,int preR,int inL,int inR){
	if(preL > preR){	//注意,preL == preR 是长度为一的序列
		return NULL;	//先序序列长度小于等于0,直接返回 
	}
	node* root = new node;	//新建一个新的结点,用来存放当前二叉树的根节点
	root->data = pre[preL];	//新结点的数据域为根结点的值
	int k;
	for(k = inL;k <= inR;k++){
		if(in[k] == pre[preL]){	//在中序序列中找到in[k] == pre[L]的结点 
			break;
		} 
	}
	
	int numLeft = k - inL;	//左子树的结点个数
	
	//左子树的先序区间为[preL+1,preL+numLeft],中序区间为[inL,k-1]
	//返回左子树的根结点地址,赋值给root的左指针	
	root->lchild = create(preL+1,preL+numLeft,inL,k-1);
	
	//右子树的先序区间为[preL+numLeft+1,preR],中序区间为[k+1,inR]
	//返回右子树的根结点地址,赋值给root的右指针	
	root->rchild = create(preL+numLeft+1,preR,k+1,inR);
	
	return root;	//返回根结点地址
} 

最后需要思考,如何通过中序遍历序列和层序遍历序列重建二叉树?

而且可以得出一个结论:中序序列可以与先序序列、后序序列、层序序列中的任意一个来构建唯一的二叉树,而后三者无论怎么组合都无法构建一棵唯一的二叉树。 原因是先序、后序、层序均是提供根结点,作用是相同的,都必须由中序序列来区分出左右子树。

6. 二叉树的静态实现⭐⭐⭐⭐⭐

本节适用于能理解前面的所有内容,但是对指针的写法不太自信的读者。通过下面的学习,读者应能完全不使用指针,而是简单使用数组来完成二叉树的上面所有操作。

所谓的静态二叉链表是指,结点的左右指针域使用int型代替,用来表示左右子树的根结点在数组中的下标。于是,结点node定义变为如下:

struct node{
	typename data;
	int lchild;
	int rchild;
}Node[maxn];

在这样的定义下,结点的动态生成就可以转变为如下的静态指定

int index = 0;
int newNode(int v){
	Node[index].data = v;
	Node[index].lchild = -1;
	Node[index].rchild = -1;
	return index++;
}

7. 题型训练

  1. 遍历二叉树——华科
  2. 遍历二叉树——清华
  3. 【先序遍历迭代实现】LeetCode 144. Binary Tree Preorder Traversal
  4. 【后序遍迭代实现】LeetCode 145. Binary Tree Postorder Traversal
  5. 【中序遍历迭代实现】LeetCode 94. Binary Tree Inorder Traversal
  6. ⭐PAT A1119 Pre- and Post-order Traversals
  7. PAT A1127 ZigZagging on a Tree
  8. 一套拳法👊刷掉n个遍历树的问题

8. 参考文档

  1. 算法笔记
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值