原文:https://blog.csdn.net/qq_42730750/article/details/108285846
前言
本篇章主要介绍线索二叉树,包括线索二叉树的基本概念、构造及遍历,并用Python实现其创建及其遍历等操作。
1. 基本概念
上篇博客介绍的二叉链表的存储结构体现的只是一种父子关系,它不能直接得到结点在遍历过程中的前驱和后继信息。那既然不能直接得到,那就添加两个指针域ltag和rtag来指向结点的前驱和后继,这样可以加快查找结点前驱和后继的速度,加上这两个指针域的二叉树就是线索二叉树啦!
l c h i l d lchildlchild l t a g ltagltag d a t a datadata r t a g rtagrtag r c h i l d rchildrchild
上面就是线索二叉树的结点,它是这样规定的:如果结点没有左子树,则令lchild指向其前驱结点;如果结点没有右子树,则令rchild指向其后继结点;ltag和rtag两个标志域是用来标识指针域是指向左/右孩子还是指向前驱/后继结点。标志域的含义如下:
l t a g = { 0 , l c h i l d 域 指 向 结 点 的 左 孩 子 1 , l c h i l d 域 指 向 结 点 的 前 驱 ltag=
{0,1,lchild域指向结点的左孩子lchild域指向结点的前驱
{
0
,
�
�
ℎ
�
�
�
域
指
向
结
点
的
左
孩
子
1
,
�
�
ℎ
�
�
�
域
指
向
结
点
的
前
驱
ltag={
0,
1,
lchild域指向结点的左孩子
lchild域指向结点的前驱
r t a g = { 0 , r c h i l d 域 指 向 结 点 的 右 孩 子 1 , r c h i l d 域 指 向 结 点 的 后 继 rtag=
{0,1,rchild域指向结点的右孩子rchild域指向结点的后继
{
0
,
�
�
ℎ
�
�
�
域
指
向
结
点
的
右
孩
子
1
,
�
�
ℎ
�
�
�
域
指
向
结
点
的
后
继
rtag={
0,
1,
rchild域指向结点的右孩子
rchild域指向结点的后继
ltag和rtag这两个指针称为线索,上述的链表也称为线索链表。
二叉线索链表结点的定义如下:
class ThreadNode(object):
def __init__(self, data='#'):
self.data = data
self.lchild = None
self.rchild = None
self.ltag = 0
self.rtag = 0
1
2
3
4
5
6
7
2. 线索二叉树的构造
以某种次序遍历将二叉树变为线索二叉树的过程称为线索化。
线索化的实质就是当二叉树中某结点不存在左孩子或右孩子时,将其lchild域或rchild域指向该结点的前驱或后继。
咱们就以这棵二叉树为例,来看一下线索二叉树是如何构造的。
2.1 先序线索二叉树
上述二叉树的先序遍历为:A B D E C F ABDECFABDECF
上面这棵就是先序线索二叉树,是通过先序遍历构造的,根据原二叉树的二叉链表可知,空指针(即度为1的结点或叶子结点的孩子指针域)得到了有效利用。
先序线索二叉树寻找结点的后继的过程如下:
(1) 如果该结点有左孩子,则左孩子就是该结点的后继;
(2) 如果该结点无左孩子但有右孩子,则右孩子就是该结点的后继;
(3) 如果该结点即无左孩子又无右孩子(即叶子结点),则右链域指向的结点就是该结点的后继。
2.2 后序线索二叉树
上述二叉树的后序遍历为:D E B F C A DEBFCADEBFCA
后序线索二叉树寻找结点的后继的过程如下:
(1) 如果该结点是二叉树的根,则该结点的后继为空;
(2) 如果该结点是其双亲的右孩子,或者是其双亲的左孩子且双亲没有子树,则该结点的后继即为双亲;
(3) 如果该结点是其双亲的左孩子,且其双亲有右子树,则该结点的后继为双亲的右子树上按后序遍历得到的第一个结点。
这就有点复杂了,先序线索二叉树、后序线索二叉树寻找结点的前驱也都复杂,书上也没说老师也没讲,这里也就不介绍了,不常用,最常用的是下面要介绍的中序线索二叉树。
2.3 中序线索二叉树
上述二叉树的后序遍历为:D B E A F C DBEAFCDBEAFC
中序线索二叉树的建立如下:
令指针PreNode指向刚刚访问过的结点,指针RootNode指向正在访问的结点,即PreNode指向RootNode的前驱。在中序遍历过程中,先判断RootNode是否有左孩子,若没有左孩子就将它的lchild指向PreNode;然后再判断PreNode是否有右孩子,若没有右孩子就将它的rchild指向RootNode。
为了方便,可以在二叉树的线索链表上添加一个头结点,令其lchild域的指针指向二叉树的根结点,其rchild域的指针指向中序遍历时访问的最后一个结点(即最右边的那个结点);然后再令二叉树中序序列中的第一个结点(即二叉树最左边的那个结点)的lchild域指针和最后一个结点的rchild域指针均指向头结点。没错,这是二叉树的双向线索链表。
中序线索二叉树的创建代码如下:
def CreateInThread(self):
RootNode = self.__CreateBinaryTree()
if RootNode is None:
self.HeadNode.lchild = self.HeadNode
else:
# lchild域的指针指向二叉树的根结点
self.HeadNode.lchild = RootNode
self.PreNode = self.HeadNode
self.InThread(RootNode)
# 处理最后一个结点
self.PreNode.rtag = 1
self.PreNode.rchild = self.HeadNode
# rchild域的指针指向中序遍历时访问的最后一个结点
self.HeadNode.rchild = self.PreNode
def InThread(self, TreeNode):
if TreeNode is not None:
# 递归, 线索化左子树
self.InThread(TreeNode.lchild)
if TreeNode.lchild is None:
# 当前结点没有左孩子
# 将当前结点的ltag置为1, 表示lchild域指向的是前驱
TreeNode.ltag = 1
TreeNode.lchild = self.PreNode
if self.PreNode.rchild is None:
# 前驱结点没有右孩子
# 将前驱结点的rtag置为1, 表示rchild域指向的是后继, 即当前的TreeNode
self.PreNode.rtag = 1
self.PreNode.rchild = TreeNode
# 标记刚刚访问的结点为下个结点的前驱结点
self.PreNode = TreeNode
self.InThread(TreeNode.rchild)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
3. 中序线索二叉树的遍历
中序线索二叉树寻找结点前驱的规律是:若ltag=1,则左链为线索,指示其前驱,否则遍历左子树中最后一个访问的结点(即左子树中最右边的那个结点)为其前驱。
中序线索二叉树寻找结点后继的规律是:若rtag=1,则右链为线索,指示其后继,否则遍历右子树中第一个访问的结点(即右子树中最左边的那个结点)为其后继。
中序线索二叉树的遍历代码实现如下:
def InOrderThread(self):
# TreeNode就是树的根结点
TreeNode = self.HeadNode.lchild
while TreeNode is not self.HeadNode:
while TreeNode.ltag == 0:
# 找到了树最左边的那个结点(不一定是叶结点)
TreeNode = TreeNode.lchild
self.VisitBinaryTreeNode(TreeNode)
while TreeNode.rchild is not self.HeadNode and TreeNode.rtag == 1:
# 线索后继
TreeNode = TreeNode.rchild
self.VisitBinaryTreeNode(TreeNode)
# rtag=0就开始寻找右子树最左边那个结点
TreeNode = TreeNode.rchild
1
2
3
4
5
6
7
8
9
10
11
12
13
14
4. 代码框架
class BinaryThreadTree(object):
def __init__(self, data_list):
self.data_list = data_list
# 创建树的头结点
self.HeadNode = ThreadNode()
self.HeadNode.ltag = 0
self.HeadNode.rtag = 1
self.HeadNode.lchild = self.HeadNode
self.PreNode = ThreadNode()
def __CreateBinaryTree(self, root=None, pos=0):
if pos >= len(self.data_list) or self.data_list[pos] == '#':
# 递归结束条件
return None
else:
root = ThreadNode(self.data_list[pos])
# 递归建立左子树
root.lchild = self.__CreateBinaryTree(root, 2*pos+1)
# 递归建立右子树
root.rchild = self.__CreateBinaryTree(root, 2*pos+2)
return root
def VisitBinaryTreeNode(self, RootNode):
if RootNode.data != '#':
print(RootNode.data, end=' ')
def CreateInThread(self):
def InThread(self, TreeNode):
def InOrderThread(self):
if __name__ == '__main__':
tree_obj = BinaryThreadTree('ABCDEF#')
tree_obj.CreateInThread()
tree_obj.InOrderThread()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
测试也是OK的:
前言
本篇章主要介绍线索二叉树,包括线索二叉树的基本概念、构造及遍历,并用Python实现其创建及其遍历等操作。
1. 基本概念
上篇博客介绍的二叉链表的存储结构体现的只是一种父子关系,它不能直接得到结点在遍历过程中的前驱和后继信息。那既然不能直接得到,那就添加两个指针域ltag和rtag来指向结点的前驱和后继,这样可以加快查找结点前驱和后继的速度,加上这两个指针域的二叉树就是线索二叉树啦!
l c h i l d lchildlchild l t a g ltagltag d a t a datadata r t a g rtagrtag r c h i l d rchildrchild
上面就是线索二叉树的结点,它是这样规定的:如果结点没有左子树,则令lchild指向其前驱结点;如果结点没有右子树,则令rchild指向其后继结点;ltag和rtag两个标志域是用来标识指针域是指向左/右孩子还是指向前驱/后继结点。标志域的含义如下:
l t a g = { 0 , l c h i l d 域 指 向 结 点 的 左 孩 子 1 , l c h i l d 域 指 向 结 点 的 前 驱 ltag=
{0,1,lchild域指向结点的左孩子lchild域指向结点的前驱
{
0
,
�
�
ℎ
�
�
�
域
指
向
结
点
的
左
孩
子
1
,
�
�
ℎ
�
�
�
域
指
向
结
点
的
前
驱
ltag={
0,
1,
lchild域指向结点的左孩子
lchild域指向结点的前驱
r t a g = { 0 , r c h i l d 域 指 向 结 点 的 右 孩 子 1 , r c h i l d 域 指 向 结 点 的 后 继 rtag=
{0,1,rchild域指向结点的右孩子rchild域指向结点的后继
{
0
,
�
�
ℎ
�
�
�
域
指
向
结
点
的
右
孩
子
1
,
�
�
ℎ
�
�
�
域
指
向
结
点
的
后
继
rtag={
0,
1,
rchild域指向结点的右孩子
rchild域指向结点的后继
ltag和rtag这两个指针称为线索,上述的链表也称为线索链表。
二叉线索链表结点的定义如下:
class ThreadNode(object):
def __init__(self, data='#'):
self.data = data
self.lchild = None
self.rchild = None
self.ltag = 0
self.rtag = 0
1
2
3
4
5
6
7
2. 线索二叉树的构造
以某种次序遍历将二叉树变为线索二叉树的过程称为线索化。
线索化的实质就是当二叉树中某结点不存在左孩子或右孩子时,将其lchild域或rchild域指向该结点的前驱或后继。
咱们就以这棵二叉树为例,来看一下线索二叉树是如何构造的。
2.1 先序线索二叉树
上述二叉树的先序遍历为:A B D E C F ABDECFABDECF
上面这棵就是先序线索二叉树,是通过先序遍历构造的,根据原二叉树的二叉链表可知,空指针(即度为1的结点或叶子结点的孩子指针域)得到了有效利用。
先序线索二叉树寻找结点的后继的过程如下:
(1) 如果该结点有左孩子,则左孩子就是该结点的后继;
(2) 如果该结点无左孩子但有右孩子,则右孩子就是该结点的后继;
(3) 如果该结点即无左孩子又无右孩子(即叶子结点),则右链域指向的结点就是该结点的后继。
2.2 后序线索二叉树
上述二叉树的后序遍历为:D E B F C A DEBFCADEBFCA
后序线索二叉树寻找结点的后继的过程如下:
(1) 如果该结点是二叉树的根,则该结点的后继为空;
(2) 如果该结点是其双亲的右孩子,或者是其双亲的左孩子且双亲没有子树,则该结点的后继即为双亲;
(3) 如果该结点是其双亲的左孩子,且其双亲有右子树,则该结点的后继为双亲的右子树上按后序遍历得到的第一个结点。
这就有点复杂了,先序线索二叉树、后序线索二叉树寻找结点的前驱也都复杂,书上也没说老师也没讲,这里也就不介绍了,不常用,最常用的是下面要介绍的中序线索二叉树。
2.3 中序线索二叉树
上述二叉树的后序遍历为:D B E A F C DBEAFCDBEAFC
中序线索二叉树的建立如下:
令指针PreNode指向刚刚访问过的结点,指针RootNode指向正在访问的结点,即PreNode指向RootNode的前驱。在中序遍历过程中,先判断RootNode是否有左孩子,若没有左孩子就将它的lchild指向PreNode;然后再判断PreNode是否有右孩子,若没有右孩子就将它的rchild指向RootNode。
为了方便,可以在二叉树的线索链表上添加一个头结点,令其lchild域的指针指向二叉树的根结点,其rchild域的指针指向中序遍历时访问的最后一个结点(即最右边的那个结点);然后再令二叉树中序序列中的第一个结点(即二叉树最左边的那个结点)的lchild域指针和最后一个结点的rchild域指针均指向头结点。没错,这是二叉树的双向线索链表。
中序线索二叉树的创建代码如下:
def CreateInThread(self):
RootNode = self.__CreateBinaryTree()
if RootNode is None:
self.HeadNode.lchild = self.HeadNode
else:
# lchild域的指针指向二叉树的根结点
self.HeadNode.lchild = RootNode
self.PreNode = self.HeadNode
self.InThread(RootNode)
# 处理最后一个结点
self.PreNode.rtag = 1
self.PreNode.rchild = self.HeadNode
# rchild域的指针指向中序遍历时访问的最后一个结点
self.HeadNode.rchild = self.PreNode
def InThread(self, TreeNode):
if TreeNode is not None:
# 递归, 线索化左子树
self.InThread(TreeNode.lchild)
if TreeNode.lchild is None:
# 当前结点没有左孩子
# 将当前结点的ltag置为1, 表示lchild域指向的是前驱
TreeNode.ltag = 1
TreeNode.lchild = self.PreNode
if self.PreNode.rchild is None:
# 前驱结点没有右孩子
# 将前驱结点的rtag置为1, 表示rchild域指向的是后继, 即当前的TreeNode
self.PreNode.rtag = 1
self.PreNode.rchild = TreeNode
# 标记刚刚访问的结点为下个结点的前驱结点
self.PreNode = TreeNode
self.InThread(TreeNode.rchild)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
3. 中序线索二叉树的遍历
中序线索二叉树寻找结点前驱的规律是:若ltag=1,则左链为线索,指示其前驱,否则遍历左子树中最后一个访问的结点(即左子树中最右边的那个结点)为其前驱。
中序线索二叉树寻找结点后继的规律是:若rtag=1,则右链为线索,指示其后继,否则遍历右子树中第一个访问的结点(即右子树中最左边的那个结点)为其后继。
中序线索二叉树的遍历代码实现如下:
def InOrderThread(self):
# TreeNode就是树的根结点
TreeNode = self.HeadNode.lchild
while TreeNode is not self.HeadNode:
while TreeNode.ltag == 0:
# 找到了树最左边的那个结点(不一定是叶结点)
TreeNode = TreeNode.lchild
self.VisitBinaryTreeNode(TreeNode)
while TreeNode.rchild is not self.HeadNode and TreeNode.rtag == 1:
# 线索后继
TreeNode = TreeNode.rchild
self.VisitBinaryTreeNode(TreeNode)
# rtag=0就开始寻找右子树最左边那个结点
TreeNode = TreeNode.rchild
1
2
3
4
5
6
7
8
9
10
11
12
13
14
4. 代码框架
class BinaryThreadTree(object):
def __init__(self, data_list):
self.data_list = data_list
# 创建树的头结点
self.HeadNode = ThreadNode()
self.HeadNode.ltag = 0
self.HeadNode.rtag = 1
self.HeadNode.lchild = self.HeadNode
self.PreNode = ThreadNode()
def __CreateBinaryTree(self, root=None, pos=0):
if pos >= len(self.data_list) or self.data_list[pos] == '#':
# 递归结束条件
return None
else:
root = ThreadNode(self.data_list[pos])
# 递归建立左子树
root.lchild = self.__CreateBinaryTree(root, 2*pos+1)
# 递归建立右子树
root.rchild = self.__CreateBinaryTree(root, 2*pos+2)
return root
def VisitBinaryTreeNode(self, RootNode):
if RootNode.data != '#':
print(RootNode.data, end=' ')
def CreateInThread(self):
def InThread(self, TreeNode):
def InOrderThread(self):
if __name__ == '__main__':
tree_obj = BinaryThreadTree('ABCDEF#')
tree_obj.CreateInThread()
tree_obj.InOrderThread()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
测试也是OK的: