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


Posted in Python onAugust 09, 2019

前言

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

实现

用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字典的特性,将字典当成一个树结构来处理也是可以的。事实上,很多人是这样做的。下图测试展现了树结点的实现:

用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

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

新添加的结点按顺序做为结点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实现二叉树、二叉树非递归遍历及绘制的例子

此时, 可以借住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层),则从上往下画其左右子树的坐标表达如下:

左子树:

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

右子树:

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

对应代码实现如下:

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:

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

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

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

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

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

Python 相关文章推荐
python中循环语句while用法实例
May 16 Python
Python之读取TXT文件的方法小结
Apr 27 Python
Pycharm在创建py文件时,自动添加文件头注释的实例
May 07 Python
Python实现字典排序、按照list中字典的某个key排序的方法示例
Dec 18 Python
在Python中,不用while和for循环遍历列表的实例
Feb 20 Python
Python3实现的旋转矩阵图像算法示例
Apr 03 Python
Python 实现自动获取种子磁力链接方式
Jan 16 Python
Python图像处理库PIL的ImageDraw模块介绍详解
Feb 26 Python
python matplotlib模块基本图形绘制方法小结【直线,曲线,直方图,饼图等】
Apr 26 Python
python 实现汉诺塔游戏
Nov 28 Python
python实现发送邮件
Mar 02 Python
python基于scrapy爬取京东笔记本电脑数据并进行简单处理和分析
Apr 14 Python
基于python二叉树的构造和打印例子
Aug 09 #Python
Python re 模块findall() 函数返回值展现方式解析
Aug 09 #Python
Django ORM 自定义 char 类型字段解析
Aug 09 #Python
解决使用export_graphviz可视化树报错的问题
Aug 09 #Python
Django中自定义admin Xadmin的实现代码
Aug 09 #Python
python输出决策树图形的例子
Aug 09 #Python
Python实现决策树并且使用Graphviz可视化的例子
Aug 09 #Python
You might like
PHP 高手之路(一)
2006/10/09 PHP
PHP IN_ARRAY 函数使用注意事项
2010/07/24 PHP
php代码审计比较有意思的例子
2014/05/07 PHP
Php连接及读取和写入mysql数据库的常用代码
2014/08/11 PHP
ThinkPHP模板之变量输出、自定义函数与判断语句用法
2014/11/01 PHP
详解WordPress中简码格式标签编写的基本方法
2015/12/22 PHP
推荐一些非常不错的javascript学习资源站点
2007/08/29 Javascript
Javascript 陷阱 window全局对象
2008/11/26 Javascript
jQuery + Flex 通过拖拽方式动态改变图片的代码
2011/08/03 Javascript
JS工作中的小贴士之”闭包“与事件委托的”阻止冒泡“
2016/06/16 Javascript
Angular2库初探
2017/03/01 Javascript
vue+axios实现登录拦截的实例代码
2017/05/22 Javascript
JS查找数组中重复元素的方法详解
2017/06/14 Javascript
nginx部署访问vue-cli搭建的项目的方法
2018/02/12 Javascript
在vue项目中引入highcharts图表的方法(详解)
2018/03/05 Javascript
基于vue中keep-alive缓存问题的解决方法
2018/09/21 Javascript
js模拟实现百度搜索
2020/06/28 Javascript
jQuery带控制按钮轮播图插件
2020/07/31 jQuery
Node.js 中判断一个文件是否存在
2020/08/24 Javascript
electron踩坑之remote of undefined的解决
2020/10/06 Javascript
如何搜索查找并解决Django相关的问题
2014/06/30 Python
Django Highcharts制作图表
2016/08/27 Python
对python的bytes类型数据split分割切片方法
2018/12/04 Python
使用python的pandas为你的股票绘制趋势图
2019/06/26 Python
Python Tkinter图形工具使用方法及实例解析
2020/06/15 Python
Python操作Word批量生成合同的实现示例
2020/08/28 Python
python爬虫线程池案例详解(梨视频短视频爬取)
2021/02/20 Python
在线吉他课程,学习如何弹吉他:Fender Play
2019/02/28 全球购物
Made in Design英国:设计家具、照明、家庭装饰和花园家具
2019/09/24 全球购物
南京某软件公司的.net面试题
2015/11/30 面试题
中职应届生会计求职信
2013/10/23 职场文书
医学专业毕业生推荐信
2013/11/14 职场文书
纪念九一八事变演讲稿1000字
2014/09/14 职场文书
工作试用期自我评价
2015/03/10 职场文书
2016年暑期社会实践活动总结报告
2016/04/06 职场文书
Python基本数据类型之字符串str
2021/07/21 Python