一、全部代码
#include<stdio.h>
#include<stdlib.h>
#define MAXSIZE 100
//二叉树
typedef struct ThreadNode {
int data;
struct ThreadNode *lchild , *rchild;
int ltag,rtag; //1是线索
} ThreadNode, *ThreadTree;
ThreadNode *pre=NULL; //定义一个全局变量作为pre前序
/*===================下面是树的功能 ====================*/
/**
创建结点
*/
ThreadNode* createNode( int data) {
ThreadNode* newNode = (ThreadNode*)malloc(sizeof(ThreadNode));
newNode->data = data;
newNode->lchild = NULL;
newNode->rchild = NULL;
newNode->ltag = 0;
newNode->rtag = 0;
return newNode;
}
/*
访问结点
*/
void visit(ThreadNode* q) {
//最后一个结点的右孩子不会被线索化 需要在函数中单独处理
printf("%d ",q->data);
if(q->lchild == NULL) {
//q的左孩子为空 可以指向前驱
q->lchild = pre;
q->ltag = 1;
}
if(pre != NULL && pre->rchild == NULL) {
// pre != NULL 说明q结点存在前驱
// pre->rchild == NULL pre右孩子为空,故可以线索化指向后继
pre->rchild = q;
pre->rtag = 1;
}
pre = q;
}
/
/**
递归: 中序遍历 && 中序线索化
*/
void inAndCreateThreadTree(ThreadNode* tree) {
if(tree == NULL) return;
inAndCreateThreadTree(tree->lchild);
visit(tree); //放在这里是中序
inAndCreateThreadTree(tree->rchild);
return;
}
//创建中序线索化树
void createInThreadTree(ThreadNode *root){
if(root != NULL){
inAndCreateThreadTree(root);
}
pre->rtag = 1;
}
/
/**
递归: 先序遍历 && 线序线索化
*/
void preAndCreateThreadTree(ThreadNode *tree){
if(tree == NULL) return;
visit(tree);
//这里会有一个问题
//当 到达左子树为空的结点的时候
//它的左孩子会先被线索化,从而指向自己的前驱
//那么再进行递归的时候 将不会再因为它的左子树为空而结束递归
// 而2是顺着线索递归到它的父节点,然后由父节点再次递归回来 从而生成死循环
//故这里该加一个判断 : 该节点的左孩子未被线索化才可以进入递归(如果已被线索化 则不需要进入递归 )
if(tree->ltag == 0)preAndCreateThreadTree(tree->lchild);
//不同于王道视频 右孩子也要加判断 只判断左孩子的时候是因为 父节点右孩子不为空
//比如这样的场景:
// 5
// /
// 7
// 先序中会先访问5 然后通过5的左孩子访问7 (此时5的右孩子还没访问到)
// 访问7的时候 会将5(7的pre为5)的右孩子线索化指向7
// 那么当结点7处理完毕之后,会递归结束返回到5 ,然后继续访问5的右孩子
// **注意** 此时5的右孩子已被线索化指向了7 如果不加以判断 就会又指回7 从而发生死循环
if(tree->rtag == 0)preAndCreateThreadTree(tree->rchild);
return;
}
//先序线索化
void createPreThreadTree(ThreadNode *root){
pre = NULL; //先把pre置空一下 防止其它处使用过带有垃圾数据
preAndCreateThreadTree(root); //退出循环之后 全局变量pre就是最后一个结点
pre->rtag = 1;
}
/
/**
后续遍历 && 后续线索化
*/
void posAndCreateThreadTree(ThreadNode *root){
if(root == NULL) return;
if(root->ltag == 0) posAndCreateThreadTree(root->lchild);
if(root->rtag == 0) posAndCreateThreadTree(root->rchild);
visit(root);
}
void createPosThreadTree(ThreadNode *root){
if(root){
posAndCreateThreadTree(root);
}
root->rchild = NULL;
root->rtag = 1;
}
void printNode(ThreadNode* root){
printf("当前结点值: %d, 左是否线索化: %d , 右是否线索化: %d\n",root->data,root->ltag,root->rtag);
//如果前驱或者后继是NULL的话 不加判断为空就想打印该节点数据会报错(死循环)
if(root->ltag == 1) {
if(root->lchild == NULL) printf("该结点前驱为NULL \n");
else printf("该结点前驱为: %d\n",root->lchild->data);
}
if(root->rtag == 1) {
if(root->rchild == NULL) printf("该结点后继为NULL \n");
else printf("该结点后继为: %d\n",root->rchild->data);
}
printf("----\n");
}
//测试用代码 先序打印出每个结点的线索
void prePrintNode(ThreadNode* root){
if(root == NULL) return;
printNode(root);
if(root->ltag == 0)prePrintNode(root->lchild);
if(root->rtag == 0)prePrintNode(root->rchild);
}
//测试用代码 中序打印出每个结点的线索
void inPrintNode(ThreadNode* root){
if(root == NULL) return;
if(root->ltag == 0)prePrintNode(root->lchild);
printNode(root);
if(root->rtag == 0)prePrintNode(root->rchild);
}
//测试用代码 后序打印出每个结点的线索
void posPrintNode(ThreadNode* root){
if(root == NULL) return;
if(root->ltag == 0)posPrintNode(root->lchild);
if(root->rtag == 0)posPrintNode(root->rchild);
printNode(root);
}
/*==================树的功能在这里结束 ===================*/
int main() {
ThreadTree root = createNode(1); //root
//第二层
root->lchild = createNode(2);
root->rchild = createNode(3);
//第三层
root->lchild->lchild = createNode(4);
root->lchild->rchild = createNode(5);
root->rchild->lchild = createNode(6);
//第四层
root->lchild->rchild->lchild = createNode(7);
//想用哪个就删掉哪个的注释 否则会因为前面的已线索化产生冲突
// printf("先序线索化:\n");
// createPreThreadTree(root);
// printf("\n");
// prePrintNode(root);
//
printf("---------\n中序线索化\n");
createInThreadTree(root);
printf("\n");
inPrintNode(root);
//
// printf("---------\n后序线索化\n");
// createPosThreadTree(root);
// printf("\n");
// posPrintNode(root);
//
/*
1
/ \
2 3
/ \ /
4 5 6
/
7
*/
return 0;
}
二、关于先序线索化
2.1 王道视频中有讲解 先序线索化需要考虑子节点递归结束后时候会因为父节点的线索化而导致死循环,但是视频课中貌似忽略了一种情况,详情见下方代码。
/**
递归: 先序遍历 && 线序线索化
*/
void preAndCreateThreadTree(ThreadNode *tree){
if(tree == NULL) return;
visit(tree);
//这里会有一个问题
//当 到达左子树为空的结点的时候
//它的左孩子会先被线索化,从而指向自己的前驱
//那么再进行递归的时候 将不会再因为它的左子树为空而结束递归
// 而2是顺着线索递归到它的父节点,然后由父节点再次递归回来 从而生成死循环
//故这里该加一个判断 : 该节点的左孩子未被线索化才可以进入递归(如果已被线索化 则不需要进入递归 )
if(tree->ltag == 0)preAndCreateThreadTree(tree->lchild);
//不同于王道视频 右孩子也要加判断 只判断左孩子的时候是因为 父节点右孩子不为空
//比如这样的场景:
// 5
// /
// 7
// 先序中会先访问5 然后通过5的左孩子访问7 (此时5的右孩子还没访问到)
// 访问7的时候 会将5(7的pre为5)的右孩子线索化指向7
// 那么当结点7处理完毕之后,会递归结束返回到5 ,然后继续访问5的右孩子
// **注意** 此时5的右孩子已被线索化指向了7 如果不加以判断 就会又指回7 从而发生死循环
if(tree->rtag == 0)preAndCreateThreadTree(tree->rchild);
return;
}
//先序线索化
void createPreThreadTree(ThreadNode *root){
pre = NULL; //先把pre置空一下 防止其它处使用过带有垃圾数据
preAndCreateThreadTree(root); //退出循环之后 全局变量pre就是最后一个结点
pre->rtag = 1;
}
2.2 重点在于 preAndCreateThreadTree() 函数中的 遍历右子树代码,如果在这里不加线索化判断,会导致死循环
/*
1
/ \
2 3
/ \ /
4 5 6
/
7
*/
2.2.1这是不加判断
preAndCreateThreadTree(tree->rchild);
因为孩子结点递归之后 回到父节点再次通过线索回到孩子节点 从而死循环。
2.2.2 这是拥有判断条件
if(tree->rtag == 0)preAndCreateThreadTree(tree->rchild);
可以看到已经正常线索化