python 数据结构 二叉树

数据结构之 二叉树:

什么是二叉树呢??
这个问题,是不可能回答的,这辈子都不可能的,请同学们自行google,
这篇博客,写的真的是图文并茂,推荐下
二叉查找树(一)之 图文解析 和 C语言的实现
不过我更推荐大家系统的学习一下《数据结构与算法》这门课。虽然枯燥而且貌似还用不上,谁知道呢。。一定是有用的,如果不想一直处于it链的底端,还是学习一下吧,算我求你们了。。。。
好!不扯了,真的推荐系统学习这门课程。特此推荐一本 《数据结构,xx考研xx》 具体不太清楚了,但是考研类的都大同小异吧,这本书比我本科课本好多了,没有那么多废话,说完概念就是练题。真的墙裂推荐!!!
好!现在就假设大家都有了二叉树的基本概念。
那么什么是二叉树呢?? 哈哈

建议:

在读文章的时候强烈建议大家找张纸和笔来,使用画图辅助理解!!!
数据结构本来就比较复杂,再加上逻辑步骤过多,我们的大脑只能记住7位步骤左右,所以还是那句话no picture say j8
另外:建议大家直接读源码吧,很多解释个人觉得越说越乱,不好意思!!

正文:

  • 构造二叉树(构造完全二叉树)
    • 改进版(构造任意二叉树)
  • 广度优先(层次遍历)
  • 深度优先(前,中,后,序遍历)
    • 递归实现遍历算法
    • 堆栈实现遍历算法(推荐

测试代码:

因为算法比较多所以先将测试代码奉上,请自行测试:

...
def main():
    #创建一个二叉树对象
    tree = B_Tree()
    #以此向树中添加节点,i == 3的情况表示,一个空节点,看完后面就明白了
    for i in range(10):
        if i == 3:
            i = None
        tree.add(i)
    #广度优先的层次遍历算法
    print([i.data for i in tree.floor_travel()])
    #前,中,后序 遍历算法(递归实现)
    print([i.data for i in tree.front_travel()])
    print([i.data for i in tree.middle_travel()])
    print([i.data for i in tree.back_travel()])
    print('------------------------------------')
    #前,中,后序 遍历算法(堆栈实现)
    print([i.data for i in tree.front_stank_travel()])
    print([i.data for i in tree.middle_stank_travel()])
    print([i.data for i in tree.back_stank_travel()])

if __name__ == "__main__":
    main()
...

构造二叉树

#二叉树的节点
class Node(object):
    def __init__(self,data=None,l_child=None, r_child=None):
        self.data = data
        self.l_child = l_child
        self.r_child = r_child

class B_Tree(object):
    def __init__(self, node=None):
        self.root = node
        
    def add(self, item=None):
        node = Node(item)
        #如果是空树,则直接添到根
        if not self.root:
            self.root = node
        else:
        #不为空,则按照 左右顺序 添加节点,这样构建出来的是一棵有序二叉树,且是完全二叉树
            my_queue = []
            my_queue.append(self.root)
            while True:
                cur_node = my_queue.pop(0)
                if not cur_node.l_child:
                    cur_node.l_child = node
                    return
                elif not cur_node.r_child:
                    cur_node.r_child = node
                    return
                else:
                    my_queue.append(cur_node.l_child)
                    my_queue.append(cur_node.r_child)

按照层次遍历的顺序将节点入队列,然后判断该节点是否有左孩子,没有的话则直接将新节点挂到下面,否则是右边,建议同学们画图理解。这就是网上的大多数版本,这样构建出来的二叉树是一个完全二叉树。那我们怎么才能构建一个更一般的二叉树呢。一个节点虽然是空节点但是我们不能用None来指代它,我们必须需要一个Node对象来占位所以我们使用下面的代码:

class B_Tree(object):
    ...    
    def add(self, item=None):
        #如果输入item是None 则表示一个空节点
        node = Node(data=item)
        #如果是空树,则直接添到根
        #此时判断空的条件为,
        if not self.root or self.root.data is None:
            self.root = node
        else:
        #不为空,则按照 左右顺序 添加节点
            my_queue = []
            my_queue.append(self.root)
            while True:
                cur_node = my_queue.pop(0)
                #即如果该当前节点为空节点则直接跳过它,起到一个占位的作用
                if cur_node.data is None:
                    continue
                if not cur_node.l_child:
                    cur_node.l_child = node
                    return
                elif not cur_node.r_child:
                    cur_node.r_child = node
                    return
                else:
                    my_queue.append(cur_node.l_child)
                    my_queue.append(cur_node.r_child)

可以看到,我们主要是使用了一个 内容为None 的Node对象来占位,代表该节点是个空节点,所以该节点不会有后代节点。从而达到构造更一般的二叉树的目的,但是我们构建时同样按照完全二叉树的顺序来构建树,且必须用None 来占位表示空节点。和上面的代码主要有俩处不同:

  1. if not self.root or self.root.data is None: 对于根节点来说,None 或者 内容为 None均表示空树,因为内容为None不能有后代节点,对于根来说不合理,所以让它相当于None ,不表示占位作用
  2. if cur_node.data is None: 此处已经是非根节点了,所以需要一个空节点来表示占位作用,当下次添加节点时遇到内容为None的节点直接什么都不做,跳过它,此时会将节点添加到下一个节点上

广度优先(层次遍历)

其实我们在构建二叉树的时候就是层次遍历的方式,这样构建出来的树,就是符合完全二叉树的,遍历算法如下:

...
    #层次遍历
    def floor_travel(self):
        #如果是空树则直接返回一个[]
        if not self.root or self.root.data is None:
            return []
        else:
            my_queue = []
            #构造个容器来返回遍历的结果
            re_queue = []
            my_queue.append(self.root)
            while my_queue:
                cur_node = my_queue.pop(0)
                re_queue.append(cur_node)
                if cur_node.l_child:
                    my_queue.append(cur_node.l_child)
                if cur_node.r_child:
                    my_queue.append(cur_node.r_child)
            return re_queue
...

前,中,后序遍历 递归实现

class B_Tree(object):
...
    #递归,前序遍历
    def front_travel(self):
        if not self.root or self.root.data is None:
            return []
        else:
            re_queue = []
            def loop(root):
                if not root:
                    return
                else:
                    #中  左  右
                    re_queue.append(root)
                    loop(root.l_child)
                    loop(root.r_child)
            loop(self.root)
            return re_queue
    
    #递归,中序遍历
    def middle_travel(self):
        if not self.root or self.root.data is None:
            return []
        else:
            re_queue = []
            def loop(root):
                if not root:
                    return
                else:
                    #左  中  右
                    loop(root.l_child)
                    re_queue.append(root)
                    loop(root.r_child)
            loop(self.root)
            return re_queue
    
    #递归,后序遍历
    def back_travel(self):
        if not self.root or self.root.data is None:
            return []
        else:
            re_queue = []
            def loop(root):
                if not root:
                    return
                else:
                    #左  右   中
                    loop(root.l_child)
                    loop(root.r_child)
                    re_queue.append(root)
            loop(self.root)
            return re_queue

...

如上,三种遍历方式,其实只有递归体内部的三条语句有点区别,用递归真的是很简单,但是递归会开辟大量的栈空间,会造成空间的浪费,更严重的是,如果递归深度太深将直接导致程序崩溃,所以我们推荐使用最后的使用堆栈的实现方式:

class B_Tree(object):
...
    #堆栈 ,前序遍历:
    def front_stank_travel(self):
        if not self.root or self.root.data is None:
            return []
        else:
            my_stank = []
            my_queue = []
            my_stank.append(self.root)
            while my_stank:
                cur_node = my_stank.pop()
                my_queue.append(cur_node)
                if cur_node.r_child:
                    my_stank.append(cur_node.r_child)
                if cur_node.l_child:
                    my_stank.append(cur_node.l_child)
            return my_queue

    #堆栈 ,中序遍历:
    def middle_stank_travel(self):
        if not self.root or self.root.data is None:
            return []
        else:
            my_stank = []
            my_queue = []
            tmp_list = []
            my_stank.append(self.root)
            while my_stank:
                cur_node = my_stank.pop()
                #my_queue.append(cur_node)
                if cur_node.r_child and cur_node.r_child not in my_stank:
                    my_stank.append(cur_node.r_child)
                if cur_node.l_child:
                    if cur_node not in tmp_list:
                        my_stank.append(cur_node)
                        tmp_list.append(cur_node)
                    else:
                        my_queue.append(cur_node)
                        tmp_list.remove(cur_node)
                        continue
                    my_stank.append(cur_node.l_child)
                else:
                    my_queue.append(cur_node)
            return my_queue
    
    def back_stank_travel(self):
        if not self.root or self.root.data is None:
            return []
        else:
            my_stank = []
            my_queue = []
            tmp_list = []
            my_stank.append(self.root)
            while my_stank:
                cur_node = my_stank[-1]
                if cur_node.r_child and cur_node not in tmp_list:
                    my_stank.append(cur_node.r_child)
                if cur_node.l_child and cur_node not in tmp_list:
                    my_stank.append(cur_node.l_child)
                if cur_node in tmp_list or (cur_node.l_child is None and cur_node.r_child is None):
                    my_queue.append(my_stank.pop())
                    if cur_node in tmp_list:
                        tmp_list.remove(cur_node)
                tmp_list.append(cur_node)
            return my_queue
...

说实话这些方法,写的我很是不满意,感觉很复杂,但是是自己一中午的成果,所以还是写在这分享下。再次强烈建议大家读函数的时候,结合画图来了解。其实网上还有篇博文,它的方法比较简单点 ,但是思想差不多,建议大家学习下python实现二叉树和它的七种遍历
好了,大家有没有发现我们每次遍历都需要做判断树是否为空,这样做真的很麻烦,我们完全可以使用装饰器来把代码装饰下,不了解装饰器的同学可以看下我之前的Python 装饰器系列,绝壁比这篇写的好~~(虚)
好那我们上装饰器修改后的代码:

import functools

def is_empty(func):
    @functools.wraps(func)    
    def wrapper(self):
        if not self.root or self.root.data is None:
            return []
        else:
            return func(self)
    return wrapper

class B_Tree(object):
...
    #递归,前序遍历
    @is_empty
    def front_travel(self):
        re_queue = []
        def loop(root):
            if not root:
                return
            else:
                #中  左  右
                re_queue.append(root)
                loop(root.l_child)
                loop(root.r_child)
        loop(self.root)
        return re_queue

    #堆栈 ,前序遍历:
    @is_empty
    def front_stank_travel(self):
        my_stank = []
        my_queue = []
        my_stank.append(self.root)
        while my_stank:
            cur_node = my_stank.pop()
            my_queue.append(cur_node)
            if cur_node.r_child:
                my_stank.append(cur_node.r_child)
            if cur_node.l_child:
                my_stank.append(cur_node.l_child)
        return my_queue
...

使用测试代码同样可以完美输出。。。

总结:

  • 构建任意的二叉树,而非完全二叉树
  • 递归实现二叉树遍历,但是空间浪费严重
  • 推荐使用堆栈的方式实现二叉树的遍历
  • 使用装饰器优化代码
  • 墙裂建议使用画图辅助!!!!

声明:

本人也是python 小白,加上数据结构本来就比较复杂,如果上述内容有讲的不对的地方还请各位批评指点。将不胜感激,再次感谢~~~
讲的不好,还请大家见谅~~~~

参考资料:

    原文作者:snailpy
    原文地址: https://www.jianshu.com/p/b09a6b6c2f4b
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