• 数据结构中的树的结点和机器学习中决策树的结点有一个很大的不同就是,数据结构中的树的每个叶结点都是独立的。

  • 树的高度(Height)指叶结点的最大层树(不包含根结点)

一、树的建立

树可以这样定义:一棵树由一系列结点和一系列连接结点的边组成

树也可以这样定义: 一棵树有根和其他子树组成,这些子树也是树

在python,使用的定义都是后者。

1.1.list of lists

对于一个list:['q',[],[]],代表的是一棵树(子树),’q’是根结点,[],[]分别左右两个子结点。所以,有一个左结点为'a'的树可以写为['q',['a',[],[]],[]]

my_tree = ['a', ['b', ['d',[],[]], ['e',[],[]] ], ['c',['f',[],[]], []] ]

在这种定义的情况下,根结点为my_tree[0],左结点为my_tree[1],右结点为my_tree[2]

def binary_tree(r):
return [r, [], []]
def insert_left(root, new_branch):
t = root.pop(1) # The left child position
if len(t) > 1: # if not empty
# The origin left child turn to be the left child of new_branch
root.insert(1, [new_branch, t, []])
else:
root.insert(1, [new_branch, [], []])
return root
def insert_right(root, new_branch):
t = root.pop(2)
if len(t) > 1:
root.insert(2, [new_branch, [], t])
else:
root.insert(2, [new_branch, [], []])
return root
def get_root_val(root):
return root[0]
def set_root_val(root, new_val):
root[0] = new_val
def get_left_child(root):
return root[1]
def get_right_child(root):
return root[2]
x = binary_tree('a')
insert_left(x,'b')
insert_right(x,'c')
insert_right(get_right_child(x), 'd') # important!
insert_left(get_right_child(get_right_child(x)), 'e')
['d', ['e', [], []], []]

1.2 Nodes and References

这种思想主要是构建一个类,并采用嵌套的方法,生成左右结点(子树)

class BinaryTree:
def __init__(self, root):
self.key = root
self.left_child = None
self.right_child = None
def insert_left(self, new_node):
if self.left_child == None:
self.left_child = BinaryTree(new_node)
else:
t = BinaryTree(new_node)
t.left_child = self.left_child
self.left_child = t
def insert_right(self, new_node):
if self.right_child == None:
self.right_child = BinaryTree(new_node)
else:
t = BinaryTree(new_node)
t.right_child = self.right_child
self.right_child = t
def get_right_child(self):
return self.right_child
def get_left_child(self):
return self.left_child
def set_root_val(self,obj):
self.key = obj
def get_root_val(self):
return self.key
x = BinaryTree('a')
x.insert_left('b')
x.insert_right('c')
x.get_right_child().insert_right('f')
x
<__main__.BinaryTree at 0x105930278>

这两种构造方法都没有办法返回到父结点,具体见 三、解析树

二、基于二叉堆的优先队列

之前的队列(queue)都是先进先出的数据结构。然而在优先队列中,在队列里的数据都有一个优先级标签。 在优先级队列中,队列的项的逻辑顺序由他们的优先级决定。最高优先级在队列的前面,最低优先级在项的后面。

实现优先级队列的经典方法是使用成为二叉堆的数据结构,它可以在\(O(log(n))\)时间中排队和取出队列

PS:二叉堆(binary heap)和stack的那个堆不是同一回事,这个堆(heap)在结构上是一棵完全二叉树,在实现上使用的数组list,采用数组的index值表示父结点及子结点。二叉堆有两个常见的变体:最小堆(其中最小的键总是在前面)和最大堆。

由于二叉堆要是一个完全二叉树,所以为了保证log(n)的时间量,必须保证树是平衡的。

如图是一个二叉堆,堆的结构类似树,实现方式由list实现。list的第一个元素是0,至于为什么要这样呢?因为树上的每一个元素都有对应的index,要满足左右结点分别是2*p&2*p+1,如果索引index是从0开始,则不满足。

二叉堆的属性是一个很重要的东西,二叉堆的所有操作基本都基于它的属性:每个结点的父结点都比它本身要小 (最小堆)

