侯体宗的博客
  • 首页
  • Hyperf版
  • beego仿版
  • 人生(杂谈)
  • 技术
  • 关于我
  • 更多分类
    • 文件下载
    • 文字修仙
    • 中国象棋ai
    • 群聊
    • 九宫格抽奖
    • 拼图
    • 消消乐
    • 相册

用Python实现二叉树、二叉树非递归遍历及绘制的例子

Python  /  管理员 发布于 7年前   207

前言

关于二叉树的实现与遍历,网上已经有很多文章了,包括C, C++以及JAVA等。鉴于python做为脚本语言的简洁性,这里写一篇小文章用python实现二叉树,帮助一些对数据结构不太熟悉的人快速了解下二叉树。本文主要通过python以非递归形式实现二叉树构造、前序遍历,中序遍历,后序遍历,层次遍历以及求二叉树的深度及叶子结点数。其他非递归形式的遍历,想必大多人应该都很清楚,就不再声明。如果你用C或者C++或者其他高级语言写过二叉树或者阅读过相关方面代码,应该知道二叉树的非递归遍历避不开通过栈或者队列实现。是的,python也一样。但是python自带的list功能很强大,即可以当stack 又可以当成queue。 这样用python实现二叉树就可以减少了对栈或者队列的声明及定义。

实现

二叉树的结点的实现

如上图1的的二叉树,要想实现二叉树。首先应该先声明一个二叉树结点,包括它的元素及左右子结点,这个在C/C++也是一样的。在python里, 可以通过类声明一个结点,如下:

class BiNode(object): """class BiNode provide interface to set up a BiTree Node and to interact""" def __init__(self, element=None, left=None, right=None):  """set up a node """  self.element = element  self.left = left  self.right = right def get_element(self):  """return node.element"""  return self.element def dict_form(self):  """return node as dict form"""  dict_set = {   "element": self.element,   "left": self.left,   "right": self.right,  }  return dict_set def __str__(self):  """when print a node , print it's element"""  return str(self.element)

上述的dict_form interface是将结点以python字典的形式呈现出来,方便后面将树打包成字典。另外说明下由于python字典的特性,将字典当成一个树结构来处理也是可以的。事实上,很多人是这样做的。下图测试展现了树结点的实现:

二叉树初始化

实现了二叉树结点,接下来实现二叉树.首先对二叉树进行初始化,代码如下:

