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

参考资料:

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,837评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,551评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,417评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,448评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,524评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,554评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,569评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,316评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,766评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,077评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,240评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,912评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,560评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,176评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,425评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,114评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,114评论 2 352

推荐阅读更多精彩内容