/*----------------------------------------------
1、线索二叉树:利用二叉树链表结构中的空链域
存放该结点在某种遍历下的直接前驱或者直接后继.
线索化过程就是在遍历过程中修改空指针域.
2、树的结点有5个元素,ltag、rtag为0则lchild、
rchild指向子树,为1,则lchild指向前驱,rchild
指向后继.
3、求指定结点的前驱和后继
----------------------------------------------*/
/*----------------------------------------
树结构 : A
Bl Cr
El Fr Gl
Hl Il Jr Kr
先序:A B E H F I J C G K
中序:H E B I F J A G K C
后序:H E I J F B K G C A
-----------------------------------------*/
#include<stdio.h>
#include<malloc.h>
//-------------------------数据结构----------------------------------------------
//二叉树结点的定义
typedef char ElemType;
typedef struct Node
{
ElemType data;
char ltag;
char rtag;
struct Node* lchild;
struct Node* rchild;
//本题为二叉链
}node,*pnode;
//定义顺序堆栈结构
#define LEN 15
typedef struct
{
pnode p[LEN];
int max;
int top;
}stack,*pstack;
pnode ptemp = NULL; //temp为全局变量,储存遍历时当前节点的前驱节点的指针
//-----------------------------------------------------------------------------------
//--------------------------函数声明-------------------------------------------------
int iNodeNum = 0; //遍历时记录结点数
pnode Create_BT(ElemType );
pnode Insert_Lchild(pnode ,ElemType );
pnode Insert_Rchild(pnode ,ElemType );
void TLR_Thread(pnode );
pstack InitStack(pstack );
void Push(pstack ,pnode );
pnode Pop(pstack );
void TLR_ThreadRecur(pnode );
pnode HeaderThreadRecur(pnode );
pnode FindSuc(pnode ,pnode );
pnode FindPre(pnode ,pnode );
//-------------------------------------------------------------------------------------
//---------------------------堆栈算法--------------------------------------------------
//初始化堆栈
pstack InitStack(pstack sp)
{
int i;
sp = (pstack)malloc(sizeof(stack));
for(i=0;i<LEN;i++)
{
sp->p[i] = NULL;
}
sp->max = LEN-1;
sp->top = -1;
return sp;
}
//入栈
void Push(pstack sp,pnode x)
{
if(sp->top == sp->max)
{
printf("error!the stack is full!\n");
return;
}
else
{
sp->top++;
sp->p[sp->top] = x;
}
}
//出栈
pnode Pop(pstack sp)
{
if(sp->top == -1)
{
printf("error!the stack is empty!\n");
return NULL;
}
else
{
return sp->p[sp->top--];
}
}
//---------------------------------------------------------------------------------------
//------------------------------二叉树算法-----------------------------------------------
//新建一棵只有根节点的二叉树,返回树的起始结点指针
pnode Create_BT(ElemType x)
{
pnode ptr,pBT;
ptr = (pnode)malloc(sizeof(node));
pBT = ptr;
ptr->data = x;
ptr->ltag = 0;
ptr->rtag = 0;
ptr->lchild = NULL;
ptr->rchild = NULL;
return pBT;
}
//在当前父节点parent的左孩子插入一个数据位x的新结点,返回新建结点
pnode Insert_Lchild(pnode p,ElemType x)
{
pnode ptr;
if(p == NULL)
{
printf("无法插入新结点!父节点错误!\n");
return NULL;
}
ptr = (pnode)malloc(sizeof(node));
ptr->data = x;
ptr->ltag = 0;
ptr->rtag = 0;
ptr->lchild = NULL;
ptr->rchild = NULL;
if(p->lchild == NULL)
{
p->lchild = ptr;
}
else
{
//若父节点已有左子树,则新建结点插入父节点和左树之间
ptr->lchild = p->lchild;
p->lchild = ptr;
}
return ptr;
}
//在当前父节点parent的右孩子插入一个数据位x的新结点,返回新建结点
pnode Insert_Rchild(pnode p,ElemType x)
{
pnode ptr;
if(p == NULL)
{
printf("无法插入新结点!父节点错误!\n");
return NULL;
}
ptr = (pnode)malloc(sizeof(node));
ptr->data = x;
ptr->ltag = 0;
ptr->rtag = 0;
ptr->lchild = NULL;
ptr->rchild = NULL;
if(p->rchild == NULL)
{
p->rchild = ptr;
}
else
{
//若父节点已有左子树,则新建结点插入父节点和左树之间
ptr->rchild = p->rchild;
p->rchild = ptr;
}
return ptr;
}
//线索化递归算法
pnode HeaderThreadRecur(pnode BT)
{
pnode head;
head = (pnode)malloc(sizeof(node));
head->ltag = 0;
head->rtag = 1;
head->rchild = head;
if(BT==NULL)
{
head->lchild = head;
}
else
{
head->lchild = BT;
head->rchild = BT; //头结点的后继在不同的遍历方法不同,先序遍历时,头结点后继为BT,即二叉树的起点
ptemp = head;
TLR_ThreadRecur(BT);
//因为TLR_ThreadRecur函数处理右结点的时候处理的是ptemp即前一个结点,所以最后要补充最后一个结点的处理
ptemp->rchild = head;
}
return head;
}
void TLR_ThreadRecur(pnode BT)
{
pnode temp_l;
if(BT != NULL)
{
//printf("%3c",BT->data); //访问结点(输出)
temp_l = BT->lchild;
if(BT->lchild == NULL)
{
BT->ltag = 1;
BT->lchild = ptemp; //可见第一个访问的结点前驱ptemp的初值
}
if(BT->rchild == NULL) //每一次指向后继的操作,实际上是对上一个结点ptemp操作
{
BT->rtag = 1;
}
if(ptemp->rtag == 1)
{
ptemp->rchild = BT;
}
ptemp = BT;
TLR_ThreadRecur(temp_l); //BT->lchild重新赋值过,所以临时变量存储原来的左指针。中序遍历就不需要了。
TLR_ThreadRecur(BT->rchild);
}
}
//指定线索化二叉树某结点sp,寻找其前驱指针
pnode FindPre(pnode BT,pnode sp)
{
if(sp->ltag == 1)
{
return sp->lchild;
}
else
{
printf("该节点有左孩子,所以无法直接找到该结点的前驱!\n");
return NULL;
//则找该结点的父节点
//**在某毕业设计上看到说,该问题需要知道知道结点双亲信息,需要用到栈,所以先序和后序线索二叉树是不完整的
//通常建立中序线索二叉树
}
}
//指定线索化二叉树某结点sp,寻找其后继指针
pnode FindSuc(pnode BT,pnode sp)
{
if(sp->rtag == 1)
{
return sp->rchild;
}
else
{
if(sp->ltag == 1) return sp->rchild;
else return sp->lchild;
}
}
/***********************************未实现************************************
//线索化非递归算法
void TLR_ThreadStack(pnode BT)
{
pstack pStack = NULL;
pnode ptr;
iNodeNum = 0;
ptr = BT;
pStack = InitStack(pStack); //初始化堆栈
Push(pStack,NULL); //压入一个空元素,为下列while设立退出循环值
while(pStack->top!=-1)
{
while(ptr != NULL)
{
printf("%3c",ptr->data);
iNodeNum++;
if(ptr->rchild != NULL)
{
Push(pStack,ptr->rchild);
}
ptr = ptr->lchild;
}
if(pStack->top != -1)
{
ptr = Pop(pStack);
}
}
printf("\n先序非递归遍历结点数为:%d\n",iNodeNum);
}
***************************************************************/
//-------------------------------------------------------------------------------------------
//---------------------------------------主函数-----------------------------------------
//先建立树,然后开始遍历,遍历过程中修改空指针域。
int main()
{
pnode BT,ptr,qtr,Head;
//初始化树
BT = Create_BT('A');
qtr = Insert_Lchild(BT,'B'); //左树
ptr = Insert_Lchild(qtr,'E');
ptr = Insert_Lchild(ptr,'H');
qtr = Insert_Rchild(qtr,'F');
ptr = Insert_Lchild(qtr,'I');
ptr = Insert_Rchild(qtr,'J');
qtr = Insert_Rchild(BT,'C'); //右树
qtr = Insert_Lchild(qtr,'G');
qtr = Insert_Rchild(qtr,'K');
qtr = BT; //指针用完回原位
ptr = BT;
//开始遍历,线索化
Head = HeaderThreadRecur(BT);
//查找某结点的前驱和后继
qtr = FindPre(BT,BT->rchild);
ptr = FindSuc(BT,BT->rchild);
printf("%3c%3c",qtr->data,ptr->data);
return 0;
}
//-------------------------------------------------------------------------------
本次代码为半成品,在编写过程中有些许问题未能完成,同时也有了些新的感悟,如下:
1、二叉树的线索化我选择了先序递归遍历为例(前过程可能有些问题,虽然编译无误,但是运行未能得到期望结果),未能完成先序遍历的非递归的探索。
同时,还有四种实现方法未能编写(中序递归、后序递归、中序非递归、后序非递归)。
2、线索化后寻找任意结点的前驱和后继算法未能完成,因为我采用了先序遍历线索化,导致寻找结点前驱时不能直接找到,需要用到堆栈存储结点的双亲信息,同样的问题会出现在后序遍历线索化的后继寻找算法中,因为先序和后序线索二叉树都是不完整的线索树。
3、通过上述过程,我发现我一开始就以先序遍历线索化的探索实际是舍本求末的,因为二叉树线索化通常只会建立中序,以及在中序线索化二叉树的基础上查找前驱和后继,但是我的学习过程中总是希望找到普遍的规律,希望一次就能吃下一个大胖子,不禁让我反思有些文章中说道的学习编程有的时候需要不求甚解或者适当的略过一些问题,抓住主要部分,之后长时间实践之后,某些问题会自然而然迎刃而解。如线索化二叉树可以直接略过先序和后序两种结构知识,紧抓中序及其算法,但是在我的学习中似乎有点太过在意点点滴滴,不愿流过每一粒沙。
4、今天偶然看到一篇文章说道sizeof的使用,sizeof是运算符而不是函数的说法,sizeof(++i)中表达式++i不会以参数形式进行自加运算的,这一点我第一次听说,顿时感觉到编译原理、函数机制方面的知识匮乏。
5、说说我的大学,我表示,机械再见,今生无缘。大学期间除了体育、实验之类的,其他所有课程,只有两本数学,和两门电子类专业课程成绩超过90,虽然因为不努力错过了很多,但是真的对于专业的欲望也是很大一个因素,我喜欢的课程电子、电气控制之类的方向成绩都还好,但是所有的机械课程完全无所收获。照着自己心的方向飞吧,加油!虽然还要补考两门机械的不知所云的课程,还有本学期我最讨厌的液压方面的系统设计,不过还好最后一学期的毕业设计是我喜欢的,单片机方向,也算是电子软件、嵌入式的门槛吧。感觉大学的专业选择是我今生一个很大的遗憾,不过最重要的是,知道现在我想要什么,而不是这几年大学里连梦想都没有的状态。