前序/中序/后序线索化二叉树
前言:线索二叉树的笔记
什么是线索二叉树
通过对应的线索二叉树,我们可以通过该树来观察到对应的结点的前驱结点和后继结点
需要注意的是,这里说的前驱和后继是针对序列而言的,就比如一个二叉树的前序序列为DAEFBCHGI,那么B结点的前驱结点指的就是F这个结点,B结点的后继结点指的就是C这个结点
如何实现线索二叉树
因为要实现线索二叉树,所以结点的结构体需要增加两个标志位,来说明该结点的左节点是指向的是正常的左子树结点还是当前结点的前驱树结点
typedef struct _TreeNode{
struct _TreeNode* pLeftTreeNode;
struct _TreeNode* pRightTreeNode;
ElemType iLeftFlag; //新增的左标识符
ElemType iRightFlag; //新增的右标识符
ElemType nodeData;
}TreeNode, *PTreeNode;
这里的话可以通过前序遍历来构造对应的线索二叉树,初始化一个全局变量pPreTreeNode在每次遍历完之后都记录当前遍历的结点,然后在下一个前序遍历的结点的时候来用到
什么时候用到?
比如pPreTreeNode = A结点,B结点是A结点的左叶子结点,而这个B结点是一个叶子结点,那么B结点的左右结点我们都需要将其线索化
B结点的左节点的线索化很简单,因为不管先/中/后序列,如果当前结点是一个线索结点,那么当前结点的前驱结点肯定就是pPreTreeNode (在这里的话肯定就是A结点)
有一个坑就是那你将这个B结点的左结点进行线索化前驱结点了之后,就不要想着把B结点的左结点也进行线索后继结点了,为什么?因为你都不知道后继结点是哪个,所以肯定也就无法进行线索化后继结点了,那么什么时候才能进行对B结点进行线索化后继结点的操作?当到了B结点的后一个遍历结点的时候,这时候pPreTreeNode结点就是B,那么此时的遍历的结点就是C,那么这个时候就可以将B结点的后继结点进行线索化了,其实也就是pPreTreeNode->pRightTreeNode = 当前结点的地址
其他的话就没什么大问题了...
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define OK 1
#define ERROR 0
#define MAXSIZE 100
typedef int ElemType;
typedef int Status;
typedef struct _TreeNode{
struct _TreeNode* pLeftTreeNode;
struct _TreeNode* pRightTreeNode;
ElemType iLeftFlag;
ElemType iRightFlag;
ElemType nodeData;
}TreeNode, *PTreeNode;
typedef struct _SqTree{
TreeNode* pRootTreeNode;
int iTreeSize;
}SqTree, *PSqTree;
// 链队列用于层次结点遍历
typedef struct _LinkNode{
TreeNode* pTreeNode;
struct _LinkNode* nextLinkNode;
}LinkNode, *PLinkNode;
typedef struct _LinkQueue{
LinkNode* pHeadLinkNode;
}LinkQueue, *PLinkQueue;
// 初始化链队列
Status initLinkQueue(LinkQueue* pLinkQueue)
{
if (pLinkQueue->pHeadLinkNode != NULL)
return ERROR;
// 正常初始化
pLinkQueue->pHeadLinkNode = malloc(sizeof(LinkNode));
memset(pLinkQueue->pHeadLinkNode, 0, sizeof(LinkNode));
return OK;
}
// 队列先进先出
Status push(LinkQueue* pLinkQueue, TreeNode* pTreeNode)
{
LinkNode* pLinkNode = NULL;
LinkNode* pTempLinkNode = NULL;
if (pLinkQueue->pHeadLinkNode == NULL)
return ERROR;
pTempLinkNode = pLinkQueue->pHeadLinkNode;
while (pTempLinkNode->nextLinkNode != NULL)
pTempLinkNode = pTempLinkNode->nextLinkNode;
pLinkNode = malloc(sizeof(LinkNode));
memset(pLinkNode, 0, sizeof(LinkNode));
pLinkNode->pTreeNode = pTreeNode;
if (pLinkNode == NULL)
return ERROR;
pTempLinkNode->nextLinkNode = pLinkNode;
return OK;
}
LinkNode* pop(LinkQueue* pLinkQueue)
{
LinkNode* pLinkNode = NULL;
if (pLinkQueue->pHeadLinkNode == NULL)
return ERROR;
pLinkNode = pLinkQueue->pHeadLinkNode->nextLinkNode; // 取出当前头节点之后的一个pLinkNode结点
pLinkQueue->pHeadLinkNode->nextLinkNode = pLinkQueue->pHeadLinkNode->nextLinkNode->nextLinkNode; // 将当前头结点指向下一个结点的下一个结点
return pLinkNode;
}
Status checkQueueEmpty(LinkQueue* pLinkQueue)
{
return pLinkQueue->pHeadLinkNode->nextLinkNode == NULL ? OK : ERROR;
}
// init root tree node
Status initRootTreeNode(SqTree* pSqTree)
{
if (pSqTree->pRootTreeNode != NULL)
return ERROR;
pSqTree->pRootTreeNode = malloc(sizeof(TreeNode));
memset(pSqTree->pRootTreeNode, 0, sizeof(TreeNode));
pSqTree->pRootTreeNode->nodeData = 1; //根节点的数据初始化为1
if (pSqTree->pRootTreeNode != NULL)
return OK;
else
return ERROR;
}
// 层序插入结点的实现
Status insertTreeNodeViaLayerTravel(SqTree* pSqTree, ElemType elem)
{
TreeNode* pInsertTreeNode = NULL;
LinkQueue linkQueue;
LinkNode* pLinkNode = NULL;
int iRightFlag = 0;
int iLeftFlag = 0;
printf("test travel for layer...\n");
memset(&linkQueue, 0, sizeof(LinkQueue));
initLinkQueue(&linkQueue);
// 压入当前的根结点作为起步操作
push(&linkQueue, pSqTree->pRootTreeNode);
// 层序遍历当前空结点的地方
while (!checkQueueEmpty(&linkQueue))
{
// 第一次的话就是弹出一个存储根节点的pLinkNode
pLinkNode = pop(&linkQueue);
printf("current get a node -> %d\n", pLinkNode->pTreeNode->nodeData);
// 如果当前的结点的左结点是为空的话,那么就直接break
if (pLinkNode->pTreeNode->pLeftTreeNode == NULL)
{
// 此时的话表示当前的结点的左结点是为空的,那么就要在当前的结点的左边插入一个结点
pInsertTreeNode = malloc(sizeof(TreeNode));
memset(pInsertTreeNode, 0, sizeof(TreeNode));
pInsertTreeNode->nodeData = elem;
pLinkNode->pTreeNode->pLeftTreeNode = pInsertTreeNode;
break;
}
else{
push(&linkQueue, pLinkNode->pTreeNode->pLeftTreeNode);
}
// 如果当前的结点的右结点是为空的话,同样也break
if (pLinkNode->pTreeNode->pRightTreeNode == NULL)
{
// 此时的话表示当前的结点的左结点是为空的,那么就要在当前的结点的右边插入一个结点
pInsertTreeNode = malloc(sizeof(TreeNode));
memset(pInsertTreeNode, 0, sizeof(TreeNode));
pInsertTreeNode->nodeData = elem;
pLinkNode->pTreeNode->pRightTreeNode = pInsertTreeNode;
break;
}
else{
push(&linkQueue, pLinkNode->pTreeNode->pRightTreeNode);
}
}
return OK;
}
// test for traveling treeNode
void travelPreTree(TreeNode* pTreeNode)
{
if (pTreeNode != NULL)
{
printf("current tree's data -> %d\n", pTreeNode->nodeData);
if (pTreeNode->iLeftFlag == 0)
{
travelPreTree(pTreeNode->pLeftTreeNode);
}
if (pTreeNode->iRightFlag == 0)
{
travelPreTree(pTreeNode->pRightTreeNode);
}
}
}
// 创建前序线索二叉树 -> 思路有问题
void createPreClueTree(TreeNode* pTreeNode, TreeNode* pPreTreeNode)
{
// 终止条件为当前结点为NULLr
if (pTreeNode != NULL)
{
if (pTreeNode->pLeftTreeNode == NULL)
{
pTreeNode->iLeftFlag = 1; // 说明当前结点的左孩子是指向当前结点的前驱结点
pTreeNode->pLeftTreeNode = pPreTreeNode;
}
if (pTreeNode->pRightTreeNode == NULL)
{
pTreeNode->iRightFlag = 1; // 说明当前结点的右孩子是指向当前父结点的右孩子
pTreeNode->pRightTreeNode = pPreTreeNode->pRightTreeNode;
}
if (pTreeNode->iLeftFlag == 0)
createPreClueTree(pTreeNode->pLeftTreeNode, pPreTreeNode);
if (pTreeNode->iRightFlag == 0)
createPreClueTree(pTreeNode->pRightTreeNode, pPreTreeNode);
}
}
TreeNode* g_pPreTreeNode = NULL; // 全局变量,用来保存当前访问的结点的上一个结点
void createPreClueTree2(TreeNode* pTreeNode)
{
// 终止条件为当前结点为NULL
if (pTreeNode != NULL)
{
// 如果当前结点的左子树的结点为空,说明当前结点的前驱线索的结点就是g_pPreTreeNode
if (pTreeNode->pLeftTreeNode == NULL)
{
pTreeNode->iLeftFlag = 1;
pTreeNode->pLeftTreeNode = g_pPreTreeNode;
}
// 如果当前结点的前驱结点的右子树为空的话,那么这里还需要修改当前结点的前驱结点的右子树的线索
if (g_pPreTreeNode != NULL && g_pPreTreeNode->pRightTreeNode == NULL)
{
g_pPreTreeNode->iRightFlag = 1; // 说明当前结点的左孩子是指向当前结点的前驱结点
g_pPreTreeNode->pRightTreeNode = pTreeNode;
}
g_pPreTreeNode = pTreeNode;
// 这里的判断为了防止前序遍历的时候无限循环,比如前序遍历顺序为ABC,到B结点的时候,B结点的左右结点都为NULL,那么首先就是左结点的线索处理
// 此时将B结点的左结点指向当前的前驱结点g_pPreTreeNode,然后就继续createPreClueTree2(pTreeNode->pLeftTreeNode)
// 但是此时的pTreeNode->pLeftTreeNode,也就是B的左结点,其实就是A结点,那么就会导致无限循环,所以这里通过iLeftFlag来进行判断,
// 总结下的话,其实就是当前节点如果是相关的线索结点的话(iLeftFlag=1 或者 iRightFlag=1),那么就不需要再去递归
if (pTreeNode->iLeftFlag == 0)
{
createPreClueTree2(pTreeNode->pLeftTreeNode);
}
if (pTreeNode->iRightFlag == 0)
{
createPreClueTree2(pTreeNode->pRightTreeNode);
}
}
}
int main()
{
SqTree sqTree;
int iSize;
printf("init tree node size -> ");
scanf("%d", &iSize);
// init treeRootNode
memset(&sqTree, 0, sizeof(SqTree));
initRootTreeNode(&sqTree); // data -> 1
// test insert tree node via layer travel
insertTreeNodeViaLayerTravel(&sqTree, 2);
insertTreeNodeViaLayerTravel(&sqTree, 3);
//insertTreeNodeViaLayerTravel(&sqTree, 4);
//insertTreeNodeViaLayerTravel(&sqTree, 5);
//insertTreeNodeViaLayerTravel(&sqTree, 6);
//insertTreeNodeViaLayerTravel(&sqTree, 7);
// create pre clue tree
printf("===============test create pre clue tree===============\n");
//g_pPreTreeNode = sqTree.pRootTreeNode;
//createPreClueTree(sqTree.pRootTreeNode, sqTree.pRootTreeNode);
createPreClueTree2(sqTree.pRootTreeNode);
g_pPreTreeNode->pRightTreeNode = NULL;
g_pPreTreeNode->iRightFlag = 1;
// test travel pre tree
printf("===============test travel pre tree===============\n");
travelPreTree(sqTree.pRootTreeNode);
// 这里创建完前序线索树还需要进行最后的特殊处理,因为这里的话前序遍历的话最后一个createPreClueTree2(pTreeNode->pLeftTreeNode) 此时已经为空了
// 导致最后一个结点的后继结点无法进行填充,但是又因为这个结点时最后一个结点,那么也就是这个结点的右子树也没必要去填充,其实就是一个NULL
// 而最后一个结点其实就是g_pPreTreeNode,那么只需要对g_pPreTreeNode->pRightTreeNode = NULL,g_pPreTreeNode->iRightFlag=1即可
return 0;
}
中序线索化
其实都是差不多的,这里就直接写自己写的代码了
// test for traveling treeNode
void travelMiddleTree(TreeNode* pTreeNode)
{
if (pTreeNode != NULL)
{
if (pTreeNode->iLeftFlag == 0)
{
travelMiddleTree(pTreeNode->pLeftTreeNode);
}
printf("current tree's data -> %d\n", pTreeNode->nodeData);
if (pTreeNode->iRightFlag == 0)
{
travelMiddleTree(pTreeNode->pRightTreeNode);
}
}
}
// 中序序列线索化二叉树
// 中序序列 -> 左根右
TreeNode* g_pMiddleTreeNode = NULL; // 全局变量,用来保存当前访问的结点的上一个结点
void createMiddleClueTree2(TreeNode* pTreeNode)
{
// 终止条件为当前结点为NULL
if (pTreeNode != NULL)
{
createMiddleClueTree2(pTreeNode->pLeftTreeNode);
// 如果当前结点的左子树的结点为空,说明当前结点的前驱线索的结点就是g_pPreTreeNode
if (pTreeNode->pLeftTreeNode == NULL)
{
pTreeNode->iLeftFlag = 1;
pTreeNode->pLeftTreeNode = g_pMiddleTreeNode;
}
// 如果当前结点的前驱结点的右子树为空的话,那么这里还需要修改当前结点的前驱结点的右子树的线索
if (g_pMiddleTreeNode != NULL && g_pMiddleTreeNode->pRightTreeNode == NULL)
{
g_pMiddleTreeNode->iRightFlag = 1; // 说明当前结点的左孩子是指向当前结点的前驱结点
g_pMiddleTreeNode->pRightTreeNode = pTreeNode;
}
g_pMiddleTreeNode = pTreeNode;
createMiddleClueTree2(pTreeNode->pRightTreeNode);
}
}