12_python_tree

树的Python实现

树的嵌套列表实现

  • 用Python List来实现二叉树数据结构。
  • 递归的嵌套列表实现二叉树,由具有3个元素的列表实现:[root, left, right]
    • 第1个元素是根节点的值(是一个数据项)
    • 第2个元素是左子树(是一个列表)
    • 第3个元素是右子树(是一个列表)
  • 嵌套列表法的优点:
    • 子树的结构与树相同,是一种递归数据结构
    • 易扩展到多叉树,仅需要增加列表元素即可
myTree = [
    'a', #根节点 myTree[0]
    
    ['b',#左子树 myTree[1]
     ['d',[],[]],
     ['e',[],[]]],
    
    ['c',#右子树 myTree[2]
     ['f',[],[]],
     []]
]

通过定义一系列函数来符注操作嵌套列表:

#创建仅有根节点的二叉树
def BinaryTree(r): 
    return [r,[],[]]

#将新节点插入树中作为其直接的左子节点(如果已有左子节点,则把原左子节点作为新插入节点的左子节点,新插入节点做直接的左子节点)
def insertLeft(root,newBranch):
    t = root.pop(1)
    if len(t) > 1:
        root.insert(1,[newBranch,t,[]])
    else:
        root.insert(1,[newBranch,[],[]])
    return root

#将新节点插入树中作为其直接的右子节点(如果已有右子节点,则把原右子节点作为新插入节点的右子节点,新插入节点做直接的右子节点)
def insertRight(root,newBranch):
    t = root.pop(2)
    if len(t) > 1:
        root.insert(2,[newBranch,[],t])
    else:
        root.insert(2,[newBranch,[],[]])
    return root

#获取根节点数据项
def getRootVal(root):
    return root[0]

#更新根节点数据项
def setRootVal(root,newVal):
    root[0] = newVal

#获取左子树
def getLeftChild(root):
    return root[1]

#获取右子树
def getRightChild(root):
    return root[2]

#应用
r = BinaryTree(3)
insertLeft(r, 4)
insertLeft(r, 5)
insertRight(r, 6)
insertRight(r, 7)
l = getLeftChild(r)
print(l)

setRootVal(l, 9)
print(r)

insertLeft(l, 11)
print(r)
print(getRightChild(getRightChild(r)))
[5, [4, [], []], []]
[3, [9, [4, [], []], []], [7, [], [6, [], []]]]
[3, [9, [11, [4, [], []], []], []], [7, [], [6, [], []]]]
[6, [], []]

树的链表实现

  • 使用节点链接法实现树
    • 每个节点保存根节点的数据项,以及指向左右子树的链接
    • 通过left和right属性引用其他BinaryTree类实现
class BinaryTree:
    def __init__(self, rootObj):
        self.key = rootObj  #成员key保存根节点数据项
        
        self.leftChild = None #成员leftChid/rightChild则保存指向左/右子树的引用(同样是BinaryTree对象)
        self.rightChild = None
    
    def insertLeft(self, newNode):
        if self.leftChild == None:
            self.leftChild = BinaryTree(newNode)
        else:
            t = BinaryTree(newNode)
            t.leftChild = self.leftChild
            self.leftChild = t
    
    def insertRight(self, newNode):
        if self.rightChild == None:
            self.rightChild = BinaryTree(newNode)
        else:
            t = BinaryTree(newNode)
            t.rightChild = self.rightChild
            self.rightChild = t
    
    def getRightChild(self):
        return self.rightChild
    
    def getLeftChild(self):
        return self.leftChild
    
    def setRootVal(self, obj):
        self.key = obj
    
    def getRootVal(self):
        return self.key
    
r = BinaryTree('a')
r.insertLeft('b')
r.insertRight('c')
r.getRightChild().setRootVal('hello')
r.getLeftChild().insertRight('d')

树的应用:表达式解析

树用于表示语言中的句子,可以分析句子的各种语法成分,对句子的各种成分进行处理

  • 程序设计语言的编译:词法、语法检查;从语法树生成目标代码
  • 自然语言处理:机器翻译、语义理解

将表达式表示为树结构:叶节点保存操作数,内部节点保存操作符

  • 表达式树层次决定计算的优先级,越底层的表达式,优先级越高。
  • 树中每个子树都表示一个子表达式,将子树替换为子表达式值的节点,即可实现求值。

