遍历二叉树是以一定规则将二叉树中结点排列成一个线性序列,得到二叉树中结点的先序序列或中序序列或后序序列。这实际上是对一个非线性序列结构进行线性化操作,使每个结点(除了第一和最后一个)在这些线性序列中有且仅有一个直接前驱和直接后继。
现在的问题是,在常用的二叉链表作为存储结构时,我们只能找到结点左右孩子的信息,而不能直接找到结点在任意序列中前驱以及后继的信息,这些信息只能通过遍历的动态过程中得到。
另一方面,在一个含有n个结点普通二叉树当中,有n+1个空链域,我们利用这些空链域来存储结点之间的前驱后继关系。在原有二叉树结点结构的基础上增加两个标志域,来判断一个结点的左右孩子指针到底指向的真的是左右子树还是前驱后继结点。
以下是线索二叉树结点的代码定义:
typedef struct BiTNode {
int data;
struct BiTNode* lchild;
struct BiTNode* rchild;
int ltag, rtag;//左右线索标志
}BiTNode,* BiTree;
BiTNode* pre = NULL;//全局变量,指向当前访问结点的前驱
下来,我们线索化一个二叉树。分以下三个步骤,首先建立线索:
void BuildThread(BiTree T) {//建立线索
if (T->lchild == NULL) {//左子树为空,建立前驱结点
T->lchild = pre;
T->ltag = 1;
}
if (pre != NULL && pre->rchild == NULL) {//右子树为空,建立后继结点
pre->rchild = T;
pre->rtag = 1;
}
pre = T;
}
第二步,采用中序遍历的思想,遍历到每一个结点,对每一个结点建立线索,这样的建立方式叫中序线索化:
void ThreadBiTree(BiTree T) {
if (T != NULL) {
ThreadBiTree(T->lchild);
BuildThread(T);
ThreadBiTree(T->rchild);
}
}
最后,整理之前两块代码,我们建立一个线索二叉树:
void BuildBiTree_Thr(BiTree T) {
if (T != NULL) {
ThreadBiTree(T);
if (pre->rchild == NULL)
pre->rtag = 1;
}
printf("\n线索二叉树建立完成!\n");
}
采用中序遍历线索化二叉树的方式时,需注意,最后被访问的结点无法通过第二步的递归调用被线索化。从而在第三步对最后一个被访问的结点单独进行处理。
由于我们采用中序线索化的方式线索化了二叉树,所以在遍历的时候也应该采用中序遍历线索二叉树的方式。通过这个操作,我们可以得到线索二叉树的中序序列。
首先要如何知道一个结点P的后继位置。两种情况,直接给出结论:
1.如果P的右标志域是1,即说明P的右指针是一个线索,那么P的后继就是P的右指针所指向的结点。
2.如果P的右标志域是0,说明P这个结点有右子树,那么P的后继结点应该是P的右子树当中最左下的结点。
代码实现如下:
BiTNode* FirstNode(BiTNode* p) {//寻找最先被访问的结点
while (p->ltag == 0)p = p->lchild;
return p;
}
BiTNode* NextNode(BiTNode* p) {//寻找后继结点
if (p->rtag == 0)return FirstNode(p->rchild);
else return p->rchild;
}
结合上面的代码,我们可以实现一个采用非递归方式的遍历线索二叉树的方法:
void InOrder_Thread(BiTree T) {//中序遍历线索二叉树
for (BiTNode* p = FirstNode(T);p != NULL;p = NextNode(p))
visit(p);
}
类似的,我们可以采用寻找前驱的方式,得到一个逆序遍历线索二叉树的方法。原理是类似的,不做过多说明。
附加:完整代码
#include
#include
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define OVERFLOW -1
typedef struct BiTNode {
int data;
struct BiTNode* lchild;
struct BiTNode* rchild;
int ltag, rtag;//左右线索标志
}BiTNode,* BiTree;
BiTNode* pre = NULL;//全局变量,指向当前访问结点的前驱
void CreateBiTree(BiTree* T);//创建二叉树
void BuildBiTree_Thr(BiTree T);//建立线索二叉树
void visit(BiTree T);//访问结点数据域
void BuildThread(BiTree T);//建立线索
void ThreadBiTree(BiTree T);//将二叉树线索化
BiTNode* FirstNode(BiTNode* p);//找到最先被访问到的结点
BiTNode* NextNode(BiTNode* p);//找到后继结点
BiTNode* LastNode(BiTNode* p);//找到最后被访问的结点
BiTNode* PreNode(BiTNode* p);//找到前驱结点
void InOrder_Thread(BiTree T);//中序遍历线索二叉树
void InOrder_Thread_Reverse(BiTree T);//中序逆向遍历线索二叉树
void PreOrder(BiTree T);//先序遍历:普通二叉树
void InOrder(BiTree T);//中序遍历:普通二叉树
void PostOrder(BiTree T);//后序遍历:普通二叉树
void CreateBiTree(BiTree* T) {
int num;
scanf_s("%d", &num);
if (num == 0)(*T) = NULL;
else {
(*T) = (BiTree)malloc(sizeof(BiTNode));
if (!(*T)) {
printf("\n内存分配失败\n");
exit(OVERFLOW);
}
(*T)->data = num;
(*T)->ltag = 0;
(*T)->rtag = 0;
CreateBiTree(&(*T)->lchild);
CreateBiTree(&(*T)->rchild);
}
}
void BuildBiTree_Thr(BiTree T) {
if (T != NULL) {
ThreadBiTree(T);
if (pre->rchild == NULL)
pre->rtag = 1;
}
printf("\n线索二叉树建立完成!\n");
}
void visit(BiTree T) {
printf("%d ", T->data);
}
void BuildThread(BiTree T) {//建立线索
if (T->lchild == NULL) {//左子树为空,建立前驱结点
T->lchild = pre;
T->ltag = 1;
}
if (pre != NULL && pre->rchild == NULL) {//右子树为空,建立后继结点
pre->rchild = T;
pre->rtag = 1;
}
pre = T;
}
void ThreadBiTree(BiTree T) {
if (T != NULL) {
ThreadBiTree(T->lchild);
BuildThread(T);
ThreadBiTree(T->rchild);
}
}
BiTNode* FirstNode(BiTNode* p) {//寻找最先被访问的结点
while (p->ltag == 0)p = p->lchild;
return p;
}
BiTNode* NextNode(BiTNode* p) {//寻找后继结点
if (p->rtag == 0)return FirstNode(p->rchild);
else return p->rchild;
}
BiTNode* LastNode(BiTNode* p) {//寻找最后被访问的结点
while (p->rtag == 0)p = p->rchild;
return p;
}
BiTNode* PreNode(BiTNode* p){//寻找前驱结点
if (p->ltag == 0)return LastNode(p->lchild);
else return p->lchild;
}
void InOrder_Thread(BiTree T) {//中序遍历线索二叉树
for (BiTNode* p = FirstNode(T);p != NULL;p = NextNode(p))
visit(p);
}
void InOrder_Thread_Reverse(BiTree T) {//中序逆向遍历线索二叉树
for (BiTNode* p = LastNode(T);p != NULL;p = PreNode(p))
visit(p);
}
void PreOrder(BiTree T) {
if (T != NULL) {
visit(T);
PreOrder(T->lchild);
PreOrder(T->rchild);
}
}
void InOrder(BiTree T) {
if (T != NULL) {
InOrder(T->lchild);
visit(T);
InOrder(T->rchild);
}
}
void PostOrder(BiTree T) {
if (T != NULL) {
PostOrder(T->lchild);
PostOrder(T->rchild);
visit(T);
}
}
void main() {
BiTree T;
printf("\n输入先序序列构建二叉树(空结点用“0”表示):");
CreateBiTree(&T);
BuildBiTree_Thr(T);
printf("\n中序遍历线索二叉树序列为:");
InOrder_Thread(T);
printf("\n中序逆向遍历线索二叉树序列为:");
InOrder_Thread_Reverse(T);
}
这样,中序线索二叉树的建立就完成了,我们利用了空链域实现了这一操作。现在的二叉树当中,原本指向空的左,右指针,现在已经指向了该结点的中序前驱,后继结点。对于那些左右指针本身不指向空的结点,我们也通过中序遍历的特点,得到了通用性的结论,得到了这些结点的后继、前驱结点的位置。
这里我们只是展示了线索二叉树的一种方式,关于二叉树的线索化,除了中序线索化,还有前序线索化和后序线索化。在后两种方式中,和中序线索化有区别,如果只是使用二叉链表,在前序线索二叉树中,我们无法找到结点的前驱结点位置;在后序线索二叉树中,我们无法找到结点的后继结点位置。解决方法有两种,采用三叉链表,或是采用遍历整个序列的老办法。