class BinHeap:
def __init__(self):
self.heap_list = [0]
self.current_size = 0

2.1 插入(insert)操作

在二叉堆中插入一个项,过程是:将这个要插入的项置于二叉堆list的最后一项,由于二叉堆要保持它的属性(每个结点的父结点都比它本身要小),这个项与其父结点进行比较,小的在上,大的在下进行可能的交换,这种比较要一直持续到根结点。

这种交换由于是将待插入的项从下往上交换,所以叫做percolate_up

    def perc_up(self, i):
while i//2 > 0:
if self.heap_list[i] < self.heap_list[i//2]:
self.heap_list[i],self.heap_list[i//2]=self.heap_list[i//2],self.heap_list[i]
i = i//2
def insert(self, k):
self.heap_list.append(k)
self.current_size += 1
self.perc_up(self, self.current_size)

2.2 删除(出队)操作

由于我们研究的是最小堆,所以出队操作是移出值最小的那个结点,即根结点。

具体操作:移除根结点后,将list的最后一个项放置在根结点的位置上,然后根据二叉堆的属性进行percolate_down操作(即根结点的项依次向下进行比较)

    def perc_down(self, i):
while (i * 2) <= self.current_size:
mc = self.min_child(i)
if self.heap_list[i] > self.heap_list[mc]:
self.heap_list[i],self.heap_list[mc]=self.heap_list[mc],self.heap_list[i]
i = mc
# 返回左右结点小的那一个
def min_child(self, i):
if i*2+1 > self.current_size:
return i*2
else:
if self.heap_list[i*2] < self.heap_list[i*2+1]:
return i*2
else:
return i*2+1
def del_min(self):
# The first element is 0
ret_val = self.heap_list[1]
self.heap_list[1] = self.heap_list[self.current_size]
self.current_size = self.current_size - 1
self.heap_list.pop()
self.perc_down(1)
return ret_val

2.3 建立二叉堆

两种想法:

1.可以将list直接进行排序,最低花费\(O(nlogn)\),得出的list肯定是个二叉堆

2.对于一个list的每一个项从左往右进行percolate_down操作,时间复杂度\(O(n)\)

    def build_heap(self, a_list):
i = len(a_list)//2
self.current_size = len(a_list)
self.heap_list = [0] + a_list[:]
while(i > 0):
self.perc_down(i)
i = i - 1

三、解析树(parse tree)

把解析表达式表示成树的形式,如:\(((7+3)*(5-2))\)可以表示成

解析树的形成可以定义为以下四个规则:

1.如果遇到(,意味着有一个新的表达式,则创建一棵新子树(添加一个新子结点,且再创建这个新结点的左孩子,将处理目标移到这个左孩子上)

2.如果遇到[+,-,*,/]操作符(operators),将此操作符置于现结点,同时创建一个右孩子,将处理目标移到这个右孩子上

3.如果遇到一个number,将这个number置于到现结点,将处理目标返回到父结点

4.如果遇到),将处理目标返回到父结点。

def build_parse_tree(fp_exp):
fp_list = fp_exp.split()
p_stack = Stack()
e_tree = BinaryTree('')
p_stack.push(e_tree)
current_tree = e_tree
for i in fp_list:
if i == '(':
current_tree.insert_left('')
p_stack.push(current_tree)
current_tree = current_tree.get_left_child()
elif i not in ['+','-','*','/',')']:
current_tree.set_root_val(int(i))
parent = p_stack.pop()
current_tree = parent
elif i in ['+','-','*','/']:
current_tree.set_root_val(i)
current_tree.insert_right('')
p_stack.push(current_tree)
current_tree = current_tree.get_right_child()
elif i == ')':
current_tree = p_stack.pop()
else:
raise ValueError
return e_tree

由于之间构造的二叉树类无法访问其父结点,所以这里要新建一个堆在储存当前的父结点。

在遇到(和operaters的这两个操作都需要将当前操作结点移到左右孩子上,所以这两个操作需要将父结点入堆。

四、树的遍历

三种遍历方式:先序遍历(preorder,中左右), 中序遍历(inorder,左中右), 后序遍历(postorder,左右中),他们的不同在于每个结点访问的顺序。

先,中,后都是对于根结点来说的。

在python中两种方法进行遍历:

1.外界函数

2.类中函数

事实证明外界函数处理遍历更加有效率,因为在大多数情况下,遍历都会跟别的操作混合在一起,所以外界函数修改更方便。

def preorder(tree):
if tree:
print(tree.get_root_val())
preorder(tree.get_left_child())
preorder(tree.get_right_child())
def preorder(self):   # 类中函数
print(self.key)
if self.left_child:
self.left.preorder()
if self.right_child:
self.right.preorder()
def postorder(tree):
if tree:
postorder(tree.get_left_child())
postorder(tree.get_right_child())
print(tree.get_root_val)
def inorder(tree):
if tree:
inorder(tree.get_left_child())
print(tree.get_root_val)
inorder(tree.get_right_child())

4.1 解析树evalute

import operator
def postorder_eval(tree):
opers = {'+':operator.add,'-':operator.sub,'*':operator.mul,
'/':operator.truediv}
res1 = None
res2 = None
if tree:
res1 = postorder_eval(tree.get_left_child())
res2 = postorder_eval(tree.get_right_child())
if res1 and res2:
return opers[tree.get_root_val()](res1,res2)
else:
return tree.get_root_val()

五、二叉搜索树(BST)

二叉搜索树的实现主要依赖于它的性质(BST property):parent的key值比左结点大,比右结点小。

class TreeNode:
def __init__(self, key, val, left=None, right=None, parent=None):
self.key = key
self.payload = val
self.left_child = left
self.right_child = right
self.parent = parent
def has_left_child(self):
return self.left_child
def has_right_child(self):
return self.right_child
def is_left_child(self):
return self.parent and self.parent.left_child == self
def is_right_child(self):
return self.parent and self.parent.right_child == self
def is_root(self):
return not self.parent
def is_leaf(self):
return not (self.right_child or self.left_child)
def has_any_children(self):
return self.right_child or self.left_child
def has_both_children(self):
return self.right_child and self.left_child
def replace_node_data(self, key, value, lc, rc):
self.key = key
self.payload = value
self.left_child = lc
self.right_child = rc
if self.has_left_child():
self.left_child.parent = self
if self.has_right_child():
self.right_child.parent = self

BinarySearchTree 这个类很复杂:

class BinarySearchTree:
def __init__(self):
self.root = None
self.size = 0
def length(self):
return self.size
def __len__(self):
return self.size
def __iter__(self):
return self.root.__iter__()

下面的put()函数在BinarySearchTree中,他的主要功能是插入一个新node。如果树没有根结点,即将待插入结点为根结点,如果树有根结点,即执行该类的私有函数_put()

    def put(self, key, val):
if self.root:
self._put(key, val, self.root)
else:
self.root = TreeNode(key ,val)
self.size = self.size + 1
def _put(self, key, val, current_node):
if key < current_node.key:
if current_node.has_left_child():
self._put(key, val, current_node.left_child)
else:
current_node.left.child = TreeNode(key, val, parent=current_node)
else:
if current_node.has_right_child():
self._put(key, val, current_node.right_child)
else:
current_node.right_child = TreeNode(key, val, parent=current_node)

下面是python的魔术方法,能轻易的进行赋值操作:(下列函数生成后即可以执行 my_zip_tree[32]=='hello' 这种赋值操作)

    def __setitem__(self, k, v):
self.put(k, v)

get()操作是put()的反操作,用来找到结点

    def get(self, key):
if self.root:
res = self._get(key, self.root)
if res:
return res.payload
else:
return None
else:
return None
def _get(self, key, current_node):
if not current_node:
return None
elif current_node.key == key:
return current_node
elif key < current_node.key:
return self._get(key, current_node.left_child)
else:
return self._get(key, current_node.right_child)
def __getitem(self, key):
return self.get(key)

使用get()函数,我们还可以创建in操作,即创建__contains__函数:

    def __contains__(self, key):
if self._get(key, self.root):
return True
else:
return False
if 'Northfield' in my_zip_tree:
print("oom ya ya")

删除结点操作是二叉搜索树中最复杂的部分,主要过程分为两步:首先,使用get()操作找到要删除的node;然后,删除node且保证其他子树连接仍然保持二叉树属性。

def delete(self, key):
if self.size > 1:
node_to_remove = self._get(key, self.root)
if node_to_remove:
self.remove(node_to_remove)
self.size = self.size - 1
else:
raise KeyError('Error, key not in tree')
elif self.size == 1 and self.root.key == key:
self.root = None
self.size = self.size - 1
else:
raise KeyError('Error, key not in tree') def __delitem__(self, key):
self.delete(key)

remove()操作分为三种情况:

第一种:删除的结点没有孩子,这种情况很直接,直接删除就ok

if current_node.is_leaf():
if current_node == current_node.parent.left_child:
current_node.parent.left_child = None
else:
current_node.parent.right_child = None

第二种:删除的结点有一个孩子(这里只考虑这个孩子是左孩子的情况,右孩子的情况是对称的)

1.如果要删除的结点是左孩子,那么我们只要更新它的左孩子与他的父结点对接即可

2.如果要删除的结点是右孩子,那么我们只要更新它的右孩子与他的父结点对接即可

3.如果删除的结点没有父母,那么他肯定是根结点

else: # this node has one child
if current_node.has_left_child():
if current_node.is_left_child():
current_node.left_child.parent = current_node.parent
current_node.parent.left_child = current_node.left_child
elif current_node.is_right_child():
current_node.left_child.parent = current_node.parent
current_node.parent.right_child = current_node.left_child
else:
current_node.replace_node_data(current_node.left_child.key,
current_node.left_child.payload,
current_node.left_child.left_child,
current_node.left_child.right_child)
else:
if current_node.is_left_child():
current_node.right_child.parent = current_node.parent
current_node.parent.left_child = current_node.right_child
elif current_node.is_right_child():
current_node.right_child.parent = current_node.parent
current_node.parent.right_child = current_node.right_child
else:
current_node.replace_node_data(current_node.right_child.key,
current_node.right_child.payload,
current_node.right_child.left_child,
current_node.right_child.right_child)

第三种:删除的结点有两个孩子。这时,并不能直接用他的孩子替代,这个时候需要找一个继任者(successor),这个继任者满足两个条件(1.在删除结点中的子孙中第一个比他大的结点。2.这个继任者最多有一个孩子),继任者接替删除结点的位置。

elif current_node.has_both_children(): #interior
succ = current_node.find_successor()
succ.splice_out()
current_node.key = succ.key
current_node.payload = succ.payload
def find_successor(self):
succ = None
if self.has_right_child():
succ = self.right_child.find_min()
else:
if self.parent:
if self.is_left_child():
succ = self.parent
else:
self.parent.right_child = None
succ = self.parent.find_successor()
self.parent.right_child = self
return succ
  • Reference:
  1. Problem Solving with Algorithms and Data Structures, Release 3.0
  2. Python 魔术方法指南

Python数据结构应用6——树的更多相关文章

  1. python数据结构之树和二叉树(先序遍历、中序遍历和后序遍历)

    python数据结构之树和二叉树(先序遍历.中序遍历和后序遍历) 树 树是\(n\)(\(n\ge 0\))个结点的有限集.在任意一棵非空树中,有且只有一个根结点. 二叉树是有限个元素的集合,该集合或 ...

  2. python数据结构树和二叉树简介

    一.树的定义 树形结构是一类重要的非线性结构.树形结构是结点之间有分支,并具有层次关系的结构.它非常类似于自然界中的树.树的递归定义:树(Tree)是n(n≥0)个结点的有限集T,T为空时称为空树,否 ...

  3. python数据结构与算法

    最近忙着准备各种笔试的东西,主要看什么数据结构啊,算法啦,balahbalah啊,以前一直就没看过这些,就挑了本简单的<啊哈算法>入门,不过里面的数据结构和算法都是用C语言写的,而自己对p ...

  4. python利用Trie(前缀树)实现搜索引擎中关键字输入提示(学习Hash Trie和Double-array Trie)

    python利用Trie(前缀树)实现搜索引擎中关键字输入提示(学习Hash Trie和Double-array Trie) 主要包括两部分内容:(1)利用python中的dict实现Trie:(2) ...

  5. python数据结构之二叉树的统计与转换实例

    python数据结构之二叉树的统计与转换实例 这篇文章主要介绍了python数据结构之二叉树的统计与转换实例,例如统计二叉树的叶子.分支节点,以及二叉树的左右两树互换等,需要的朋友可以参考下 一.获取 ...

  6. python数据结构与算法——链表

    具体的数据结构可以参考下面的这两篇博客: python 数据结构之单链表的实现: http://www.cnblogs.com/yupeng/p/3413763.html python 数据结构之双向 ...

  7. python数据结构之图的实现

    python数据结构之图的实现,官方有一篇文章介绍,http://www.python.org/doc/essays/graphs.html 下面简要的介绍下: 比如有这么一张图: A -> B ...

  8. Python数据结构与算法--List和Dictionaries

    Lists 当实现 list 的数据结构的时候Python 的设计者有很多的选择. 每一个选择都有可能影响着 list 操作执行的快慢. 当然他们也试图优化一些不常见的操作. 但是当权衡的时候,它们还 ...

  9. Python数据结构与算法--算法分析

    在计算机科学中,算法分析(Analysis of algorithm)是分析执行一个给定算法需要消耗的计算资源数量(例如计算时间,存储器使用等)的过程.算法的效率或复杂度在理论上表示为一个函数.其定义 ...

随机推荐

  1. werfault进程使用CPU率高

    werfault进程是Windows vista 错误报告进程,是用来向微软反馈报告.是安全的正常进程. 解决方法:1.打开控制面板”—“系统和维护”,点击“问题报告和解决方案”. 2.点击“更改设置 ...

  2. Js 实现自定义事件

    var Event = { on: function (eventName, callback) { if (!this[eventName]) { this[eventName] = []; } t ...

  3. [C#网络应用编程]2、对线程的管理

    在System.Threading命名空间下,有一个Thread类,用于对线程进行管理,如创建线程.启动线程.终止线程.合并线程.让线程休眠等 Thread类 (假设Thread firTh = ne ...

  4. RocketMQ源码 — 七、 RocketMQ高可用(2)

    上一篇说明了RocketMQ怎么支持broker集群的,这里接着说RocketMQ实现高可用的手段之一--冗余. RocketMQ部署的时候一个broker set会有一个mater和一个或者多个sl ...

  5. Django之Apps源码学习

    先了解下官方文档的介绍 Django包含了一个已经安装应用的注册表,这个注册表存储着配置信息以及用来自省,同时也维护着可用模型的列表. 这个注册表就是apps,位于django.apps下,本质上是一 ...

  6. Android两级嵌套ListView滑动问题的解决

    Android下面两级嵌套ListView会出现滑动失效,解决方案,把两级Listview全换成NoScrollListView,代码如下: public class NoScrollListView ...

  7. SOFA 源码分析 —— 过滤器设计

    前言 通常 Web 服务器在处理请求时,都会使用过滤器模式,无论是 Tomcat ,还是 Netty,过滤器的好处是能够将处理的流程进行分离和解耦,比如一个 Http 请求进入服务器,可能需要解析 h ...

  8. Python撸支付宝红包教程,行走在灰色产业边缘的程序员!

      2018年刚到就作死撸羊毛(支付宝).2017年用分享给支付宝好友链接的官方通道"撸"了400大洋. 如许天天早上7:30便起床开愉快心的分享红包链接.200多个老友分享完一次 ...

  9. python 整理数据中的图片

    import shutil import time import pymysql conn=pymysql.connect(host="222",user="root&q ...

  10. 填坑!!!virtualenv 中 nginx + uwsgi 部署 django

    一.为什么会有这篇文章 第一次接触 uwsgi 和 nginx ,这个环境搭建,踩了太多坑,现在记录下来,让后来者少走弯路. 本来在 Ubuntu14.04 上 搭建好了环境,然后到 centos7. ...