一、线索二叉树概念
具有
n
个结点的二叉链表中,其二叉链表的
n
个结点中共有
2n
个指针域,在这
2n
个指针域中,真正用于指向后件(左子结点或右子结点)的指针域只有
n-1
个,而另外的
n+1
个指针域都是空的。这样就利用二叉链表中的空指针域,存放指向结点在某种遍历次序下的前趋和后继结点的指针(这种附加的指针称为
"
线索
"
),这种加上了线索的二叉链表称为线索链表,相应的二叉树称为线索二叉树
(ThreadedBinaryTree)
。根据线索性质的不同,线索二叉树可分为前序线索二叉树、中序线索二叉树和后序线索二叉树三种。
二、线索链表的结点结构
线索链表中的结点结构为:

ltag
和
rtag
是增加的两个标志域,用来区分结点的左、右指针域是指向其左、右孩子的指针,还是指向其前趋或后继的线索。

三、二叉树的线索化
1
.线索化和线索化实质
将二叉树变为线索二叉树的过程称为线索化
。按某种次序将二叉树线索化的实质是:按该次序遍历二叉树,在遍历过程中用线索取代空指针。
2
.二叉树的中序线索化
(
1
)分析
算法与中序遍历算法类似,只需要将遍历算法中访问结点的操作具体化为建立正在访问的结点与其非空中序前趋结点间线索。
a).
若上次访问到的结点的右指针为空,则将当前访问到的结点序号填入,并置右标志域为
1
;
b).
若当前访问到的结点的左指针为空,则将上次访问到的及诶单序号填入,并置左标志域为
1
(
2
)将二叉树按中序线索化的算法
PROCEDURE INTHREAD(BT,h)
IF BT != 0 THEN
{
INTHREAD(L(BT),h)
IF (h != 0) and (R(h) = 0) THEN
{ R(h) = BT; Rflag(h) = 1; }
IF L(BT) = 0 THEN
{ L(BT) = h; Lflag(BT) = 1; }
h = BT
INTHREAD(R(BT),h)
}
RETURN
(
3
)算法分析
和中序遍历算法一样,递归过程中对每结点仅做一次访问。因此对于
n
个结点的二叉树,算法的时间复杂度亦为
O(n)
。
3
.二叉树的前序线索化和后序线索化
前序线索化和后序线索化算法与二叉树的中序线索化类似。
四、线索二叉树的运算
1
.
在中序线索二叉树中,查找结点
*p
的中序后继结点
在中序线索二叉树中,查找结点
*p
的中序后继结点分两种情形:
a).
若
*p
的右子树空
(
即
p->rtag
为
Thread)
,则
p->rchild
为右线索,直接指向
*p
的中序后继。
b).
若
*p
的右子树非空
(
即
p->rtag
为
Link)
,则
*p
的中序后继必是其右子树中第一个中序遍历到的结点。也就是从
*p
的右孩子开始,沿该孩子的左链往下查找,直至找到一个没有左孩子的结点为止,该结点是
*p
的右子树中
“
最左下
”
的结点,即
*P
的中序后继结点。
具体算法如下:
BinThrNode *Inorderpre(BinThrNode *p)
{ //在中序线索树中找结点*p的中序前趋,设p非空
BinThrNode *q;
if (p->ltag==Thread) //*p的左子树为空
return p->lchild; //返回左线索所指的中序前趋
else{
q=p->lchild; //从*p的左孩子开始查找
while (q->rtag==Link)
q=q->rchild; //右子树非空时,沿右链往下查找
return q; //当q的右子树为空时,它就是最右下结点
} //end if
}
该算法的时间复杂度不超过树的高度
h
,即
O(h)
。
2
.
在中序线索二叉树中查找结点
*p
的中序前趋结点
中序是一种对称序,故在中序线索二叉树中查找结点
*p
的中序前趋结点与找中序后继结点的方法完全对称。具体情形如下:
a).
若
*p
的左子树为空,则
p->1child
为左线索,直接指向
*p
的中序前趋结点;
b).
若
*p
的左子树非空,则从
*p
的左孩子出发,沿右指针链往下查找,直到找到一个没有右孩子的结点为止。该结点是
*p
的左子树中
“
最右下
”
的结点,它是
*p
的左子树中最后一个中序遍历到的结点,即
*p
的中序前趋结点。
具体算法如下:
BinThrNode *Inorderpre(BinThrNode *p)
{ //在中序线索树中找结点*p的中序前趋,设p非空
BinThrNode *q;
if (p->ltag==Thread) //*p的左子树为空
return p->lchild; //返回左线索所指的中序前趋
else{
q=p->lchild; //从*p的左孩子开始查找
while (q->rtag==Link)
q=q->rchild; //右子树非空时,沿右链往下查找
return q; //当q的右子树为空时,它就是最右下结点
} //end if
}
由上述讨论可知:对于非线索二叉树,仅从
*p
出发无法找到其中序前趋
(
或中序后继
)
,而必须从根结点开始中序遍历,才能找到
*p
的中序前趋
(
或中序后继
)
。线索二叉树中的线索使得查找中序前趋和中序后继变得简单有效。
3
.
在后序线索二叉树中,查找指定结点
*p
的后序前趋结点
在后序线索二叉树中,查找指定结点
*p
的后序前趋结点的具体规律是:
a).
若
*p
的左子树为空,则
p->lchild
是前趋线索,指示其后序前趋结点。
b).
若
*p
的左子树非空,则
p->lchild
不是前趋线索。由于后序遍历时,根是在遍历其左右子树之后被访问的,故
*p
的后序前趋必是两子树中最后一个遍历结点。
当
*p
的右子树非空时,
*p
的右孩子必是其后序前趋
当
*p
无右子树时,
*p
的后序前趋必是其左孩子
4
.
在后序线索二叉树中,查找指定结点
*p
的后序后继结点
具体的规律:
a).
若
*p
是根,则
*p
是该二叉树后序遍历过程中最后一个访问到的结点。
*p
的后序后继为空;
b).
若
*p
是其双亲的右孩子,则
*p
的后序后继结点就是其双亲结点
c).
若
*p
是其双亲的左孩子,但
*P
无右兄弟,
*p
的后序后继结点是其双亲结点;
d).
若
*p
是其双亲的左孩子,但
*p
有右兄弟,则
*p
的后序后继是其双亲的右子树中第一个后序遍历到的结点,它
是该子树中
“
最左下的叶结点
”
;
由上述讨论中可知:在后序线索树中,仅从
*p
出发就能找到其后序前趋结点;要找
*p
的后序后继
结点,仅当
*p
的右子树为空时,才能直接由
*p
的右线索
p->rchild
得到。否则必须知道
*p
的双亲结点才能找到其后序后继。因此,如果线索二叉树中的结点没有指向其双亲结点的指针,就可能要从根开始进行后序遍历才能找到结点
*P
的后序后继。由此,线索对查找指定结点的后序后继并无多大帮助。
5
.
在前序线索二叉树中,查找指定结点
*p
的前序后继结点
6
.
在前序线索二叉树中,查找指定结点
*p
的前序前趋结点
在前序线索二叉树中,找某一点
*p
的前序后继也很简单,仅从
*p
出发就可以找到;但找其前序前趋
也必须知道
*p
的双亲结点。当树中结点未设双亲指针时,同样要进行从根开始的前序遍历才能找到结点
*p
的前序前趋。
五、遍历线索二叉树
遍历某种次序的线索二叉树,只要从该次序下的开始结点开发,反复找到结点在该次序下的后继,直至终端结点。遍历中序线索二叉树算法:
PROCEDURE INTHTRAV(BT)
IF BT = 0 THEN RETURN
h = BT
WHILE (Lflag(h) = 0) DO h = L(h)
OUTPUT V(h)
WHILE (R(h) != 0 DO
{
IF (Rflag(h) = 1) THEN h = R(h)
ELSE
{
h = R(h)
WHILE ((Lflag(h) = 0) and (L(h) != 0)) DO h = L(h)
}
OUTPUT V(h)
}
RETURN
分析:
a).
中序序列的终端结点的右线索为空,所以
do
语句的终止条件是
p==NULL
。
b).
该算法的时间复杂性为
O(n)
。因为是非递归算法,常数因子上小于递归的遍历算法。因此,若对一棵二叉树要经常遍历,或查找结点在指定次序下的前趋和后继,则应采用线索链表作为存储结构为宜。
c).
以上介绍的线索二叉树是一种全线索树(即左右线索均要建立)。许多应用中只要建立左右线索中的一种。
d).
若在线索链表中增加一个头结点,令头结点的左指针指向根,右指针指向其遍历序列的开始或终端结点会更方便。
六、验证前序线索及前序线索遍历和中序线索及中序线索遍历的程序
#include <iostream>
#include <sstream>
using namespace std;
template<class T>
struct tbtnode
{
T s_t;
bool lflag;
bool rflag;
tbtnode *lchild;
tbtnode *rchild;
};
template<class T>
tbtnode<T>* createtbt(tbtnode<T>* tbt, int k, ostream& out, istream& in)
{
tbtnode<T> *p, *t;
T tmp;
out << "Input key: " << endl;
in >> tmp;
out << '\t' << tmp << endl;
if ('0' != tmp)
{
p = new tbtnode<T>;
p->s_t = tmp;
p->lchild = NULL;
p->rchild = NULL;
p->lflag = 0;
p->rflag = 0;
if (0 == k) t = p;
if (1 == k) tbt->lchild = p;
if (2 == k) tbt->rchild = p;
createtbt(p, 1, out, in);
createtbt(p, 2, out, in);
}
return (t);
}
template<class T>
void pretraverse(tbtnode<T> *root)
{
if (NULL != root)
{
cout << root->s_t << " ";
pretraverse(root->lchild);
pretraverse(root->rchild);
}
}
template<class T>
void intraverse(tbtnode<T> *root)
{
if (NULL != root)
{
intraverse(root->lchild);
cout << root->s_t << " ";
intraverse(root->rchild);
}
}
template<class T>
void postraverse(tbtnode<T> *root)
{
if (NULL != root)
{
postraverse(root->lchild);
postraverse(root->rchild);
cout << root->s_t << " ";
}
}
template<class T>
void inthread(tbtnode<T> *tbt, tbtnode<T> **h)
{
if (NULL != tbt)
{
inthread(tbt->lchild, h);
if (tbt->lchild == NULL)
{
tbt->lchild = *h;
tbt->lflag = 1;
}
if ((*h != NULL) && ((*h)->rchild == NULL))
{
(*h)->rchild = tbt;
(*h)->rflag = 1;
}
*h = tbt;
inthread(tbt->rchild, h);
}
}
template<class T>
void prethread(tbtnode<T> *tbt, tbtnode<T> **h)
{
if (NULL != tbt)
{
if (tbt->lchild == NULL)
{
tbt->lchild = *h;
tbt->lflag = 1;
}
if ((*h != NULL) && ((*h)->rchild == NULL))
{
(*h)->rchild = tbt;
(*h)->rflag = 1;
}
*h = tbt;
//*h = tbt->lchild;
if (tbt->lflag == 0)
prethread(tbt->lchild, h);
if (tbt->rflag == 0)
prethread(tbt->rchild, h);
}
}
template<class T>
void posthread(tbtnode<T> *tbt, tbtnode<T> **h)
{
if (NULL != tbt)
{
posthread(tbt->lchild, h);
posthread(tbt->rchild, h);
if (tbt->lchild == NULL)
{
tbt->lchild = *h;
tbt->lflag = 1;
}
if ((*h != NULL) && ((*h)->rchild == NULL))
{
(*h)->rchild = tbt;
(*h)->rflag = 1;
}
*h = tbt;
}
}
template<class T>
void inthtraverse(tbtnode<T> *tbt)
{
tbtnode<T> *h;
if (tbt == NULL) return;
h = tbt;
while (h->lflag == 0) h = h->lchild;
cout << h->s_t << " ";
while (h->rchild != NULL)
{
if (h->rflag == 1)
h = h->rchild;
else
{
h = h->rchild;
while ((h->lflag == 0) && (h->lchild != NULL))
h = h->lchild;
}
cout << h->s_t << " ";
}
}
template<class T>
void prethtraverse(tbtnode<T> *tbt)
{
tbtnode<T> *h;
if (tbt == NULL) return;
h = tbt;
cout << tbt->s_t << " ";
while (h->rchild != NULL)
{
if ((h->lflag == 0) && (h->lchild != NULL))
{
h = h->lchild;
}
else
{
h = h->rchild;
}
cout << h->s_t << " ";
}
}
template<class T>
void posthtraverse(tbtnode<T> *tbt)
{
tbtnode<T> *h;
if (tbt == NULL) return;
h = tbt;
}
int main()
{
typedef tbtnode<char> tbtnode_char;
tbtnode_char *root;
//istringstream ssin(string("AB0DG000CE0H00F00"));
istringstream ssin(string("ABCD00E00FG00H00IJK00L00MN00O00"));
root = createtbt(root, 0, cout, ssin);
pretraverse(root);
cout << endl;
intraverse(root);
cout << endl;
postraverse(root);
cout << endl;
cout << "通过穿线二叉树进行遍历" << endl;
tbtnode_char *h = NULL;
//inthread(root, &h);
//inthtraverse(root);
prethread(root, &h);
prethtraverse(root);
cout << endl;
return 0;
}

