整体介绍
线索二叉树是链表表示的树,它是利用了二叉树未被使用的 n + 1个闲置的指针构成的树;
根据二叉树的三种遍历方式构成了三种不同的线索二叉树;
二叉树的遍历只能从根结点开始依次遍历,而构建了线索二叉树后,就可以从二叉树中任何一个结点进行遍历,这就是线索化最大的意义了;
实际上线索二叉树的应用面是很窄的,但是学习它最重要的意义还是理解它的这种思想;
就是将闲置的空间充分利用起来,这应该是最重要的意义了;
线索二叉树的存储结构
与二叉树结构唯一的不同就是多了是否线索化的标识
typedef struct ThrBiTreeNode {
ElemType data;
struct ThrBiTreeNode *lchild, *rchild;
//这里是和普通的二叉树的区别,用来标识该结点的左右指针是否被线索化
//1表示线索化了,0表示未线索化
int ltag, rtag;
}ThrBiTreeNode, *ThrBiTree;
前(先)序线索二叉树
1、创建
这里是使用递归的方式创建的,非递归方式可以通过参考二叉树的非递归现实
void createPreTree(ThrBiTree root) {
ThrBiTree pre = NULL;
if (NULL == root) {
return;
}
//通过递归的方式进行前序线索化
preThread(root, pre);
//如果递归完成最后一个结点的后继结点因为空
if (NULL == pre -> rchild) {
pre -> rtag = 1;
}
}
void preThread (ThrBiTree &cur, ThrBiTreeNode &pre) {
//递归出口
if (NULL == cur) {
return;
}
//如果当前结点没有左孩子结点就将该指针线索化
if (NULL == cur -> lchild) {
cur -> lchild = pre;
cur -> ltag = 1;
}
//如果当前结点没有右孩子结点就线索化
if (NULL != pre && NULL != pre -> rchild) {
pre -> rchild = cur;
pre -> rtag = 1;
}
//记录当前结点的前驱结点,方便线索化处理
pre = p;
//由于当前结点的左孩子已被线索化,故这里需要判断,只线索化未被线索化的结点
if (p -> ltag = 0) {
preThread(p -> lchild, pre);
}
//右孩子的线索化
preThread(p -> rchild, pre);
}
2、遍历
由于前序遍历(根左右)和二叉树本身的特点,所以从一棵树的根结点无法找到其前驱,只能找到后继,因此无法通过前驱完成逆向遍历,只能通过后继顺序遍历树;
//寻找后继结点
ThrBiTreeNode *findNextNode(ThrBiTreeNode *p) {
//如果右孩子没有被线索化
if (0 == p -> rtag) {
//存在左孩子则,后继为左孩子
if (0 == p -> ltag && NULL != p -> lchild) {
return p -> lchild;
}
//左孩子不存在,则后继为右孩子
if (NULL != p -> rchild) {
return p -> rchild;
}
} else {//被线索化了,直接返回
return p -> rchild;
}
}
//前序遍历
void preOrder(ThrBiTreeNode *p) {
if (NULL == p) {
return;
}
//当前结点最先被遍历,然后查找后继结点依次遍历,为空就结束
for (ThrBiTreeNode *cur = p; cur != NULL; cur = findNextNode(cur)) {
visit(cur);
}
}
中序线索二叉树
1、创建
void createInTree(ThrBiTree root) {
ThrBiTreeNode *pre = NULL;
if (NULL == root) {
return;
}
//通过递归的方式进行中序线索化
inThread(root, pre);
//如果递归完成最后一个结点的后继结点因为空
if (NULL == pre -> rchild) {
pre -> rtag = 1;
}
}
void inThread (ThrBiTree &cur, ThrBiTreeNode &pre) {
//递归出口
if (NULL == cur) {
return;
}
//左孩子的线索化
inThread (cur -> lchild, pre);
//如果当前结点没有左孩子结点就将该指针线索化
if (NULL == cur -> lchild) {
cur -> lchild = pre;
cur -> ltag = 1;
}
//如果当前结点没有右孩子结点就线索化
if (NULL != pre && NULL != pre -> rchild) {
pre -> rchild = cur;
pre -> rtag = 1;
}
//记录当前结点的前驱结点,方便线索化处理
pre = p;
//右孩子的线索化
inThread(p -> rchild, pre);
}
2、遍历
中序遍历即可以找到前驱结点,也可以找到后继结点,因此可以正向和反向遍历
a、正向遍历
//找到当前结点中序遍历的第一个被遍历的结点
ThrBiTreeNode * findFirstNode(ThrBiTreeNode *cur) {
//中序遍历(左根右),因此要找到子树中最后一个左孩子结点,即为第一个要遍历的结点
while(p -> ltag == 0) {
p = p -> lchild;
}
return p;
}
//找到当前结点的后继结点
ThrBiTreeNode * findNextNode(ThrBiTreeNode *cur) {
//如果当前结点的右指针未被线索化,就调第一个方法寻找
if (cur -> rtag == 0) {
return findFirstNode(cur -> rchild);
} else {//被线索化就直接返回
return cur -> rchild;
}
}
void inorder(ThrBiTreeNode *p) {
if (NULL == p) {
return;
}
for (ThrBiTreeNode * cur = findFirstNode(p); cur != NULL; cur = findNextNode(cur)) {
visit(cur);
}
}
b、逆向遍历
//找到当前结点中序遍历的最后一个被遍历的结点
ThrBiTreeNode * findLastNode(ThrBiTreeNode *cur) {
//中序遍历(左根右),因此要找到子树中最后一个右孩子结点结点,即为最后要遍历的结点
while(p -> rtag == 0) {
p = p -> rchild;
}
return p;
}
//找到当前结点的前继结点
ThrBiTreeNode * findPreNode(ThrBiTreeNode *cur) {
//如果当前结点的左指针未被线索化,就调第一个方法寻找
if (cur -> ltag == 0) {
return findFirstNode(cur -> lchild);
} else {//被线索化就直接返回
return cur -> lchild;
}
}
void inorder(ThrBiTreeNode *p) {
if (NULL == p) {
return;
}
for (ThrBiTreeNode * cur = findLastNode(p); cur != NULL; cur = findPreNode(cur)) {
visit(cur);
}
}
后序线索二叉树
1、创建
void createPostTree(ThrBiTree root) {
ThrBiTreeNode *pre = NULL;
if (NULL == root) {
return;
}
//通过递归的方式进行后序线索化
postThread(root, pre);
//如果递归完成最后一个结点的后继结点因为空
if (NULL == pre -> rchild) {
pre -> rtag = 1;
}
}
void postThread (ThrBiTree &cur, ThrBiTreeNode &pre) {
//递归出口
if (NULL == cur) {
return;
}
//左孩子的线索化
postThread (cur -> lchild, pre);
//右孩子的线索化
postThread(p -> rchild, pre);
//如果当前结点没有左孩子结点就将该指针线索化
if (NULL == cur -> lchild) {
cur -> lchild = pre;
cur -> ltag = 1;
}
//如果当前结点没有右孩子结点就线索化
if (NULL != pre && NULL != pre -> rchild) {
pre -> rchild = cur;
pre -> rtag = 1;
}
//记录当前结点的前驱结点,方便线索化处理
pre = p;
}
2、遍历
由于后序遍历(左右根)和二叉树本身的特点,所以从一棵树的根结点无法找到其后继,只能找到前驱,因此只能通过前驱结点逆序遍历树;
//寻找前驱结点,要么为左孩子要么为右孩子
ThrBiTreeNode *findPreNode(ThrBiTreeNode *p) {
//如果左孩子没有被线索化,左孩子被线索化后就是前驱了
//这个条件就是如果当前结点无法直接找到前驱应该怎么处理
if (0 == p -> ltag) {
//如果有右孩子,则右孩子为前驱
if (0 == p -> rtag && NULL != p -> rchild) {
return p -> rchild;
}
//如果没有右孩子却有左孩子则前驱为左孩子
if (NULL != p -> lchild) {
return p -> lchild;
}
} else {//被线索化了,直接返回
return p -> lchild;
}
}
//后续逆向遍历
void postOrderReverse(ThrBiTreeNode *p) {
if (NULL == p) {
return;
}
//当前结点最先被遍历,然后查找前驱结点依次遍历,为空就结束
for (ThrBiTreeNode *cur = p; cur != NULL; cur = findNextNode(cur)) {
visit(cur);
}
}