Python数据结构应用——树(python树结构 库)

网友投稿 464 2022-08-28


Python数据结构应用——树(python树结构 库)

​​Python数据结构应用——树​​

数据结构是计算机存储、组织数据的方式。数据结构是指相互之间存在一种或多种特定关系的数据元素的集合。通常情况下,精心选择的数据结构可以带来更高的运行或者存储效率。数据结构往往同高效的检索算法和索引技术有关。

数据结构中的树的结点和机器学习中决策树的结点有一个很大的不同就是,数据结构中的树的每个叶结点都是独立的。树的高度(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 rootdef 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 rootdef get_root_val(root): return root[0]def set_root_val(root, new_val): root[0] = new_valdef 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))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)O(nlogn),得出的list肯定是个二叉堆

2.对于一个list的每一个项从左往右进行​​percolate_down​​操作,时间复杂度O(n)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))((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 operatordef 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 = selfreturn succ

Reference:

​​Problem Solving with Algorithms and Data Structures, Release 3.0​​​​Python 魔术方法指南​​

去期待陌生,去拥抱惊喜。

数据结构中的树的结点和机器学习中决策树的结点有一个很大的不同就是,数据结构中的树的每个叶结点都是独立的。树的高度(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 rootdef 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 rootdef get_root_val(root): return root[0]def set_root_val(root, new_val): root[0] = new_valdef 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))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)O(nlogn),得出的list肯定是个二叉堆

2.对于一个list的每一个项从左往右进行​​percolate_down​​操作,时间复杂度O(n)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))((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 operatordef 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 = selfreturn succ

Reference:

​​Problem Solving with Algorithms and Data Structures, Release 3.0​​​​Python 魔术方法指南​​


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:Python中的队列 || 堆、栈、队列之间的区别(python队列的基本操作)
下一篇:Pandas数据处理(pandas数据处理总结)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~