什么是线索二叉树?
遍历二叉树是以一定的规则将二叉树中的结点排列成一个线性序列,从而等到二叉树的各种遍历序列,其实质是对一个非线性操作进行线性化操作,使这个访问序列中的每一个结点(去除第一个和最后一个)都有一个之直接前驱和直接后继。
我们发现在二叉链表表示的二叉树中存在大量的空指针,若利用这些空指针存放指向其直接前驱和直接后继的节点,那么可以更方便的运用某些二叉树操作算法。
为什么要引入线索二叉树?
为了加快查找结点前驱和后继的访问速度。
怎么进行线索化?
规定:若无左子树,令lchild指向其前驱结点;若无右子树,令rchild指向其后继结点。这样的话,需要增加两个标志域表明当前指针域所指对象是指向左(右)孩子结点还是指向直接前驱(后继)。
线索二叉树的结点结构
从怎么进行线索化中估计大家能看出来。
l t a g ltag ltag | l c h i l d lchild lchild | d a t a data data | r c h i l d rchild rchild | r t a g rtag rtag |
---|
其中,标志域的含义如下:
l
t
a
g
{
0
,
lchild域指向结点的左孩子
1
,
lchild域指向结点的前驱
r
t
a
g
{
0
,
rchild域指向结点的左孩子
1
,
rchild域指向结点的后继
ltag \begin{cases} 0, & \text{lchild域指向结点的左孩子} \\ 1, & \text{lchild域指向结点的前驱} \end{cases} rtag \begin{cases} 0, &\text{rchild域指向结点的左孩子}\\ 1, &\text{rchild域指向结点的后继} \end{cases}
ltag{0,1,lchild域指向结点的左孩子lchild域指向结点的前驱rtag{0,1,rchild域指向结点的左孩子rchild域指向结点的后继
相关代码
1. 先序线索二叉树
#include <iostream>
#include <string>
using namespace std;
typedef char ElemType;
typedef struct ThreadNode
{
ElemType data;
struct ThreadNode *lchild, *rchild;
int ltag, rtag;
ThreadNode(){ ltag = 0, rtag = 0; };
}ThreadNode, *ThreadTree;
// 根据先序遍历序列和中序遍历序列建树
ThreadTree createTree(string s1, string s2)
{
if (s1 == "" || s2 == "") return NULL;
ThreadTree tree = new ThreadNode();
tree->data = s1[0];
int m = 0;
while (m < s2.size() && s2[m] != s1[0]) m++;
tree->lchild = createTree(string(s1.begin() + 1, s1.begin() + 1 + m), string(s2.begin(), s2.begin() + m));
tree->rchild = createTree(string(s1.begin() + 1 + m, s1.end()), string(s2.begin() + m + 1, s2.end()));
return tree;
}
// 遍历结点
void visit(ThreadTree p)
{
cout << p->data << " ";
return;
}
// 递归创建先序线索二叉树
void FirstThread(ThreadTree &tree, ThreadTree &pre)
{
if (tree)
{
if (!tree->lchild){
tree->lchild = pre;
tree->ltag = 1;
}
if (pre && !pre->rchild)
{
pre->rchild = tree;
pre->rtag = 1;
}
pre = tree;
if(!tree->ltag) FirstThread(tree->lchild, pre);
if(!tree->rtag) FirstThread(tree->rchild, pre);
}
return;
}
// 创建先序线索二叉树主程序
void createFristOrder(ThreadTree &tree)
{
if (!tree) return;
ThreadTree pre = NULL;
FirstThread(tree, pre);
pre->rchild = NULL;
pre->rtag = 1;
return;
}
// 查找先序线索二叉树指定结点的下一个结点
ThreadTree nextNode(ThreadTree p)
{
if (p->ltag) return p->rchild;
return p->lchild;
}
// 先序遍历 先序线索二叉树
void firstThreadOrder(ThreadTree tree)
{
if (!tree) return;
for (ThreadTree it = tree; it; it = nextNode(it))
{
visit(it);
}
cout << endl;
return;
}
int main()
{
ThreadTree tree = createTree("abdce", "bdaec");
createFristOrder(tree);
firstThreadOrder(tree);
return 0;
}
2. 中序线索二叉树
#include <iostream>
#include <string>
using namespace std;
typedef char ElemType;
typedef struct ThreadNode
{
ElemType data;
struct ThreadNode *lchild, *rchild;
int ltag, rtag;
ThreadNode(){ ltag = 0, rtag = 0; };
}ThreadNode, *ThreadTree;
// 根据先序遍历序列和中序遍历序列建树
ThreadTree createTree(string s1, string s2)
{
if (s1 == "" || s2 == "") return NULL;
ThreadTree tree = new ThreadNode();
tree->data = s1[0];
int m = 0;
while (m < s2.size() && s2[m] != s1[0]) m++;
tree->lchild = createTree(string(s1.begin() + 1, s1.begin() + 1 + m), string(s2.begin(), s2.begin() + m));
tree->rchild = createTree(string(s1.begin() + 1 + m, s1.end()), string(s2.begin() + m + 1, s2.end()));
return tree;
}
// 遍历结点
void visit(ThreadTree p)
{
cout << p->data << " ";
return;
}
// 递归创建中序线索化二叉树
void InThread(ThreadTree &tree, ThreadTree &pre)
{
if (tree)
{
InThread(tree->lchild, pre);
if (!tree->lchild){
tree->lchild = pre;
tree->ltag = 1;
}
if (pre && !pre->rchild){
pre->rchild = tree;
pre->rtag = 1;
}
pre = tree;
InThread(tree->rchild, pre);
}
return;
}
// 中序线索化二叉树
void createInThread(ThreadTree &tree)
{
if (!tree) return;
ThreadTree pre = NULL;
InThread(tree, pre);
pre->rchild = NULL;
pre->rtag = 1;
}
// 求中序线索二叉树中中序序列的第一个结点
ThreadTree firstNode(ThreadTree tree)
{
while (tree->ltag == 0) tree = tree->lchild;
return tree;
}
// 求中序线索二叉树在中序序列下的后继结点
ThreadTree nextNode(ThreadTree p)
{
if (p->rtag) return p->rchild;
return firstNode(p->rchild);
}
// 遍历中序线索二叉树
void inThreadOrder(ThreadTree tree)
{
if (!tree) return;
for (ThreadTree it = firstNode(tree); it; it = nextNode(it)){
visit(it);
}
cout << endl;
return;
}
int main()
{
ThreadTree tree = createTree("abdce", "bdaec");
createInThread(tree);
inThreadOrder(tree);
return 0;
}
3. 后序线索二叉树
参考:https://blog.youkuaiyun.com/My_heart_/article/details/52087948
后序线索化二叉树有一个比较关键的问题,就是使用后序线索二叉树进行遍历的时候需要知道结点的父节点,因此需要给结构体加上一个父节点域,才能发挥后序线索二叉树的作用,而后序线索二叉树的线索化和先序、中序是一样的。
关于2020王道课后习题
题目描述
查找在中序线索二叉树中的一个结点在后序遍历序列下的前驱结点。
思路分析
分为四种情况:
- 当前结点有右孩子。则右孩子即为当前结点在后序遍历序列下的前驱结点。
- 当前结点无右孩子,有左孩子。则左孩子即为当前结点在后序遍历序列下的前驱结点。
- 当前结点没有孩子,并且是中序遍历的第一个结点。那么他也是后序遍历序列下的第一个结点。
- 当前结点没有孩子,不是中序遍历序列的第一个结点。那么当前一直在当前结点在中序序列中的祖先中找到一个有左孩子的结点,那个左孩子就是当前结点在后序遍历序列下的前驱结点。
代码
// 查找指定结点在后序序列中的前驱结点
ThreadTree findPreInLastOrder(ThreadTree tree, ThreadTree p)
{
if (!p->rtag) return p->rchild;
if (!p->ltag) return p->lchild;
if (p->lchild == NULL) return NULL;
while (p->ltag == 1 && p->lchild != NULL) p = p->lchild;
if (!p->ltag) return p->lchild;
return NULL;
}