目录
前言
建立线索二叉树的思路差不多,这次主要是针对中序线索二叉树的基本操作,既可以找前驱,又可以找后继,最具有代表性。之后我会单独做关于树一章节的总结。
实验要求:
1.先序遍历创建二叉树;
2.中序线索化二叉树;
3.分别通过 找前驱 和 找后继 的方式,遍历中序线索二叉树。
代码实现和思路讲解
线索二叉树的定义:
我这里为了更加直观,将标志单独用枚举型独立出来了。自定义命名isClue:clue = 1,表示这个位置的指针是线索指针,child = 0,也有否的含义,表示不是线索指针,那就是孩子指针了。
typedef enum { clue = 1, child = 0 } isClue; //1:是线索,0:是孩子
typedef char Elemtype;
typedef struct btNode {
Elemtype data;
isClue lTag, rTag;
struct btNode *lChild;
struct btNode *rChild;
} TBTNode;
先序创建二叉树:
这个非常简单
TBTNode *PreCreateBTree(char *str, int *loca) {
TBTNode *T;
if (str[*loca] == '#') { //如果当前字符是'#',表示空树
(*loca)++; //移动到下一个字符
return NULL; //返回空指针
}
else { //否则,表示有数据
T = (TBTNode *)malloc(sizeof(TBTNode));
if (!T) {
exit(0);
}
T->data = str[(*loca)++];
T->lChild = PreCreateBTree(str, loca); //递归创建左子树
T->rChild = PreCreateBTree(str, loca); //递归创建右子树
return T; //返回根结点指针
}
}
创建带头结点的中序线索化二叉树:
分为两个步骤:一、创建头结点完善线索化的二叉链表。二、将原本的二叉链表线索化。
中序线索化,我们自然是用中序遍历,直接格式:递归左子树,访问根节点,递归右子树。于是我们只需要对一个结点负责,如果左子树存在,标志为child,不存在就线索化。全局变量pre指向遍历过程中的前驱结点,在线索化二叉链表之前已经初始化为头结点了。
如果二叉树是空树,头结点就头尾都指向自己。否则就左标志为child,链接根节点,右标志为clue链接最右,即中序遍历最后一个元素。
//中序线索化二叉树
TBTNode *pre = NULL; //定义一个全局变量pre
void InThread(TBTNode *T) {
if (T) {
InThread(T->lChild); //递归遍历左子树
if (!T->lChild) {
T->lTag = clue; //将lTag设为clue
T->lChild = pre; //将lChild指向pre
}
else T->lTag = child; //左孩子存在
if (!pre->rChild) {
pre->rTag = clue; //将pre的rTag设为clue
pre->rChild = T; //将pre的rChild指向当前结点
}
else pre->rTag = child;
pre = T; //更新pre为当前结点
InThread(T->rChild); //递归遍历右子树
}
}
//创建中序线索化二叉树
TBTNode *CreateInThread(TBTNode *T) {
pre = NULL;
TBTNode *head = (TBTNode *)malloc(sizeof(TBTNode));
if (!head) {
exit(0);
}
head->lTag = child; //头结点的lTag为chile
head->rTag = clue; //头结点的rTag为clue
head->rChild = head; //头结点的rChild指向自己
if (!T) { //如果二叉树为空
head->lChild = head; //头结点的lChild也指向自己
}
else {
head->lChild = T; //头结点的lChild指向原来的根结点
pre = head;
InThread(T); //中序线索化二叉树
pre->rChild = head; //处理最后一个结点,将其rChild设为头结点
pre->rTag = clue; //将其rTag设为clue
head->rChild = pre; //将头结点的rChild指向最后一个结点
}
return head;
}
找前驱和找后继两种方式遍历线索化中序二叉树:
中序遍历的结点前驱是左子树的最右结点,后继是右子树的最左结点。
找前驱开始结点为最右结点,因为是最后一个中序遍历结点。可以通过头结点的右指针快速访问到并输出。然后就是一直找前驱,左指针是线索指针可以直接访问,如果不是就找左孩子的最右子树。
找后继则要先访问根节点,通过循环找到最左子树,因为是第一个中序遍历结点。然后就是和上面思路一样,一直找后继,右线索指针就直接访问,没有就找右子树的最左子树。
所以两种遍历方法的代码几乎一模一样:
//找前驱的方式遍历中序线索二叉树
void InOrderPre(TBTNode *head) {
TBTNode *p = head->rChild; //指向最右子树
while (p != head) {
while (!p->rTag) { //右指针不是线索,即是孩子
p = p->rChild; //左子树最右孩子是前驱
}
printf("%c ", p->data);
while (p->lTag && p->lChild != head) { //左指针是线索且不是头结点
p = p->lChild;
printf("%c ", p->data);
}
p = p->lChild;
}
}
// 找后继的方式遍历中序线索二叉树
void InOrderNext(TBTNode *head) {
TBTNode *p = head->lChild;
while (p != head) {
while (!p->lTag) { //左指针不是线索,即是孩子
p = p->lChild; //右子树最左孩子
}
printf("%c ", p->data);
while (p->rTag && p->rChild != head) { //右指针是线索且不是头结点
p = p->rChild;
printf("%c ", p->data);
}
p = p->rChild;
}
}
结果测试
中序线索二叉树图:
红线为后继,黄线为前驱:
测试结果:
总结
通过找前驱的方式遍历中序线索二叉树,结果会和中序遍历相反,可以通过栈等方式逆序一下就可以了。
主要需要明确思路,找后继遍历就得找到第一个元素,最左元素,然后依次找后继,有线索找线索,没线索找右子树的最左端。找前驱就得找最后一个元素,最右元素,然后依次找前驱,有线索访问线索,没有线索访问左子树的最右子树。
线索化过程没有什么难度,就是中序遍历,将中间的print方法替换成我们想要的方法,需要注意的就是用一个pre指针,备份访问过的前驱结点,最后注意将头结点的线索化补充完整。