欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

十分钟搞懂线索二叉树

程序员文章站 2024-02-01 08:42:52
...

一、线索二叉树是什么?

在一个有n个节点的二叉树中,必定有n+1个空指针域,n-1个非空指针域。这样就会非常浪费空间,而且对于这种普通的二叉树我们采用的遍历算法都是递归算法,由于递归是一个不断入栈、出栈的过程,所以相对来说就会浪费系统的资源。那么有没有一种结构可以帮助我们解决这两个问题呢?线索二叉树就是为此而生的哈,线索二叉树中每一个节点都有一个前驱节点和一个后继节点, 它充分的将二叉树中的空指针域利用了起来,在遍历树的时候,我们也不用使用递归算法了,可以直接访问当前节点的前驱节点和后继节点即可。

二、前驱、后继节点

下面我们采用中序遍历为例子分析每个节点的前驱、后继节点。比如下面一棵二叉树采用中序遍历输出的结果为:DBAECF
十分钟搞懂线索二叉树
在这个例子中,B的直接前驱节点就是D,直接后继节点就是A。F的直接前驱节点是C,直接后继节点为None。

将上面的二叉树线索化就得到下面的结构,图中黄色的线指向的是该节点的直接前驱节点,红色的线指向的是该节点的直接后继节点。最后我们就通过节点之间的前驱与后继关系对树进行遍历。
十分钟搞懂线索二叉树

三、线索二叉树节点的结构

对于二叉树中的节点,如果节点有左孩子,那么它的left_Child指向左孩子,否则指向它的直接前驱节点;如果节点有右孩子,那么它的right_Child指向右孩子,否则指向它的直接后继节点。它的节点结构如下所示:
十分钟搞懂线索二叉树

  • 如果该节点的 left_Thread = False,代表该节点的 left_Child 指针域指向的是其左孩子;如果 left_Thread = True ,代表该节点的 left_Child 指针域指向的是它的直接前驱节点。
  • 如果该节点的 right_Thread = False,代表该节点的 right_Child 指针域指向的是其右孩子;如果 right_Thread = True ,代表该节点的 right_Child 指针域指向的是它的直接后继节点。

节点结构代码如下:

class Node:
    def __init__(self,data):
        self.data = data
        self.left_Child = None
        self.right_Child = None
        self.left_Thread = False
        self.right_Thread = False

四、线索化二叉树

线索化就是要找到二叉树中每个节点的前驱节点和后继节点。与二叉树的遍历一样,二叉树的线索化也有前序、中序、后序3种方式,下面我以中序遍历为例,分析如何进行二叉树的线索化。

我们在二叉树的中序遍历中用到了下面这段代码:

    def middle_node(self,node):
        if node != None:
            self.middle_node(node.left)
            print(node.data,end=' ')
            self.middle_node(node.right)

采用中序遍历线索化二叉树时使用的也是上面的那段代码,只不过我们需要做一点小小的改动。由于在遍历过程中,我们需要获取当前访问的节点的前驱节点,并且将当前节点的 left_Child 指向前驱节点,所以我们设置一个 prev 变量,它时刻指向当前访问节点的前一个节点 。代码如下:

    def middle_order_Thread_Tree(self,node):
        if(node != None):

            # 递归线索化左子树
            self.middle_order_Thread_Tree(node.left_Child)

            # 当前节点没有左孩子
            if(node.left_Child == None):
                node.left_Thread = True
                node.left_Child = self.prev

            # 前驱节点没有右孩子
            if(self.prev != None and self.prev.right_Child == None):
                self.prev.right_Thread = True
                self.prev.right_Child = node

            self.prev = node

            # 递归线索化右子树
            self.middle_order_Thread_Tree(node.right_Child)

五、非递归遍历线索二叉树

前面我们说过线索化二叉树可以充分利用树中节点的空指针域,并且可以使用非递归方法对树进行遍历,那么如何使用非递归方法进行遍历呢?我们仍以中序遍历为例。

