【数据结构】树(四)—— 线索二叉树(C语言版)
【数据结构】树(四)—— 线索二叉树(C语言)
- 前言导论
- 1. 为什么要加线索?
- 2. 线索二叉树的结构特点
- 3. 二叉树的存储结构
- 4. 二叉树的线索化
- 5. 如何在线索二叉树中找前驱与后继结点
- 6. 三种线索二叉树的比较
- 7.(总结)图文详解中序线索二叉树的线索化
- 8. 完整代码
前言导论
由《树(二)—— 二叉树》一文的介绍中可知,二叉数本身是一种非线性结构,采用任何一种遍历二叉树的方法,都可以得到树中所有结点的一个线性序列。在这个序列中,除第一个结点外,每个结点都有自己的直接前趋;除最后一个结点外,每个结点都有一个直接后继。
传统的二叉链表存储仅能体现一种父子关系,不能直接得到结点在遍历中的前驱或后继。
由此引出下列问题:
如何快速确定一个结点的前驱与后继结点,为此,线索二叉树引出解决确定结点的前驱与后继只能每次从根结点遍历的低效问题;
同时,由前文提到的二叉树的遍历可知,遍历方式分为:前序遍历、中序遍历、后序遍历。为此由线索二叉树确定一个结点的前驱与后继结点时,根据遍历方式的不同分为:
- 前序线索二叉树
- 中序线索二叉树
- 后序线索二叉树
下面对上述三种线索二叉树进行性说明,以中序遍历为主例
1. 为什么要加线索?
① 很多空指针域没有存储任何事物,对内存资源是一种浪费;
② 二叉链表中,我们只能知道每个结点指向其左右孩子结点的地址,却不知道每个结点的前驱是谁,后继是谁(这里的前序与后继是根据遍历方式而确定的);
③线索链表解决了无法直接找到该结点在某种遍历序列中的前驱和后继结点的问题;
④ 线索二叉树等于把一棵二叉树转变成了一个双向链表,这样我们插入删除某个结点将非常方便。
2. 线索二叉树的结构特点
n 个结点的二叉链表中含有 ( 2 n − ( n − 1 ) ) = n + 1 (2n-(n-1))=n+1 (2n−(n−1))=n+1个空指针域。
每一棵二叉树上,很多结点都含有未使用的指向 NULL 的指针域。除了度为 2 的结点,度为 1 的结点,有一个空的指针域;叶子结点两个指针域都为 NULL。
1. 规律:在有 n 个结点的二叉链表中必定存在 n+1 个空指针域。 2. 线索二叉树的作用:方便从一个指定结点出发,找出其前驱、后继;方便遍历 3. 可以表示前驱和后继的结点特点:二叉树结点左右孩子不全有,即左右孩子结点至少有一个为空的结点;3. 二叉树的存储结构
利用二叉链表中的空指针域,存放指向结点在某种遍历次序下的前驱和后继结点的指针,(这种附加的指针称为"线索")。这种加上了线索的二叉链表称为线索链表,相应的二叉树称为线索二叉树。
线索二叉树实际上就是使用这些空指针域来存储结点之间前趋和后继关系的一种特殊的二叉树。
注意:ltag,rtag只是存放0或1数字的布尔型变量,
#define TElemType int//宏定义,结点中数据域的类型//枚举,Link为0,Thread为1
typedef enum PointerTag{ Link, Thread // thread :线
}PointerTag;
//结点结构构造
typedef struct BiThrNode{TElemType data; //数据域struct BiThrNode* lchild,*rchild; //左孩子,右孩子指针域 PointerTag Ltag,Rtag; //标志域,枚举类型
}BiThrNode,*BiThrTree;
4. 二叉树的线索化
线索链表中的“线索”,指的是链表中指向结点前趋和后继的指针。二叉树经过某种遍历方法转化为线索二叉树的过程称为线索化。
线索化根据二叉树的遍历分为前序、中序、后序线索化
将二叉树转化为线索二叉树,实质上以某种次序在遍历二叉树的过程中,将二叉链表中的空指针改为指向直接前趋或者直接后继的线索。
线索化的过程即为在遍历的过程中修改空指针的过程。
在遍历过程中,如果当前结点没有左孩子,需要将该结点的 lchild 指针指向遍历过程中的前一个结点,所以在遍历过程中,设置一个指针(名为 pre ),时刻指向当前访问结点的前一个结点。
//中序对二叉树进行线索化
void InThreading(BiThrTree p){//如果当前结点存在if (p) {InThreading(p->lchild);//递归当前结点的左子树,进行线索化//如果当前结点没有左孩子,左标志位设为1,左指针域指向上一结点 preif (!p->lchild) {p->Ltag=Thread;p->lchild=pre;}//如果 pre 没有右孩子,右标志位设为 1,右指针域指向当前结点。if (!pre->rchild) {pre->Rtag=Thread;pre->rchild=p;}pre=p;//线索化完左子树后,让pre指针指向当前结点InThreading(p->rchild);//递归右子树进行线索化}
}
注意:中序对二叉树进行线索化的过程中,在两个递归函数中间的运行程序,和之前介绍的中序遍历二叉树的输出函数的作用是相同的。
将中间函数移动到两个递归函数之前,就变成了前序对二叉树进行线索化的过程;后序线索化同样如此。
中序遍历中,最先访问到的结点没有前驱结点,即其结点的左孩子无法指向其他结点,设置为空;最后遍历的结点没有后继结点,即其结点的右孩子无法指向其他结点,设置为空;
图示说明:
前驱线索:由左孩子指针充当;
后继线索:由右孩子指针充当。
5. 如何在线索二叉树中找前驱与后继结点
void InOrderThraverse_Thr(BiThrTree p)
{while(p){//一直找左孩子,最后一个为中序序列中排第一的while(p->Ltag == Link){p = p->lchild;}printf("%c ", p->data); //操作结点数据//当结点右标志位为1时,直接找到其后继结点while(p->Rtag == Thread && p->rchild !=NULL){p = p->rchild;printf("%c ", p->data);}//否则,按照中序遍历的规律,找其右子树中最左下的结点,也就是继续循环遍历p = p->rchild;}
}
6. 三种线索二叉树的比较
7.(总结)图文详解中序线索二叉树的线索化
先序线索二叉树:
后序线索二叉树:
8. 完整代码
#include <stdio.h>
#include <stdlib.h>
#define TElemType char//宏定义,结点中数据域的类型
//枚举,Link为0,Thread为1
typedef enum {Link,Thread
}PointerTag;
//结点结构构造
typedef struct BiThrNode{TElemType data;//数据域struct BiThrNode* lchild,*rchild;//左孩子,右孩子指针域PointerTag Ltag,Rtag;//标志域,枚举类型
}BiThrNode,*BiThrTree;BiThrTree pre=NULL;//采用前序初始化二叉树
//中序和后序只需改变赋值语句的位置即可
void CreateTree(BiThrTree * tree){char data;scanf("%c",&data);if (data!='#'){if (!((*tree)=(BiThrNode*)malloc(sizeof(BiThrNode)))){printf("申请结点空间失败");return;}else{(*tree)->data=data;//采用前序遍历方式初始化二叉树CreateTree(&((*tree)->lchild));//初始化左子树CreateTree(&((*tree)->rchild));//初始化右子树}}else{*tree=NULL;}
}
//中序对二叉树进行线索化
void InThreading(BiThrTree p){//如果当前结点存在if (p) {InThreading(p->lchild);//递归当前结点的左子树,进行线索化//如果当前结点没有左孩子,左标志位设为1,左指针域指向上一结点 preif (!p->lchild) {p->Ltag=Thread;p->lchild=pre;}//如果 pre 没有右孩子,右标志位设为 1,右指针域指向当前结点。if (pre&&!pre->rchild) {pre->Rtag=Thread;pre->rchild=p;}pre=p;//pre指向当前结点InThreading(p->rchild);//递归右子树进行线索化}
}
//中序遍历线索二叉树
void InOrderThraverse_Thr(BiThrTree p)
{while(p){//一直找左孩子,最后一个为中序序列中排第一的while(p->Ltag == Link){p = p->lchild;}printf("%c ", p->data); //操作结点数据//当结点右标志位为1时,直接找到其后继结点while(p->Rtag == Thread && p->rchild !=NULL){p = p->rchild;printf("%c ", p->data);}//否则,按照中序遍历的规律,找其右子树中最左下的结点,也就是继续循环遍历p = p->rchild;}
}int main() {BiThrTree t;printf("输入前序二叉树:\n");CreateTree(&t);InThreading(t);printf("输出中序序列:\n");InOrderThraverse_Thr(t);return 0;
}
运行结果
输入前序二叉树:
124###35##6##
输出中序序列:
4 2 1 5 3 6