当我们创建一棵二叉树时,我们往往会采取递归的方法对二叉树进行前序遍历,中序遍历,后序遍历,下面给出这三种遍历的代码。
一、二叉树的三种遍历
前序遍历 :根 左 右
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;
}
运行截图:
可以看到中序遍历结果和线索化后的中序遍历结果是一样的。
以上就是二叉树的三种遍历和线索化的全部内容了,感谢阅读!