线索二叉树
树结点的结构
下面是代码的实现:
树的结构发生了变化
struct TreadedBiNode
{
QString ch;
TreadedBiNode * lchild;//指向右孩子
int ltag;//ltag为0时指向该结点的左孩子,为1时指向该结点的前驱
TreadedBiNode * rchild;//指向左孩子
int rtag;
};
线索化
线索化最合适的还是在遍历过程中进行前后驱的赋值比较合适
整理一下线索化过程的思路,一个结点的前驱:指向自己的左子树,后驱指向自己的右子树
按照遍历的方式不同,代码不同
中序遍历,先遍历左子树,再遍历根,再到右子树,
整个过程需要增加一个指针,指向刚才访问过的结点,也就是根的左子树,在访问到根的时候,让根的前驱为左子树
在访问到右子树的时候,刚才访问过的结点就是根,此时再让右子树的前驱指向根。
拿一个最简单的树作为例子:
线索化的操作:该结点左子树空余,将其指向前驱,右子树空余,指向后驱。
中序遍历,递归过程中最先到达的结点一点是H,此时的pre(指向最先访问的结点的指针)还是nullptr初始状态。
此时,可以给H的前驱赋值了,指向pre,鉴于pre指向空,不进行操作,此时更新pre指针,让其指向H
然后随着遍历的发生,回到了根D处,D有左子树,所以不进行操作,而H没有右子树,此时pre又指向H,所以让H的右子树做后驱指向D。同时更新pre,让其指向D。
然后随着遍历的发生,回到了I处,pre此时指向D,D有右子树,所以不进行操作,I没有左子树,所以指向前驱D(此时由pre代替),I没有右子树,后驱本身就是nullptr,结束遍历即可。
整个过程中有两个指针,一个是指向该结点本身的函数参数,一个是指向上一个访问过结点的结点指针。
综上,还是一个递归调用的过程,过程中需要一个pre指针指向前一个访问到的结点,之后判断目前结点的左子树和上一个结点的右子树情况,再次进行各项赋值操作,具体代码如下:
void InTreaded(TreadedBiNode * root)
{
if(nullptr==root)
{
return;
}
else
{
InTreaded(root->lchild);
if(root->lchild == nullptr)//左子树为空,可以放置前驱
{
root->ltag = 1;
root->lchild = pre;
}
if(nullptr != pre)
{
if(pre->rchild == nullptr)//判断上一个访问的结点的右子树是不是空,是空的话,防止后驱
{
pre->rtag = 1;
pre->rchild = root;
}
}
pre = root;
InTreaded(root->rchild);
}
}
从这儿也能看出,创建树最好的遍历方式是先序,从根出发,输入字符串和匹配代码最合适
线索化也是,最好是使用中序遍历,因为需要两个指针一起协调工作,结构和原理都类似链表,所以从左到右或者从右到左是最合适的方式,从中间到两边就不那么方便了。
增加头指针
完成线索化后,模仿双向链表,给一个头指针,如下图(代码比较简单不在说明)
void InitHeader(TreadedBiNode * header,TreadedBiNode * Root)
{
if(nullptr == header||nullptr == Root)
return;
//①左子树最左侧的叶子结点的前驱指针,指向头指针,②头指针的前驱指向根,③头结点的后驱指针指向右子树最右侧的叶子结点,④右子树最右侧的叶子结点的后驱,指向头指针
header->lchild = Root;//②头指针的前驱指向根
header->ltag = 0;
TreadedBiNode * pCurrent = Root;
while(pCurrent->ltag != 1)
pCurrent = pCurrent->lchild;
pCurrent->lchild = header;//①左子树最左侧的叶子结点的前驱指针
pCurrent = Root;
while(pCurrent->rtag != 1)
pCurrent = pCurrent->rchild;
pCurrent->rchild = header;//④右子树最右侧的叶子结点的后驱,指向头指针
pCurrent->rtag = 1;
header->rchild = pCurrent;//③头结点的后驱指针指向右子树最右侧的叶子结点
}
具体代码执行情况
进行实际操作,以上述的树为例:
使用先序遍历法创建树,输入
char * InPut = "ABDH##I##EJ###CF##G##";
TreadedBiNode * MyRoot = CreateTree(InPut);
创建的时候,直接把lflag和tflag全部初始化。创建接口如下:
TreadedBiNode * CreateTree(char * leaf)
{
if(nullptr == leaf)
return nullptr;
if(position>( (int)strlen(leaf)-1)||strlen(leaf) == 0)
return nullptr;
TreadedBiNode * root = new TreadedBiNode;
if(leaf[position] == '#')
{
root = nullptr;
position++;
return root;
}
else
{
root->ch = leaf[position];
position++;
root->lchild = CreateTree(leaf);
if(root->lchild == nullptr)
root->ltag = 1;//可以放前驱
else
root->ltag = 0;//有左孩子
root->rchild = CreateTree(leaf);
if(root->lchild == nullptr)
root->rtag = 1;//可以放前驱
else
root->rtag = 0;//右故事
return root;
}
}
创建完成之后进行线索化和增加头指针
InTreaded(MyRoot);//线索化
TreadedBiNode * MyHeader = new TreadedBiNode;//做一个头指针
MyHeader->lchild = nullptr;
MyHeader->rchild = nullptr;
MyHeader->ltag = 1;
MyHeader->rtag = 1;
InitHeader(MyHeader,MyRoot);//连接头指针
完成后,传递头指针即可完成顺序和逆序的遍历
遍历
先看一下遍历的操作和结果:
Foreach(MyHeader);
qDebug()<<"反向打印";
Anti_Foreach(MyHeader);
输出:
遍历程序如下:
//遍历
void Foreach(TreadedBiNode * header)
{
if(nullptr == header)
return;
TreadedBiNode * pCurrent = header->lchild;//第一个元素——根
while(pCurrent != header)
{
while(pCurrent->ltag != 1)
pCurrent = pCurrent->lchild;
qDebug()<<pCurrent->ch;//链表首地址
while(pCurrent->rtag == 1&&pCurrent->rchild!=header)//调用后续指针
{
pCurrent = pCurrent->rchild;//
qDebug()<<pCurrent->ch;
}
pCurrent = pCurrent->rchild;
}
}
整个过程中都是以上图作为基本遍历思路,顺序则是按照中序遍历进行打印,逆序就是反中序打印
- 首先找到最左侧的左叶子结点(H),也是中序的开始,打印
- 查看后驱,指向根,打印
- 根有右孩子,指向右孩子,没有,指向后驱,操作都是一样的 ( pCurrent = pCurrent->rchild;
完成3操作后,左叶子结点和根都打印了,右侧是否是叶子结点无法判断,也有可能是新的根,所以重复以上操作,找到右孩子结点的左叶子结点,如果有重复输出即可,没有,自己就是右叶子结点,输出,然后重复2,查看后驱,打印即可。
整个过程就是一个从左向右的打印过程,指针不断被更新指向,逆向打印是一样的,先到的最右侧的右叶子结点(G)
从右向左打印,刚才是右侧结点无法判断,需要循环判读,此处就变成了左侧结点无法判断。
逆序遍历
void Anti_Foreach(TreadedBiNode * header)
{
if(nullptr == header)
return;
TreadedBiNode * pCurrent = header->lchild;//第一个元素——根
while(pCurrent != header)
{
while(pCurrent->rtag != 1)
pCurrent = pCurrent->rchild;//到达最右侧
qDebug()<<pCurrent->ch;//链表首地址
while(pCurrent->ltag == 1&&pCurrent->lchild!=header)//调用后续指针
{
pCurrent = pCurrent->lchild;//
qDebug()<<pCurrent->ch;
}
pCurrent = pCurrent->lchild;
}
}