学习笔记:二叉树的前序遍历,中序遍历,后续遍历以及中序线索化

当我们创建一棵二叉树时,我们往往会采取递归的方法对二叉树进行前序遍历,中序遍历,后序遍历,下面给出这三种遍历的代码。

一、二叉树的三种遍历

前序遍历 根 左 右

void pre_order(Node *root){
	if(root==NULL) return;
	printf("%d ",root->key);
	pre_order(root->lchild);
	pre_order(root->rchild);
	return;
}

中序遍历:左 根 右

void in_order(Node *root){
	if(root==NULL) return;
	in_order(root->lchild);
	printf("%d ",root->key);
    in_order(root->rchild);
	return;
}

 后序遍历:左 右 根

void post_order(Node *root){
	if(root==NULL) return;
	post_order(root->lchild);
    post_order(root->rchild);
	printf("%d ",root->key);
	return;
}

这种递归方式还是很好理解的,可以将其理解为:输出 * 序遍历的结果。其中边界条件为当这个  根节点为空。

若想要还原一棵二叉树,则需要前序+中序,或者中序+后序即可。

二、二叉树的线索化

为什么需要二叉树的线索化呢?我们都知道链表的遍历非常简单!找到下一节点只需要简单的一句话就可以,效率极高,而且具有较强的可读性。

Node = Node -> next;

 我们想要在二叉树中也实现这样简单有效的遍历方式,所以我们采取线索化的方式。

所谓线索化,可以理解为一种废物利用,将节点剩余的左右空指针利用起来!

左边空指针指向前驱;右边空指针指向后继(如图所示)

如何用代码实现线索化呢?我们在二叉树节点的数据结构中加上两个辅助变量,表示二叉树两边的属性。

普通二叉树节点的结构定义:

typedef struct Node{
	int key;
	Node *lchild,*rchild;
}Node;

线索化二叉树节点的结构定义: 

typedef struct Node{
	int key;
	int ltag,rtag;//1:线索 0:实际的边
	Node *lchild;
	Node *rchild;
}Node;

定义 ltag,rtag 变量,例如:ltag = 1,则该节点的左边指针为一条线索!ltag = 0,则为一个实实在在的

 因此,我们需要改写三种遍历方法的代码:

void pre_order(Node *root){
	if(root==NULL) return;
	printf("%d ",root->key);
	if(root->ltag==0)pre_order(root->lchild);
	if(root->rtag==0)pre_order(root->rchild);
	return ;
}
void in_order(Node *root){
	if(root==NULL) return;
	if(root->ltag==0)in_order(root->lchild);
	printf("%d ",root->key);
	if(root->rtag==0)in_order(root->rchild);
	return ;
}

void post_order(Node *root){
	if(root==NULL) return;
	if(root->ltag==0)post_order(root->lchild);
    if(root->rtag==0)post_order(root->rchild);
	printf("%d ",root->key);
	return ;
}

 当节点的指针为一个实实在在的边的时候,才向下继续深入遍历!

下面,我们来建立这棵二叉树的中序线索~

为了方便理解,我们从中序遍历的代码入手:

重点来啦!!!

void in_order(Node *root){
	if(root==NULL) return;
	if(root->ltag==0)in_order(root->lchild);
	printf("%d ",root->key);
	if(root->rtag==0)in_order(root->rchild);
	return ;
}

我们中序遍历的结果是输出这棵树的中序遍历结果,我们观察到,所有的输出结果其实都是由上面代码的一条语句输出出来的:

    printf("%d ",root->key);

于是,我们可以简单地得到:所以的节点都会以中序遍历的顺序 ,依次在这条语句的位置遍历!

void in_order(Node *root){
	if(root==NULL) return;
	if(root->ltag==0)in_order(root->lchild);
	//就是这里!
	if(root->rtag==0)in_order(root->rchild);
	return;
}

我们中序线索化便是基于这个顺序,给每一个节点进行线索化操作!

void __build_inorder_thread(Node *root){
	if(root==NULL) return;
	if(root->ltag==0)__build_inorder_thread(root->lchild);
	//---此处可以遍历到所有节点,并且从中序遍历的第一个节点开始---
	if(inorder_root==NULL) inorder_root=root;//这里记录中序遍历的第一个节点,下面需要使用
	if(root->lchild==NULL){//如果节点的左子树为空,则线索化指向前驱
		root->lchild=pre_Node;
		root->ltag=1;
	}
	if(pre_Node&&pre_Node->rchild==NULL){//如果节点有前驱,而且前驱的右子树为空,则将前驱节点线索化,使得其指向现节点
		pre_Node->rchild=root;
		pre_Node->rtag=1;
	}
	pre_Node=root;//重置前驱
	//---到这里哦!---
	if(root->rtag==0)__build_inorder_thread(root->rchild);
	return ;
}

但是!但是!但是!这个程序还是存在一个小小小BUG :

由于我们是基于节点的上一个节点进行该节点后继的线索化,如果我们到了最后一个节点呢?

比如下图的线索化图示:

若我们处理到了节点6,我们找不到下一个节点,然后该节点的右边空指针需要初始化使其指向NULL,我们无法处理。

于是我们对其进行二次封装

