前一篇简单的对二叉树进行初探,简单的了解了一下二叉树的一些概念,和二叉树的 顺序存储 和 链式存储 以及二叉树的一些简单操做,和二叉树的几种遍历方式。这一篇,咱们在对二叉树进行了解,假如这个二叉树
有不少的叶子节点
,那么叶子节点
的左孩子
和右孩子
的指针空间是否会浪费呢?数组
如开篇提到的,假如,一个二叉树有不少的叶子节点,以下图 bash
左孩子
指针域和
右孩子
指针域中则为
NULL
,那么对于这些空间来讲就是
空间浪费
。
那么,当
节点
的左孩子
的指针域为空的时候,能够将这个指向遍历(以中序遍历为例)时的前一个节点,称之为前驱
;函数当
节点
的右孩子
的指针域为空的时候,能够将这个指向遍历(以中序遍历为例)时的后一个节点,称之为后继
,以下图(以中序遍历为例):优化
二叉树
为
线索化二叉树
,
优势:ui
那么怎么区分节点
的左孩子
指针指向的是左子树
仍是前驱
呢?spa
那么咱们能够对二叉树节点
的结构进行优化,增长两个标识
,来指示具体指向的是左右子树
仍是前驱后继
,以下示意图:3d
// Link==0表示指向左右孩子指针
// Thread==1表示指向前驱或后继的线索
typedef enum {Link, Thread} PointerTag;
// 线索二叉树节点
typedef struct BiThrNode{
//数据
CElemType data;
//左右孩子指针
struct BiThrNode *lchild,*rchild;
//左右标记
PointerTag LTag;
PointerTag RTag;
}BiThrNode,*BiThrTree;
复制代码
下面以中序遍历为例,线索二叉树的线索化和遍历指针
线索二叉树的线索化code
首先,声明定义一些变量,并定义一个方法,cdn
#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);
复制代码