前一篇简单的对二叉树进行初探,简单的了解了一下二叉树的一些概念,和二叉树的 顺序存储 和 链式存储 以及二叉树的一些简单操作,和二叉树的几种遍历方式。这一篇,我们在对二叉树进行了解,假如这个二叉树
有很多的叶子节点
,那么叶子节点
的左孩子
和右孩子
的指针空间是否会浪费呢?
如开篇提到的,假如,一个二叉树有很多的叶子节点,如下图
那么,上图中,标记 '^'的左孩子
指针域和右孩子
指针域中则为NULL
,那么对于这些空间来说就是空间浪费
。
我们称这种那么,当
节点
的左孩子
的指针域为空的时候,可以将这个指向遍历(以中序遍历为例)时的前一个节点,称之为前驱
;当
节点
的右孩子
的指针域为空的时候,可以将这个指向遍历(以中序遍历为例)时的后一个节点,称之为后继
,如下图(以中序遍历为例):
二叉树
为线索化二叉树
,
优点:
那么怎么区分节点
的左孩子
指针指向的是左子树
还是前驱
呢?
那么我们可以对二叉树节点
的结构进行优化,增加两个标识
,来指示具体指向的是左右子树
还是前驱后继
,如下示意图:
// Link==0表示指向左右孩子指针 // Thread==1表示指向前驱或后继的线索 typedef enum {Link, Thread} PointerTag; // 线索二叉树节点 typedef struct BiThrNode{ //数据 CElemType data; //左右孩子指针 struct BiThrNode *lchild,*rchild; //左右标记 PointerTag LTag; PointerTag RTag; }BiThrNode,*BiThrTree; 复制代码
下面以中序遍历为例,线索二叉树的线索化和遍历
线索二叉树的线索化
首先,声明定义一些变量,并定义一个方法,
#define OK 1 #define ERROR 0 #define TRUE 1 #define FALSW 0 #define MAXSIZE 100 // 存储空间初始分配量 typedef int Status; // 函数类型 typedef char CElemType; CElemType Nill = '#'; // 字符型以空格标识空 #pragma mark -二叉树的构造 int indexs = 1; typedef char String[24]; /* 0号单元存放串的长度 */ String str; // 字符传构造字符数组 Status StrAssign(String T, char *chars) { int i; if (strlen(chars) > MAXSIZE) { return ERROR; }else { T[0] = strlen(chars); for (i = 1; i <= T[0]; i++) { T[i] = *(chars+i-1); } return OK; } } // 打印值 Status visit(CElemType e) { printf("%c ", e); return OK; } 复制代码
按照中序遍历的方式,创建线索化二叉树
,先创建,再线索化
// 中序二叉树线索化 // 定义全局变量,始终指向上一个访问的节点 BiThrTree pre; void InThreading(BiThrTree p) { if (p) { // ✅ 递归左子树线索化 InThreading(p->lchild); if (!p->lchild) { // ✅ 没有左孩子,其前驱节点刚放访问过,赋值给 pre // 修改左标识 p->LTag = Thread; // 左孩子的指针指向前驱 p->lchild = pre; } else { // 有左孩子,修改左标识 // 修改左标识 p->LTag = Link; } // ✅ 判断前驱的右孩子 if (!pre->rchild) { // 没有右孩子,p 就是 pre 的后继 // 修改右标识 pre->RTag = Thread; // 前驱右孩子指针指向后继(当前节点p) pre->rchild = p; } else { // 修改右标识 pre->RTag = Link; } // ✅ 修改pre 指向 p的前驱 pre = p; // ✅ 递归右子树线索化 InThreading(p->rchild); } } 复制代码
在对二叉树进行线索化的时候,我们可以增加一个头节点
* 左孩子指向根节点 * 右孩子指向中序遍历的最后一个节点 * 中序遍历的第一个节点的左孩子指针(原来为null)和最后一个节点的右孩子指针(原来为null)都指向头结点 复制代码
好处:可以从第一个节点起,顺着前驱遍历,也可以顺着后继遍历,不用开始就递归找左孩子,如下图:
代码:
Status InOrderThreading(BiThrTree *Thrt , BiThrTree T){ *Thrt = (BiThrTree)malloc(sizeof(BiThrNode)); if (!*Thrt) { exit(OVERFLOW); } // 建立头结点 (*Thrt)->LTag = Link; (*Thrt)->RTag = Thread; // 右指针回指向,暂时指向自己(还不知道最后一个节点) (*Thrt)->rchild = (*Thrt); if (!T) { // 首节点为空,左孩子指针指向头结点 (*Thrt)->lchild = *Thrt; } else { // ✅ 图中1 (*Thrt)->lchild = T; // 修改pre,指向上一个操作的节点 pre = (*Thrt); //中序遍历进行中序线索化 InThreading(T); //✅ 图中4,最后一个结点rchil 孩子指向头结点 pre->rchild = *Thrt; //✅ 最后一个结点线索化 pre->RTag = Thread; //✅ 图中2 (*Thrt)->rchild = pre; } return OK; } 复制代码
// 中序遍历 Status InOrderTraverse_Thr(BiThrTree T){ BiThrTree p = T->lchild; // p = T时,说明根节点(首节点)为空或者遍历结束 while (p != T) { // 遍历,找到开始的叶子节点,即找到左子树为空的节点 while (p->LTag == Link) { p = p->lchild; } // 访问 if(!visit(p->data)) /* 访问其左子树为空的结点 */ return ERROR; while (p->RTag == Thread && p->rchild!=T) { p=p->rchild; visit(p->data); /* 访问后继结点 */ } p = p->rchild; } return OK; } // 调用 BiThrTree H,T; //StrAssign(str,"ABDH#K###E##CFI###G#J##"); StrAssign(str,"ABDH##I##EJ###CF##G##"); CreatBiThrTree(&T); /* 按前序产生二叉树 */ InOrderThreading(&H,T); /* 中序遍历,并中序线索化二叉树 */ InOrderTraverse_Thr(H); 复制代码