前言
之前我们研究二叉树,今天我们继续研究二叉树的优化--线索二叉树。
产生背景
在二叉树中,结点的度为0~2的范围。当度为0或1时,结点中为指向孩子的空间是没有用的,如图:

利用空间
浪费的空间利用起来,把没用的左右孩子的区域用来指向前驱或者后继结点。这样树在使用起来的时候,减少遍历的次数,这样会更有效率。如图:

结点增加标志位
通过上面的图片,很容易理解线索二叉树的相关逻辑。但是我们会遇到一个问题:如何区分孩子区域指向的是孩子结点还是前驱或后继结点呢?

通过对结点增加标志位来区分是孩子结点还是前驱后继结点。

似曾相识-双向链表
线索化的二叉树,其实和我们之前学习过的双向链表很类似;所以我们可以给二叉树增加一个头结点,这样在遍历的时更便捷:

代码
#include <stdio.h>
#include "string.h"
#include "stdlib.h"
#include "math.h"
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define MAXSIZE 100
typedef int Status;
typedef char CElemType;
CElemType Nil = '#';
int indexs = 1;
typedef char String[24];
String str;
Status strAssign(String T, char *chars) {
if (strlen(chars) > MAXSIZE) {
return ERROR;
} else {
T[0]=strlen(chars);//下标0的位置存储字符串长度
for (int i = 1; i <= T[0]; i++) {
T[i] = *(chars + i - 1);
}
return OK;
}
}
/**
Link == 0,标示指向左右孩子指针
Thread==1,标示指向前驱或后继的线索
*/
typedef enum {Link, Thread} PointerTag;
/**
线索二叉树结构体
*/
typedef struct BiThrNode{
CElemType data;
struct BiThrNode *lchild, *rchild;
PointerTag lTag, rTag;
}BiThrNode, *BiThrTree;
/**
打印
*/
Status visit(CElemType e) {
printf("%c ", e);
return OK;
}
/**
构造二叉树
*/
Status createBiThrTree(BiThrTree *T) {
CElemType h;
h = str[indexs++];
if (h == Nil) {
*T = NULL;
} else {
*T = (BiThrTree)malloc(sizeof(BiThrNode));
if (!*T) {
exit(OVERFLOW);
}
(*T)->data = h;
createBiThrTree(&(*T)->lchild);
if ((*T)->lchild) {//左孩子存在,标记为孩子指针
(*T)->lTag = Link;
}
createBiThrTree(&(*T)->rchild);
if ((*T)->rchild) {//右孩子存在,标记为孩子指针
(*T)->rTag = Link;
}
}
return OK;
}
BiThrTree pre;//全局变量,指向前一个结点
void inThreading(BiThrTree p) {
if (p) {
//递归左子树
inThreading(p->lchild);
if (!p->lchild) {//左孩子不存在,设置做孩子前驱线索
p->lTag = Thread;
p->lchild = pre;
} else {
p->lTag = Link;
}
if (!pre->rchild) {//前驱先说右孩子不存在,设置前驱结点的右孩子线索为当前结点
pre->rTag = Thread;
pre->rchild = p;
} else {
pre->rTag = Link;
}
pre = p;
//递归右子树
inThreading(p->rchild);
}
}
/**
中序遍历二叉树T,并线索化,创建头结点
*/
Status inOrderThreading(BiThrTree T, BiThrTree *Thrt) {
//头结点
*Thrt = (BiThrTree)malloc(sizeof(BiThrNode));
if (!*Thrt) {
exit(OVERFLOW);
}
//头结点左孩子标记为孩子指针
(*Thrt)->lTag = Link;
//头结点右孩子标记为线索指针
(*Thrt)->rTag = Thread;
//头结点右孩子指向自己
(*Thrt)->rchild = (*Thrt);
if (!T) {
(*Thrt)->lchild = *Thrt;
} else {
//头结点孩子指针指向根结点
(*Thrt)->lchild = T;
//前驱结点指向头结点
pre = (*Thrt);
//中序遍历进行线索化
inThreading(T);
//最后一个结点线索化
pre->rchild = *Thrt;
pre->rTag = Thread;
(*Thrt)->rchild = pre;
}
return OK;
}
/**
中序遍历二叉线索树
*/
Status inOrderTraverse(BiThrTree T) {
BiThrTree p;
p=T->lchild;//p指向根结点
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;
}
运行
int main(int argc, const char * argv[]) {
// insert code here...
printf("Hello, 线索二叉树!\n");
BiThrTree H,T;
strAssign(str,"ABDH##I##EJ###CF##G##");
createBiThrTree(&T); /* 按前序产生二叉树 */
inOrderThreading(T,&H); /* 中序遍历,并中序线索化二叉树 */
inOrderTraverse(H);
printf("\n");
return 0;
}
