python数据结构——(6)树及其应用

树的概念

相较于之前说明的栈、队列、表等线性结构,本次说明的“树”是一种非线性结构。树的结构被分成三部分:根、枝和叶。树当中有很多概念,在这里我们列表说明。

定义说明
节点 Node每个节点都有名称,每个节点还可以保存数据
边 Edge边具有出入方向,父节点到子节点,子节点到父节点不一样
根 Root唯一一个没有入边的节点
路径 path一个节点到根节点,由边依次连接在一起的节点的“有序列表”
子节点 Children / 父节点 parent这两个概念是相互的,连着的两个节点,上面的是下面的父节点,下面的是上面的子节点
兄弟节点 Sibling具有同一个父节点的两个节点
子树 Subtree一个节点和其所有的子孙节点,以及相关边的集合
叶节点 Leaf没有子节点的节点
层级 Level从根节点开始到达一个节点的路径所包含的边的数量
高度 Height书中所有节点的最大层级

树的实现

列表实现

树的每一个节点可以表示成:[root, left, right]。

root:根节点
left:左子树
right:右子树

实现代码如下:

def BinaryTree(r):
    return [r, [], []]

def insertLeft(root, newBranch):
    t = root.pop(1)
    # t有子树
    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]

链表实现

链表实现树的 方式更常见,每一个节点包含三个部分:key, leftchild, rightchild。构建出节点之后,将这些节点连接起来就形成了树。

代码实现如下:

# 链表实现树
class BinaryTree:
    def __init__(self, rootObj):
        self.key = rootObj
        self.leftChild = None
        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 = 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

树的简单应用

问题:表达式解释。

存数据

解读:将一个表达式中的操作数和操作符都存入树中。例如:( 3+ ( 4 * 5 ) )。我们规定,叶节点存放操作数,内部节点存放操作符。

创建表达式解析树的过程

1、创建空树,当前节点为根节点;
2、读入“(”,创建左子节点,当前节点下降;
3、读入“3”,当前节点设置为3,上升到父节点;
4、读入“+”,当前节点设置为+,创建右子节点,当前节点下降;
5、读入“(”,创建左子节点,当前节点下降;
6、读入“4”,当前节点设置为4,上升到父节点;
7、读入“*”,当前节点设置为 *,创建右子节点,当前节点下降;
8、读入“5”,当前节点设置为5,上升到父节点;
9、读入“)”,上升到父节点;
10、读入“)”,上升到父节点。

从上述我们可以总结出,总共有4种情况:

  1. 遇到“(”,创建左子节点,当前节点下降;
  2. 遇到“)”,上升到父节点;
  3. 遇到操作数,当前节点设置为该操作数,上升到父节点;
  4. 遇到操作符,当前节点设置为该运算符,创建右子节点,当前节点下降。

需要注意的是:在树的方法中,我们并没有“上升到父节点的方法”,在这里我们通过栈来实现。

实现代码如下:

def buildParseTree(fpexp):
    fplist = fpexp.split()
    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 = Stack.pop()
            currentTree = parent
        elif i in ['+','-','*','/']:
            currentTree.setRootVal(i)
            currentTree.insertRight('')
            Stack.push(currentTree)
            currentTree = currentTree.geRightChild()
        elif i == ')':
            currentTree = pStack.pop()
        else:
            raise ValueError
    return eTree

取数据

上述 code 实现了将表达式存入树的过程,那么又该怎样从树中读取各个符号并求值呢?我们通过递归来实现。

在这之前,我们来介绍一个小技巧,从而更加方便的实现符号运算。

import operator

print(operator.add(1,2))
op = operator.add
print(op(1, 2))

测试结果:

3
3

实现求值的代码如下:

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()    # 叶节点
        

树的遍历

树的遍历(Tree Traversals),分为三种:前序遍历、中序遍历和后序遍历。

  • 前序遍历(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())

关于树呢,还有二叉完全树、二叉查找树等内容,入门来说相对复杂,请有兴趣的同学自行学习。

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值