线索化二叉树产生的背景
- 链式二叉树结构
从上图中我们能够看到叶子节点H、I、J、F、G都没有左右孩子,节点E没有右孩子,这样无形中浪费了链式的存储的空间
线索化二叉树的好处:
- 节省链式存储的空间
- 增加二叉树遍历效率。遍历二叉树时我们难免使用递归遍历二叉树,当我们线索化二叉树后遍历二叉树将不用递归就能完成,本文下面会讲。
如何线索化二叉树
合理利用节点没有左右孩子的指针地址
- 给没有左右孩子节点添加前驱后继
- 左孩子为NULL则左孩子指向前驱,右孩子为NULL则右孩子指向后继
- 需要注意的按照某一种遍历顺序的第一个节点左孩子为NULL时,左孩子应指向NULL;最后一个节点的右孩子为NULL时,右孩子应指向NULL
如图所示,按照中序遍历,H节点为第一个节点,它的左孩子应指向NULL;G节点为最后一个节点,它的右孩子应指向NULL
所有的前驱和后继都需要按照某一种遍历的次序逻辑,本文采用中序遍历
思考:如何区分一个节点的左孩子指针指向左孩子还是前驱呢?
- 此时我们需要给节点引入标识符用来标识是指向左孩子还是前驱
- 同理也要有标识符来标识指向右孩子还是后继
如上图所示,ltag用来标识指向左孩子还是前驱,rtag用来标识指向右孩子还是后继
线索化二叉树的实现
先遍历二叉树在遍历时设置前驱后继
- 只有在遍历的时候才能知道该节点是否有左右孩子,才能设置标识符、设置前驱后继
- 线索化二叉树的代码实现
typedef enum {
Link, //节点ltag == Link表示指向左孩子,节点rtag == Link表示指向右孩子
Thread //节点ltag == Thread表示指向前驱,节点rtag == Thread表示指向后继
}PointerTag;
typedef struct BiNode{
CElemType data;
PointerTag lTag;
PointerTag rTag;
struct BiNode *leftChild,*rightChild;
}BiNode,*BiTree;
#pragma mark - 构造二叉树
Status InitBiTree(BiTree *T){
*T = NULL;
return OK;
}
Status CreatBiTree(BiTree *T){
CElemType data = str[indexs++];
if (data == '#') {//空节点
*T = NULL;
}else{
*T = (BiTree)malloc(sizeof(BiNode));
if (!*T) {
return ERROR;
}
(*T)->data = data;
//生成左子树
CreatBiTree(&(*T)->leftChild);
if ((*T)->leftChild) {//如果左子树存在
(*T)->lTag = Link;
}
//生成右子树
CreatBiTree(&(*T)->rightChild);
if ((*T)->rightChild) {//如果右子树存在
(*T)->rTag = Link;
}
}
return OK;
}
#pragma mark - 遍历二叉树
//全局变量,始终指向刚刚访问过得节点
BiTree pre;
//采用中序遍历二叉树
void MideTraversalBiTree(BiTree T){
if (T) {
MideTraversalBiTree(T->leftChild);
printf("%c ",T->data);
if (T->leftChild) {//左子树存在
T->lTag = Link;
}else{//左子树不存在,指向前驱
T->lTag = Thread;
T->leftChild = pre;
}
//设置后继或者右孩子
if (pre->rightChild) {//前驱右子树存在
pre->rTag = Link;
}else{//前驱右子树不存在,指向后继
pre->rTag = Thread;
//前驱右孩子指针指向后继(当前结点T)
pre->rightChild = T;
}
//保持pre指向当前节点T
pre = T;
//继续递归遍历
MideTraversalBiTree(T->rightChild);
}
}
线索化二叉树的双向链表结构
线索化的二叉树在遍历的时候其实相当于操作一个双向链表结构,因此我们可以给线索化的二叉树添加一个头结点,如下图所示
- 构造带有头结点的线索化二叉树
Status MidHeadNodeBiTree(BiTree *H,BiTree T){ //创建头结点 *H = (BiTree)malloc(sizeof(BiNode)); if (!*H) {//创建头结点失败 return ERROR; } //设置ltag为指向左孩子 (*H)->lTag = Link; //设置rtag指向后继 (*H)->rTag = Thread; //设置头结点的后继为自己 (*H)->rightChild = *H; if (!T) {//如果二叉树不存在,则头结点的左孩子指向自己 (*H)->leftChild = (*H); }else{//二叉树存在 //头结点的左孩子指向二叉树T (*H)->leftChild = T; //刚刚访问的变量指向头结点 pre = *H; //中序遍历线索化二叉树 MideTraversalBiTree(T); //最后一个节点的右孩子指向头结点 pre->rightChild = *H; //最后一个节点线索化 pre->rTag = Thread; // (*H)->rightChild = pre; } return OK; } - 遍历带有头结点的线索化后的二叉树
void MidTraversalNodeBiTree(BiTree T){ if (T) { BiTree p = T->leftChild; while (p != T) { //找到第一个节点 while (p->lTag == Link) { p = p->leftChild; } printf("%c ",p->data); while (p->rTag == Thread && p->rightChild != T) { p = p->rightChild; printf("%c ",p->data); } p = p->rightChild; } } }