反思
今天本来准备写图论的笔记,可是仔细一想,线索二叉树我好像还不是很会。
其实我本来是不打算认真看线索二叉树的,一来是因为线索二叉树实在是长了一副不太能用到的样子(太难打啦,还麻烦),而来我也没有看出来线索二叉树有什么奇怪的功能(那个功能遍历一遍不就可以了吗)。
所以一直都搁置了。可是转眼一想,还是有点后悔,因为自己如果懒到连课本上有的内容都不学好,还怎么学习非课本上的拓展内容呢?自己连这点高标准都不留给自己,还怎么继续学习,考研谁敢要?
所以经过我的深思熟虑,我觉得还是仔细的弄一遍线索二叉树比较好。
线索二叉树的存储结构
具体应用:知道某个节点在某种遍历中的前驱和后继。
实现方法:对任意节点,若左指针域为空,则用左指针域存储该节点的前驱线索,若右指针域为空,则用该指针域存储该节点的后继线索。
节点的定义如下:
struct Node
{
char data;
int ltag,rtag;//标记,是否是存储的前驱后继,可设定为,为0存储孩子,为1存储前驱后继。
Node *l,*r;//存储左右孩子或者前驱后继
};
本文讲述中序线索二叉树,前序后序变化不大。
线索链表的实现
(1)构造函数
创建二叉树的步骤与普通的二叉树不同之处在于标记的初始化,这里统一将标记初始化为0。
这里的构造函数不仅仅有二叉树的创建,最重要的是二叉链表的线索化(线索化建立在二叉树已经建立完成的基础上)。
具体实现思想为:
- 函数设置形参root和全局变量pre,分别表示要处理的树的根结点和其前驱结点(因为同一个时间点前驱只能有一个,所以可以用全局变量,再说不用全局变量写不了啊)。
- 如果root==NULL,return;
- 如果root!=NULL,中序线索化root的左子树,中序线索化root本身
- 如果root->lchild==NULL 则root->left=pre,root->ltag=1;
- 如果root->rchild==NULL,root->rtag=1,如果pre!=NULL,并且pre->rtag=1,pre->rchild=root;//root是pre的后继
- pre=root
- 中序线索化root的右子树
void ThrBiTree::ThrBiTree (Node *root) {
if (root==NULL)
return; //递归结束条件
ThrBiTree(root->lchild);
if (!root->lchild){ //对root的左指针进行处理
root->ltag =1;
root->lchild = pre; //设置pre的前驱线索
}
if (!root->rchild)
root->rtag =1;
if(pre != NULL){
if (pre->rtag==1) pre->rchild = root;
}
pre = root;
ThrBiTree(root->rchild);
}
(2)中序遍历查找后继
分两种情况:
①节点p的右标记为1,则说明右指针是线索,他就是后继
②节点p的右标记为0,说明他有右孩子,不能直接找到其后继节点。
根据中序遍历的操作定义,它的后继节点应当是遍历其右子树时第一个节点->右子树最左下的节点
所以沿着其右孩子的左指针往下找,某个节点没有左孩子时(即ltag=0),就是所需的后继节点。
Node* InThrBiTree::Next(Node* p)
{
Node* q; //要查找的p的后继
if (p->rtag==1) q = p->rchild;
else{
q = p->rchild;
while (q->ltag==Child)
{
q = q->lchild;
}
}
return q;
}
(3)中序遍历
找到第一个节点,依次遍历后继即可。
void InThrBiTree::InOrder( Node *root){
Node * p = root;
if (root==NULL) return;
while (p->ltag==Child) {
p = p->lchild;
}
cout<<p->data<<" ";
while (p->rchild!=NULL) {
p = Next(p);
cout<<p->data<<" ";
}
cout<<endl;
}