中序遍历线索二叉树步骤:

  • 找到当前节点左子树中最左边的节点
  • 打印输出节点数据
  • 只要当前节点的 right_Thread = True,找到其直接后继节点,当前节点指向其直接后继节点、并打印输出当前节点数据
  • 当前节点指向其右孩子,循环遍历

重复上述过程,直到当前节点为空。在遍历的时候,一定要记住我们是从左往右走的,从树最左边的节点一直向树最右边的节点推进,和中序遍历的思想一样。

代码如下:

   def middle_order_Print(self,p):
        while(p != None):

            # 一直找左孩子,直到第一个中序输出的节点
            while(p.left_Thread == False):
                p = p.left_Child

            print(p.data,end=' ')           # 打印节点数据

            # 如果 p.right_Thread = True,找到其后继节点
            while(p.right_Thread == True and p.right_Child != None):
                p = p.right_Child
                print(p.data,end=' ')

            # 往右推进,循环遍历
            p = p.right_Child

六、完整代码示例

'''
    function: 线索二叉树
    author: lemon
'''

class Node:
    def __init__(self,data):
        self.data = data
        self.left_Child = None
        self.right_Child = None
        self.left_Thread = False
        self.right_Thread = False



class Thread_Tree:
    def __init__(self):
        self.root = None
        self.prev = None


    # 构建二叉树
    def build_Tree(self, data):
        new_node = Node(data)
        if self.root == None:
            self.root = new_node
        else:
            current = self.root
            # 确定新节点应该哪一个节点的下一层
            while current != None:
                father_node = current
                if current.data > new_node.data:
                    current = current.left_Child
                else:
                    current = current.right_Child

            # 确定新节点应该为当前节点的左节点还是右节点
            if father_node.data > new_node.data:
                father_node.left_Child = new_node
            else:
                father_node.right_Child = new_node


    # 中序遍历线索化二叉树
    def middle_order_Thread_Tree(self,node):
        if(node != None):

            # 递归线索化左子树
            self.middle_order_Thread_Tree(node.left_Child)

            # 当前节点没有左孩子
            if(node.left_Child == None):
                node.left_Thread = True
                node.left_Child = self.prev

            # 前驱节点没有右孩子
            if(self.prev != None and self.prev.right_Child == None):
                self.prev.right_Thread = True
                self.prev.right_Child = node

            self.prev = node

            # 递归线索化右子树
            self.middle_order_Thread_Tree(node.right_Child)

    # 中序遍历该线索化二叉树
    def middle_order_Print(self,p):
        while(p != None):

            # 一直找左孩子,直到第一个中序输出的节点
            while(p.left_Thread == False):
                p = p.left_Child

            print(p.data,end=' ')           # 打印节点数据

            # 如果 p.right_Thread = True,找到其后继节点
            while(p.right_Thread == True and p.right_Child != None):
                p = p.right_Child
                print(p.data,end=' ')

            # 往右推进,循环遍历
            p = p.right_Child

def main():
    thread_tree = Thread_Tree()

    array = [7,4,1,5,16,8,11,12,15,9,2]
    for data in array:
        thread_tree.build_Tree(data)

    thread_tree.middle_order_Thread_Tree(thread_tree.root)
    thread_tree.middle_order_Print(thread_tree.root)


if __name__ == '__main__':
    main()

中序遍历结果:

1 2 4 5 7 8 9 11 12 15 16 	

总结

在学习线索二叉树的时候,一定要记住我们的最终目的是充分利用树中节点的空指针域,不使用递归算法就可以完成对树的遍历。为了达到这个目的,我们就需要充分利用树中节点的空指针域,并找到其前驱节点和后继节点,从而建立一种链接关系。在最后遍历的时候,我们就可以使用这种链接关系在不使用递归算法的情况下,完成对树的遍历。