class BiTree: """class BiTree provide interface to set up a BiTree and to interact""" def __init__(self, tree_node=None):  """set up BiTree from BiNode and empty BiTree when nothing is passed"""  self.root = tree_node`

上面代码很简单,就是对树通过一个传进来的结点进行初始化,如果参数为空则初始化为一个空树。

顺序构造二叉树

那么我如果想通过一个列表元素按顺序实现树的构造或者通过字典进行构造呢?

先说下用一个列表元素按顺序构造。

假设现在已经存在一颗二叉树,如下图2

新添加的结点按顺序做为结点2的左子结点(这里不考虑像二叉查找树等的插入要求)。基本插入方法如下:

判断根结点是否存在,如果不存在则插入根结点。否则从根结点开始,判断左子结点是否存在,如果不存在插入, 如果左子结点存在判断右结点,不存在插入。如果左右结点存在,再依次遍历左右子结点的子结点,直到插入成功。

上述的方法类似于层次遍历,体现了广度搜索优先的思想。因此从代码实现上,很显然需要一个队列对子结点进行入队与出队。在python上这很简单,一个list 就实现了,代码如下:

 def add_node_in_order(self, element):  """add a node to existent BiTree in order"""  node = BiNode(element)  if self.root is None:   self.root = node  else:   node_queue = list()   node_queue.append(self.root)   while len(node_queue):    q_node = node_queue.pop(0)    if q_node.left is None:     q_node.left = node     break    elif q_node.right is None:     q_node.right = node     break    else:     node_queue.append(q_node.left)     node_queue.append(q_node.right) def set_up_in_order(self, elements_list):  """set up BiTree from lists of elements in order """  for elements in elements_list:   self.add_node_in_order(elements)

set_up_in_order()实现了通过列表对树进行顺序构造。

从字典初始化构造二叉树

当然你会发现,用上述方法构造的二叉树永远都是完全二叉树。实际情况下,我们需要初始化像图3这样的一棵不规则的二叉树,怎么办?

此时, 可以借住python的字典对树进行构造,参考的node的dict_form,约定”element”的key_value是结点值,“left”,“right”的key_value也是一个字典代表左右子树,如果为空则为None 。为方便书写,对于一个结点除了element不能缺外, 左右子树不存在时相应key可以缺失。同时对于叶结点,可以省略写成相应的元素值而不用继续构造一个字典。此时可以通过类似如下字典初始一棵二叉树表示,如下:

dict_tree = { "element": 0, "left": {  "element": 1,  "left": {   "element": 3,   "left": 6,   "right": 7,  } }, "right": {  "element": 2,  "left": 4,  "right": {   "element": 5,   "left": 8,   "right": 9,  }, },}

上述字典表示的二叉树即为图3所示

通过字典进行初始树,可以借用层次遍历的思想实现树的构造,本质上其实就是对树进行一个非递归实现的拷贝,代码实现如下:

def set_up_from_dict(self, dict_instance):  """set up BiTree from a dict_form tree using level traverse, or call it copy """  if not isinstance(dict_instance, dict):   return None  else:   dict_queue = list()   node_queue = list()   node = BiNode(dict_instance["element"])   self.root = node   node_queue.append(node)   dict_queue.append(dict_instance)   while len(dict_queue):    dict_in = dict_queue.pop(0)    node = node_queue.pop(0)    # in dict form, the leaf node might be irregular, like compressed to element type    # Thus , all this case should be solved out respectively    if isinstance(dict_in.get("left", None), (dict, int, float, str)):     if isinstance(dict_in.get("left", None), dict):      dict_queue.append(dict_in.get("left", None))      left_node = BiNode(dict_in.get("left", None)["element"])      node_queue.append(left_node)     else:      left_node = BiNode(dict_in.get("left", None))     node.left = left_node    if isinstance(dict_in.get("right", None), (dict, int, float, str)):     if isinstance(dict_in.get("right", None), dict):      dict_queue.append(dict_in.get("right", None))      right_node = BiNode(dict_in.get("right", None)["element"])      node_queue.append(right_node)     else:      right_node = BiNode(dict_in.get("right", None))     node.right = right_node

将二叉树打包成字典

往往我们也需要将一颗二叉树用字典的形式表示出来, 其方法与从字典初始化一棵二叉树一样,代码实现如下:

def pack_to_dict(self):  """pack up BiTree to dict form using level traversal"""  if self.root is None:   return None  else:   node_queue = list()   dict_queue = list()   node_queue.append(self.root)   dict_pack = self.root.dict_form()   dict_queue.append(dict_pack)   while len(node_queue):    q_node = node_queue.pop(0)    dict_get = dict_queue.pop(0)    if q_node.left is not None:     node_queue.append(q_node.left)     dict_get["left"] = q_node.left.dict_form()     dict_queue.append(dict_get["left"])    if q_node.right is not None:     node_queue.append(q_node.right)     dict_get["right"] = q_node.right.dict_form()     dict_queue.append(dict_get["right"])  return dict_pack

求二叉树的深度

求二叉树的深度或者高度的非递归实现,本质上可以通过层次遍历实现,方法如下:

1. 如果树为空,返回0 。

2. 从根结点开始,将根结点拉入列。

3. 当列非空,记当前队列元素数(上一层节点数)。将上层节点依次出队,如果左右结点存在,依次入队。直至上层节点出队完成,深度加一。继续第三步,直至队列完全为空。

代码实现如下:

 def get_depth(self):  """method of getting depth of BiTree"""  if self.root is None:   return 0  else:   node_queue = list()   node_queue.append(self.root)   depth = 0   while len(node_queue):    q_len = len(node_queue)    while q_len:     q_node = node_queue.pop(0)     q_len = q_len - 1     if q_node.left is not None:      node_queue.append(q_node.left)     if q_node.right is not None:      node_queue.append(q_node.right)    depth = depth + 1   return depth

前序遍历

二叉树的前序,中序,后序称体现的是深度优先搜索的思想。

本质上它们的方法其实是一样的。

先说前序遍历, 方法如下:

1. 如果树为空,返回None 。

2. 从根结点开始,如果当前结点左子树存在,则打印结点,并将该结点入栈。让当前结点指向左子树,继续步骤2直至当前结点左子树不存在。

3. 将当结点打印出来,如果当前结点的右子树存在,当前结点指向右子树,继续步骤2。否则进行步骤4.

4. 如果栈为空则遍历结束。若非空,从栈里面pop一个节点,从当前结点指向该结点的右子树。如果右子树存在继续步骤2,不存在继续步骤4直至结束。

以图2为例,用N代表结点。

1.N0 ,N1依次打印,并且入栈。

2. 打印N3,

3. N3右子树不存在,N1出栈,遍历N1右子树N4

4. N4的左子树不存在,打印N4。N4右子树不存在,N0出栈,指向其右子树N2

5. N2的左子树不存在,打印N2,判断右子树及栈空结束

代码实现如下:

def pre_traversal(self):  """method of traversing BiTree in pre-order"""  if self.root is None:   return None  else:   node_stack = list()   output_list = list()   node = self.root   while node is not None or len(node_stack):    # if node is None which means it comes from a leaf-node' right,    # pop the stack and get it's right node.    # continue the circulating like this    if node is None:     node = node_stack.pop().right     continue    # save the front node and go next when left node exists    while node.left is not None:     node_stack.append(node)     output_list.append(node.get_element())     node = node.left    output_list.append(node.get_element())    node = node.right  return output_list

中序遍历

中序遍历的思想基本与前序遍历一样,只是最开始结点入栈时先不打印。只打印不存在左子树的当前结点,然后再出栈遍历右子树前再打印出来,代码实现如下:

 def in_traversal(self):  """method of traversing BiTree in in-order"""  if self.root is None:   return None  else:   node_stack = list()   output_list = list()   node = self.root   while node is not None or len(node_stack):    # if node is None which means it comes from a leaf-node' right,    # pop the stack and get it's right node.    # continue the circulating like this    if node is None:     node = node_stack.pop()     # in in-order traversal, when pop up a node from stack , save it     output_list.append(node.get_element())     node = node.right     continue    # go-next when left node exists    while node.left is not None:     node_stack.append(node)     node = node.left    # save the the last left node    output_list.append(node.get_element())    node = node.right  return output_list

后序遍历

后序遍历的实现思想与前序、中序一样。有两种实现方式。

先说第一种,同中序遍历,只是中序时从栈中pop出一个结点打印,并访问当前结点的右子树。 后序必须在访问完右子树完在,在打印该结点。因此可先

看栈顶点是否被访问过,如果访问过,即已经之前已经做了其右子树的访问因此可出栈,并打印,继续访问栈顶点。如果未访问过,则对该点的访问标记置为访问,访问该点右子树。可以发现,相对于前序与中序,后序的思想是一致的,只是需要多一个存储空间来表示结点状态。python代码实现如下:

 def post_traversal1(self):  """method of traversing BiTree in in-order"""  if self.root is None:   return None  else:   node_stack = list()   output_list = list()   node = self.root   while node is not None or len(node_stack):    # if node is None which means it comes from a leaf-node' right,    # pop the stack and get it's right node.    # continue the circulating like this    if node is None:     visited = node_stack[-1]["visited"]     # in in-order traversal, when pop up a node from stack , save it     if visited:      output_list.append(node_stack[-1]["node"].get_element())      node_stack.pop(-1)     else:      node_stack[-1]["visited"] = True      node = node_stack[-1]["node"]      node = node.right     continue    # go-next when left node exists    while node.left is not None:     node_stack.append({"node": node, "visited": False})     node = node.left    # save the the last left node    output_list.append(node.get_element())    node = node.right  return output_list

另外,后续遍历还有一种访问方式。考虑到后续遍历是先左子树,再右子树再到父结点, 倒过来看就是先父结点, 再右子树再左子树。 是不是很熟悉, 是的这种遍历方式就是前序遍历的镜像试,除了改变左右子树访问顺序连方式都没变。 再将输出的结果倒序输出一遍就是后序遍历。 同样该方法也需要额外的空间存取输出结果。python代码如下:

def post_traversal2(self):  """method of traversing BiTree in post-order"""  if self.root is None:   return None  else:   node_stack = list()   output_list = list()   node = self.root   while node is not None or len(node_stack):    # if node is None which means it comes from a leaf-node' left,    # pop the stack and get it's left node.    # continue the circulating like this    if node is None:     node = node_stack.pop().left     continue    while node.right is not None:     node_stack.append(node)     output_list.append(node.get_element())     node = node.right    output_list.append(node.get_element())    node = node.left  return output_list[::-1]

求叶子节点

求叶子节点有两种方法,一种是广度搜索优先,即如果当前节点存在左右子树将左右子树入队。如果当前节点不存在子树,则该节点为叶节点。继续出队访问下一个节点。直至队列为空,这个方法留给读者去实现。

另外一种方法是,用深度搜索优先。 采用前序遍历,当判断到一个结点不存在左右子树时叶子结点数加一。代码实现如下:

 def get_leaf_num(self):  """method of getting leaf numbers of BiTree"""  if self.root is None:   return 0  else:   node_stack = list()   node = self.root   leaf_numbers = 0   # only node exists and stack is not empty that will do this circulation   while node is not None or len(node_stack):    if node is None:     """node is None then pop the stack and get the node.right"""     node = node_stack.pop().right     continue    while node.left is not None:     node_stack.append(node)     node = node.left    # if there is not node.right, leaf_number add 1    node = node.right    if node is None:     leaf_numbers += 1   return leaf_numbers

二叉树的可视化

到此, 除了树的结点删除(这个可以留给读者去尝试), 这里已经基本完成二叉树的构造及遍历接口。 但你可能真正在意的是如何绘制一颗二叉树。 接下来,本节将会通过python matplotlib.annotate绘制一颗二叉树。

要绘制一棵二叉树,首先需要对树中的任意结点给出相应相对坐标(axis_max: 1)。对于一棵已知树, 已知深度 dd ,那么可以设初始根结点坐标为(1/2,1−12d)(1/2,1−12d). (这个设置只是为了让根结点尽量中间往上且不触及axis)

假设已经知道父结点的坐标(xp,yp)(xp,yp), 当前层数ll(记根节点为第0层),则从上往下画其左右子树的坐标表达如下:

左子树:

右子树:

对应代码实现如下:

def get_coord(coord_prt, depth_le, depth, child_type="left"): if child_type == "left": x_child = coord_prt[0] - 1 / (2 ** (depth_le + 1)) elif child_type == "right": x_child = coord_prt[0] + 1 / (2 ** (depth_le + 1)) else: raise Exception("No other child type") y_child = coord_prt[1] - 1 / depth return x_child, y_child

如果知道当前结点与父结点坐标,即可以通过plt.annotate进行结点与箭标绘制,代码实现如下:

def plot_node(ax, node_text, center_point, parent_point): ax.annotate(node_text, xy=parent_point, xycoords='axes fraction', xytext=center_point, textcoords='axes fraction',    va="bottom", ha="center", bbox=NODE_STYLE, arrowprops=ARROW_ARGS)

已知树深度, 当前结点及当前结点所在层数,则可以通过上述计算方式计算左右子树的结点坐标。 使用层次遍历,即可遍历绘制整棵树。代码实现如下:

 def view_in_graph(self):  """use matplotlib.pypplot to help view the BiTree """  if self.root is None:   print("An Empty Tree, Nothing to plot")  else:   depth = self.get_depth()   ax = node_plot.draw_init()   coord0 = (1/2, 1 - 1/(2*depth))   node_queue = list()   coord_queue = list()   node_plot.plot_node(ax, str(self.root.get_element()), coord0, coord0)   node_queue.append(self.root)   coord_queue.append(coord0)   cur_level = 0   while len(node_queue):    q_len = len(node_queue)    while q_len:     q_node = node_queue.pop(0)     coord_prt = coord_queue.pop(0)     q_len = q_len - 1     if q_node.left is not None:      xc, yc = node_plot.get_coord(coord_prt, cur_level + 1, depth, "left")      element = str(q_node.left.get_element())      node_plot.plot_node(ax, element, (xc, yc), coord_prt)      node_queue.append(q_node.left)      coord_queue.append((xc, yc))     if q_node.right is not None:      xc, yc = node_plot.get_coord(coord_prt, cur_level + 1, depth, "right")      element = str(q_node.right.get_element())      node_plot.plot_node(ax, element, (xc, yc), coord_prt)      node_queue.append(q_node.right)      coord_queue.append((xc, yc))    cur_level += 1   node_plot.show()

最后, 可以对如下的一颗二叉树进行测试:

dict_tree2 = { "element": 0, "left": {  "element": 1,  "left": 3,  "right": {   "element": 4,   "left": 5,   "right": 6,  }, }, "right": {  "element": 2,  "left": 7,  "right": {   "element": 8,   "left": {    "element": 9,    "left": 10,    "right": 11,   },  }, },}

其绘制结果如下图4:

遍历及深度叶子数 ,输出结果如下:

至此, 本文结。 Have fun reading , 需望此文可以帮助你了解二叉树的结构

以上这篇用Python实现二叉树、二叉树非递归遍历及绘制的例子就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持。


  • 上一条:
    python selenium爬取斗鱼所有直播房间信息过程详解
    下一条:
    基于python二叉树的构造和打印例子
  • 昵称:

    邮箱:

    0条评论 (评论内容有缓存机制,请悉知!)
    最新最热
    • 分类目录
    • 人生(杂谈)
    • 技术
    • linux
    • Java
    • php
    • 框架(架构)
    • 前端
    • ThinkPHP
    • 数据库
    • 微信(小程序)
    • Laravel
    • Redis
    • Docker
    • Go
    • swoole
    • Windows
    • Python
    • 苹果(mac/ios)
    • 相关文章
    • 在python语言中Flask框架的学习及简单功能示例(0个评论)
    • 在Python语言中实现GUI全屏倒计时代码示例(0个评论)
    • Python + zipfile库实现zip文件解压自动化脚本示例(0个评论)
    • python爬虫BeautifulSoup快速抓取网站图片(1个评论)
    • vscode 配置 python3开发环境的方法(0个评论)
    • 近期文章
    • 智能合约Solidity学习CryptoZombie第四课:僵尸作战系统(0个评论)
    • 智能合约Solidity学习CryptoZombie第三课:组建僵尸军队(高级Solidity理论)(0个评论)
    • 智能合约Solidity学习CryptoZombie第二课:让你的僵尸猎食(0个评论)
    • 智能合约Solidity学习CryptoZombie第一课:生成一只你的僵尸(0个评论)
    • 在go中实现一个常用的先进先出的缓存淘汰算法示例代码(0个评论)
    • 在go+gin中使用"github.com/skip2/go-qrcode"实现url转二维码功能(0个评论)
    • 在go语言中使用api.geonames.org接口实现根据国际邮政编码获取地址信息功能(1个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf分页文件功能(0个评论)
    • gmail发邮件报错:534 5.7.9 Application-specific password required...解决方案(0个评论)
    • 欧盟关于强迫劳动的规定的官方举报渠道及官方举报网站(0个评论)
    • 近期评论
    • 122 在

      学历:一种延缓就业设计,生活需求下的权衡之选中评论 工作几年后,报名考研了,到现在还没认真学习备考,迷茫中。作为一名北漂互联网打工人..
    • 123 在

      Clash for Windows作者删库跑路了,github已404中评论 按理说只要你在国内,所有的流量进出都在监控范围内,不管你怎么隐藏也没用,想搞你分..
    • 原梓番博客 在

      在Laravel框架中使用模型Model分表最简单的方法中评论 好久好久都没看友情链接申请了,今天刚看,已经添加。..
    • 博主 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 @1111老铁这个不行了,可以看看近期评论的其他文章..
    • 1111 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 网站不能打开,博主百忙中能否发个APP下载链接,佛跳墙或极光..
    • 2016-10
    • 2016-11
    • 2018-04
    • 2020-03
    • 2020-04
    • 2020-05
    • 2020-06
    • 2022-01
    • 2023-07
    • 2023-10
    Top

    Copyright·© 2019 侯体宗版权所有· 粤ICP备20027696号 PHP交流群

    侯体宗的博客