表达式解析:从全括号表达式构建表达式解析树,利用表达式解析树对表达式求值,从表达式解析树恢复原表达式的字符串形式

  • 全括号表达式要分解为单词Token列表
    • 其单词分为括号‘()’、操作符‘±*/’和操作数‘0-9’这几类。左括号是表达式的开始,右括号则是表达式的结束。
    • 例子(3+(45)):[’(’,‘3’,’+’,’(’,‘4’,’’,‘5’,’)’,’)’
  • 创建表达式解析式:从左到右扫描全括号表达式的每个单词,依据规则建立解析树
    • 当前单词是左括号,则为当前节点添加一个新节点作为其左子节点,当前节点下降为这个新节点;如果当前单词是操作符±*/,将当前节点的值设为此符号,为当前节点添加一个新节点作为其右子节点,当前节点下降为这个新节点;如果当前单词是操作数,将当前节点的值设为此数,当前节点上升到父节点;如果当前节点是右括号,则当前节点上升到父节点。
    • 创建空树,当前节点为根节点
    • 读入’(’,创建了左子节点,当前节点下降
    • 读入’3’,当前节点设置为3,上升到父节点
    • 读入’+’,当前节点设置为+,创建右子节点,当前节点下降
    • 读入’(’,创建左子节点,当前节点下降
    • 读入’4’,当前节点设置为4,上升到父节点
    • 读入’’,当前节点设置为,创建右子节点,当前节点下降
    • 读入’5’,当前节点设置为5,上升到父节点
    • 读入’)’,上升到父节点
    • 读入’)’,再上升到父节点

建立表达式解析树思路

  • 创建左右子树:insertLeft/Right()
  • 当前节点设置值:setRootVal()
  • 下降到左右子树:setLeft/RightChild()
  • 上升到父节点:用一个栈来记录跟踪父节点
    • 当前节点下降时,将下降前的节点push入栈
    • 当前节点需要上升到父节点时,上升到pop出栈的节点即可
def buildPaeseTree(fpexp):
    
    fplist = fpexp.split()#分割为token列表
    pStack = Stack()
    eTree = BinaryTree('')
    pStack.push(eTree)  #入栈下降
    currentTree = eTree
    
    for i in fplist:
        #表达式开始
        if i == '(': 
            currentTree.insertLeft('')
            pStack.push(currentTree) #入栈下降
            currentTree = currentTree.getLeftChild()
        #操作数   
        elif i not in ['+', '-', '*', '/', ')']: 
            currentTree.setRootVal(int(i))
            parent = pStack.pop() #出栈上升
            currentTree = parent
        #操作符
        elif i in ['+', '-', '*', '/']:
            currentTree.setRootVal(i)
            currentTree.insertRight('')
            pStack.push(currentTree) #入栈下降
            currentTree = currentTree.getRightChild()
        #表达式结束
        elif i == ')':
            currentTree = pStack.pop() #出栈上升
        else:
            raise ValueError
    return eTree

pt = buildPaeseTree("((10 + 5) * 3)")

增强程序可读性operator模块,为我们提供了很多常用操作符的函数。当我们在字典中查找一个操作符时,相应的函数功能会被取回。因为这个取回的变量是一个函数,可以按通常调用的方式来调用它们,如函数名(变量1,变量2)。

  • 例如,查找符’+’(2,2)就等价于operator.add(2,2)
import operator
print(operator.add)
print(operator.add(1,2))
op = operator.add 
n = op(1,2)
print(n)
<built-in function add>
3
3

利用表达式解析树求值思路:

  • 由于二叉树是一个递归数据结构,可以用递归算法来处理
  • 求值递归函数evaluate():由前述对子表达式的描述,可从树的底层子树开始,逐步向上层求值,最终得到整个表达式的值
    • 基本结束条件:叶节点是最简单的子树,没有左右子节点,其根节点的数据项即为子表达式树的值
    • 缩小规模:将表达式树分为左子树、右子树,即为缩小规模
    • 调用自身:分别调用evaluate()计算左子树和右子树的值,然后将左右子树的值依根节点的操作符进行计算,从而得到表达式的值
import operator
def evaluate(parseTree):
    opers = {
   '+':operator.add, '-':operator.sub, '*':operator.mul, '/':operator.truediv}  #操作符字典,函数引用和操作符的映射
    
    leftC = parseTree.getLeftChild()  #缩小规模
    rightC = parseTree.getRightChild()
    
    if leftC and rightC:
        fn = opers[parseTree.getRootVal()]
        return fn(evaluate(leftC),evaluate(rightC))  #递归调用
    else:
        return parseTree.getRootVal()  #基本结束条件

树的遍历

  • 遍历Traversal:对一个数据集中的所有数据项进行访问的操作
  • 根据对节点访问次序的不同来区分3种遍历
    • 前序遍历preorder: 根 - 左 - 右
    • 中序遍历inorder: 左 - 根 - 右
    • 后序遍历postorder: 左 - 右 - 根
#前序遍历
def preorder(tree):
    if tree:
        print(tree.getRootVal())
        preorder(tree.getLeftChild())
        preorder(tree.getRightChild())
#中序遍历
def inorder(tree):
    if tree != None:
        inorder(tree.getLeftChild())
        print(tree.getRootVal())
        inorder(tree.getRightChild())
#后序遍历
def postorder(tree):
    if tree != None:
        postorder(tree.getLeftChild())
        postorder(tree.getRightChild())
        print(tree.getRootVal())
  • 使三种遍历方法成为二叉树类BinaryTree中的内置方法
    • 内置的方法在递归调用方法之前必须检查左右子节点是否存在。
  • 但是一般来说,在使用遍历方法,外置函数更好一些。因为很少需要单纯遍历整个树。多数情况下,只是想利用基本的遍历方法来实现其他的事情。
class BinaryTree:
    def preorder(self):
        print(self.key)
        if self.leftChild:
            self.leftChild.preorder()
        if self.rightChild:
            self.rightChild.preorder()
    def inorder(self):
        if self.leftChild:
            self.leftChild.inorder()
        print(self.key)
        if self.rightChild:
            self.rightChild.inorder()
    def postorder(self):
        if self.leftChild:
            self.leftChild.postorder()
        if self.rightChild:
            self.rightChild.postorder()
        print(self.key)
  • 后序遍历的一种一般应用:解析树求值
def postordereval(tree):
    opers = {
   '+':operator.add, '-':operator.sub, '*':operator.mul, '/':operator.truediv}
    res1 = None
    res2 = None
    
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值