首页 > 编程语言 >(转)Python描述数据结构之线索二叉树篇

(转)Python描述数据结构之线索二叉树篇

时间:2023-09-25 10:24:52浏览次数:46  
标签:lchild 结点 TreeNode Python self 二叉树 数据结构 线索

原文: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的:

 



 

标签:lchild,结点,TreeNode,Python,self,二叉树,数据结构,线索
From: https://www.cnblogs.com/liujiacai/p/17727309.html

相关文章

  • python面向对象的三大特性:封装性、继承性、多态性
    python面向对象的三大特性:封装性、继承性、多态性一、python中的封装在python代码中,封装具有两层含义:①在把现实世界中的实体中的属性和方法写到类的里面的操作即为封装。classPerson(object):#封装属性#封装方法②封装可以为属性和方法添加私有权限(属性和方......
  • 数据结构优化建图
    2023ICPC网络赛2B分治看到1e5给10s以为是根号log的做法,一直在往小的块暴力,大的块O(n)建图想,但这并没有用。实际上有些常数的双log也可以很慢,还是不要根据数据范围把做法锁的太死!考虑优化每个虫洞之内的建图,关键在于那个曼哈顿距离是不独立的。考虑只有一个绝对值怎么做:直接排序......
  • Python 基本语法
    在开始学习Python编程语言之前,首先要掌握基本的语法。本文将介绍Python编程语言的基本语法,帮助初学者顺利进入Python编程世界。一、Python编程语言概述Python是一种高级编程语言,具有简单易学、语法简洁、功能强大等特点。Python支持多种编程范式,包括面向对象、面向......
  • 如何在python代码中自动插入时间和作者信息
    在编程的过程中,为养成良好的写代码习惯,很多人通常喜欢将一些作者信息以及编码信息存储在代码中,以便于后期的查阅,也可帮助后来者进行快速入手,那么如何才能让他自动出现在我们的代码中呢,我们可按照下面的方式进行设置,希望可以帮到你!在python编程工具pycharm中按照以下路径打开:File......
  • 小白学Python:提取Word中的所有图片,只需要1行代码
    大家好,这里是程序员晚枫,全网同名。最近在小破站账号:Python自动化办公社区更新一套课程:给小白的《50讲Python自动化办公》在课程群里,看到学员自己开发了一个功能:从word里提取图片。这个功能非常实用。我在征求开发者:王鹏大哥的同意后,把这行代码集成到了python-office这个库里,实现......
  • python pip Fatal error in launcher:
    执行pip命令,提示Fatalerrorinlauncher原因:是不是修改过python.exe的名字。因为pip在生成的时候,就把Pythone.exed绝对路径写到了文件里,而pip执行又依赖python,所以执行报错。系统里是否装了多个版本的python,同上一条原因,因为写了绝对路径,导致文件寻找时,有可能交叉......
  • 结对项目:用Python实现四则运算
    这个作业属于哪个课程计科1/2班这个作业要求在哪里结对项目这个作业的目标实现一个自动生成小学四则运算题目的命令行程序团队成员姓名学号梁昊东3121005000李铭伟3121004145github链接:https://github.com/e1ecb0t/e1ecb0t/tree/main/cacul......
  • python教程:调用svn status命令对提交的文件进行add状态过滤(只保存新增加的文件)
    需求说明编写一段python程序,用于对svnadd状态的文件进行过滤,并用列表对这些文件进行保存。代码实现以下是一个示例的Python程序,用于对SVN的svnstatus命令中状态为“A”(新增)的文件进行过滤,并将它们存储在一个列表中:importsubprocessdefget_added_files():added_fi......
  • python教程:解决报错:ERROR: THESE PACKAGES DO NOT MATCH THE HASHES FROM THE REQUIRE
    从以下两种途径来解决。清除缓存这个错误通常表示安装的软件包与要求文件中的哈希值不匹配。这可能是由于要求文件被更改或软件包被篡改引起的。为了解决这个问题,你可以尝试以下几个步骤:清理缓存:运行以下命令清理pip缓存:pipcachepurge```更新要求文件:如果你更新了软件包的版本......
  • python.exe get-pip.py安装失败
    装pip要用get-pip.py来安装,但安装时还要下载whl文件,如果系统中没有设置国内镜像源,从国外下,会经常失败。原因:大部分失败都是因为网络问题才失败的。解决方法:使用国内源,在C:\Users\你的用户名\pip文件夹下,把以下内容保存到pip.ini里。[global]index-url=http://mir......