- 大小: 1.6 KB

- 大小: 5.8 KB
分享到:
相关推荐
二叉树是一种重要的数据结构,它由节点组成,每个节点包含一个值以及指向左子节点和右子...理解并掌握中序线索化二叉树及其遍历的方法,对于提升算法能力,特别是在数据结构和算法相关的编程面试中,都是非常有价值的。
线索化二叉树及对线索化二叉树的遍历.cpp
`ThreadBinaryTree_PreOrderInput`函数则是利用先序遍历的方式创建一个线索二叉树,从输入中读取字符构建树结构。 中序遍历通常用于构造二叉搜索树,它能保证树中任意节点的左子树所有节点的值小于该节点,右子树...
2. 中序遍历:中序遍历的顺序是左子树 -> 根节点 -> 右子树,对于构造线索二叉树来说,中序遍历特别有用,因为线索化后的中序遍历可以模拟栈的行为,线性地访问所有节点。中序线索化使得在没有中序遍历递归或栈的...
二叉树的遍历和线索化是理解和操作二叉树的关键技术。本篇将深入探讨这两大主题。 首先,我们来讨论二叉树的遍历。遍历二叉树是指按照特定顺序访问每个节点的过程。主要有三种常见的遍历方法: 1. **前序遍历...
二叉树创建遍历及线索化,二叉树的创建存储以及先序中序后序遍历,图形树输出
本文主要介绍了C语言实现线索二叉树的定义与遍历,结合具体实例形式分析了基于C语言的线索二叉树定义及遍历操作相关实现技巧与注意事项。 一、线索二叉树的定义 线索二叉树是一种特殊的二叉树结构,它在每个结点中...
本代码提供了一个完整的线索二叉树的构建和遍历的实现示例,其中涉及到了节点的定义、二叉树的构建、中序线索化及中序遍历等关键步骤。通过对这些步骤的详细了解和学习,可以更好地理解和应用线索二叉树的相关知识。
本节内容主要介绍了数据结构中的树和二叉树,特别是关于遍历二叉树和线索二叉树的概念及算法。首先,树是一种非线性的数据结构,由根节点、左子树和右子树组成。二叉树是特殊的树,每个节点最多有两个子节点,分为左...
线索二叉树是一种特殊形式的二叉树,它在二叉链表的基础上增加了线索,用于在树中进行更高效的前序、中序和后序遍历。这种数据结构在计算机科学,尤其是数据结构课程中占有重要地位,因为它能有效地解决非递归遍历的...
在文档"二叉树的遍历.docx"中,可能详细地介绍了这些遍历方法的原理、步骤以及具体的代码实现,包括可能的优化和拓展,比如层序遍历、线索二叉树遍历等。如果你对二叉树的遍历有深入研究的需求,建议查阅该文档以...
同时,线索二叉树提供了一种有效的数据结构方式来处理二叉树遍历中可能出现的空指针问题,并且能够更加高效地实现中序遍历。在实际应用中,线索二叉树非常适合需要频繁遍历的场景,比如在文本编辑器中实现光标移动、...
线索二叉树的建立,前中后线索的建立,前中后序递归非递归遍历,广义表形式输出,层序遍历
1. **前序线索二叉树**:基于二叉树的前序遍历来构建。 2. **中序线索二叉树**:基于二叉树的中序遍历来构建。 3. **后序线索二叉树**:基于二叉树的后序遍历来构建。 4. **层序线索二叉树**:基于二叉树的层序遍...
线索二叉树是一种特殊的二叉树数据结构,它在二叉链表的基础上增加了线索,用于在非递归情况下实现前序、中序和后序遍历。这种数据结构在计算机科学中尤其在算法和数据结构的学习中具有重要意义,因为它允许我们高效...
在实际的教学和学习过程中,像LTree这样的文件极有可能包含了严蔚敏教授关于线索二叉树的详细讲解、相关习题及其解答。通过这种结构化的学习材料,学生能够更加系统地理解线索二叉树的实现和应用场景,从而在算法...
本实验报告主要探讨了二叉树的建立、遍历以及线索化的相关概念和实现方法。 首先,二叉树的建立是通过创建节点并连接它们来完成的。在C语言中,可以定义一个结构体`bithrnode`来表示二叉树的节点,包含数据域`data`...
线索二叉树是一种特殊的二叉树数据结构,它在普通链式存储的二叉树基础上增加了额外的信息,以便更高效地进行遍历操作。在普通的二叉树中,每个节点只有左右子节点的指针,而在线索二叉树中,除了原有的子节点指针外...
计算机软件及应用中序遍历线索二叉树的演示PPT学习教案.pptx是关于计算机软件及应用中序遍历线索二叉树的演示教案,主要讲解了中序遍历线索二叉树的算法实现和应用。 知识点总结: 1. 中序遍历线索二叉树:中序...