void build_inorder_thread(Node *root){
	__build_inorder_thread(root);
	pre_Node->rchild=NULL;
	pre_Node->rtag=1;
	return;
}

这样,我们便轻松地解决了这个问题~O(∩_∩)O

完成了二叉树的线索化,我们又该如何通过线索去遍历这棵二叉树呢?

我们想让遍历二叉树像遍历链表一样简单,于是定义getNext方法,使得二叉树可以这样遍历:

Node *node =inorder_root;
	while(node){
		printf("%d ",node->key);
		node=getNext(node);
	}

下面给出getNext方法:

Node *getNext(Node *node){//中序遍历 getNext
	if(node->rtag==1) return node->rchild;//如果该节点的右边为一条线索,则直接得到下一个节点
	//该节点右边为实实在在的边,则获得下一节点,即右子树的最左边的节点,这个节点是右子树中序遍历结果的第一个节点
	node=node->rchild;
	while(node->ltag==0){
		node=node->lchild;
	}
	return node;
}

这样,我们便完成了线索化二叉树的遍历,这样会使得遍历更加高效!

以下是完整验证代码,感兴趣的童鞋可以自行验证:

#include<iostream>
#include<time.h>
#include<cstdio>
#define MAX_NODE 10
using namespace std;
typedef struct Node{
	int key;
	int ltag,rtag;//1:线索 0:实际的边
	Node *lchild;
	Node *rchild;
}Node;
Node *getNewNode(int key){
	Node *p =new Node;
	p->key=key;
	p->lchild=p->rchild=NULL;
	p->ltag=p->rtag=0;
	return p;
}
Node *insert(Node*root,int key){
	if(root==NULL) return getNewNode(key);
	if(rand()%2) root->lchild=insert(root->lchild,key);
	else root->rchild=insert(root->rchild,key);
	return root;
}
void clear(Node*root){
	if(root==NULL) return;
	if(root->ltag==0)clear(root->lchild);
	if(root->rtag==0)clear(root->rchild);
	delete root;
	return ;
}

void pre_order(Node *root){
	if(root==NULL) return;
	printf("%d ",root->key);
	if(root->ltag==0)pre_order(root->lchild);
	if(root->rtag==0)pre_order(root->rchild);
	return ;
}
void in_order(Node *root){
	if(root==NULL) return;
	if(root->ltag==0)in_order(root->lchild);
	printf("%d ",root->key);
	if(root->rtag==0)in_order(root->rchild);
	return ;
}

void post_order(Node *root){
	if(root==NULL) return;
	if(root->ltag==0)post_order(root->lchild);
    if(root->rtag==0)post_order(root->rchild);
	printf("%d ",root->key);
	return ;
}
Node *pre_Node=NULL;Node *inorder_root=NULL;
void __build_inorder_thread(Node *root){
	if(root==NULL) return;
	if(root->ltag==0)__build_inorder_thread(root->lchild);
	//---此处可以遍历到所有节点,并且从中序遍历的第一个节点开始---
	if(inorder_root==NULL) inorder_root=root;//这里记录中序遍历的第一个节点,下面需要使用
	if(root->lchild==NULL){//如果节点的左子树为空,则线索化指向前驱
		root->lchild=pre_Node;
		root->ltag=1;
	}
	if(pre_Node&&pre_Node->rchild==NULL){//如果节点有前驱,而且前驱的右子树为空,则将前驱节点线索化,使得其指向现节点
		pre_Node->rchild=root;
		pre_Node->rtag=1;
	}
	pre_Node=root;//重置前驱
	//---到这里哦!---
	if(root->rtag==0)__build_inorder_thread(root->rchild);
	return ;
}
void build_inorder_thread(Node *root){
	__build_inorder_thread(root);
	pre_Node->rchild=NULL;
	pre_Node->rtag=1;
	return;
}
Node *getNext(Node *node){//中序遍历 getNext
	if(node->rtag==1) return node->rchild;//如果该节点的右边为一条线索,则直接得到下一个节点
	//该节点右边为实实在在的边,则获得下一节点,即右子树的最左边的节点,这个节点是右子树中序遍历结果的第一个节点
	node=node->rchild;
	while(node->ltag==0){
		node=node->lchild;
	}
	return node;
}
int main(){
	srand(time(0));
	Node *root=NULL;
	for(int i=0;i<MAX_NODE;i++){
		root=insert(root,rand()%100);
	}	
	printf("前序遍历结果:\n");
	pre_order(root); printf("\n");//前序遍历
	pre_Node=NULL;
	build_inorder_thread(root);
	printf("中序遍历结果:\n");
	in_order(root);  printf("\n");//中序遍历
	printf("后序遍历结果:\n");
	post_order(root);printf("\n");//后序遍历
	//like linklist
	printf("线索化后的中序遍历结果:\n");
	Node *node =inorder_root;
	while(node){
		printf("%d ",node->key);
		node=getNext(node);
	}
	printf("\n");
	clear(root);
	return 0;
}

运行截图:

可以看到中序遍历结果和线索化后的中序遍历结果是一样的。

以上就是二叉树的三种遍历和线索化的全部内容了,感谢阅读!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值