Bootstrap

树--- python实现


树是一种非线性数据结构。
在这里插入图片描述
在树中,每个节点都含有自己的数值,以及与之相连的子节点,连接节点的线叫做相连线(edge)。如下图所示,A是根节点(root),也是B和C的父节点(parent node),也就是说B、C都是A的子节点(child node)。同理,B是D和E的父节点,以此类推。要注意H、I、J、F、G都是尾节点(leaf node),因为它们位于树的最底部,没有任何子节点。

树的特点:

  • 每个节点都只有有限个子节点或无子节点;
  • 没有父节点的节点称为根节点;
  • 每一个非根节点有且只有一个父节点;
  • 除了根节点外,每个子节点可以分为多个不相交的子树;
  • 树里面没有环路(cycle)

相关术语

  • 节点Node:组成树的基本部分;每个节点具有名称,或键值,节点还可以保存额外数据项,数据项根据不同的应用而变;
  • 边Edge:每条边恰好连接两个节点,表示节点之间具有关联,边具有出入方向;
  • 根Root:树中唯一一个没有入边的节点;
  • 路径Path:由边依次连接在一起的节点的有序列表;

树的种类:

  • 二叉树:每个节点最多含有两个子树的树称为二叉树;
  • 完全二叉树:对于一棵二叉树,假设其深度为d(d>1)。除了第d层外,其它各层的节点数目均已达最大值,且第d层所有节点从左向右连续地紧密排列,这样的二叉树被称为完全二叉树;
  • 满二叉树:所有叶节点都在最底层的完全二叉树;
  • 平衡二叉树(AVL树):当且仅当任何节点的两棵子树的高度差不大于1的二叉树;
  • 排序二叉树(二叉查找树(英语:Binary Search Tree)):也称二叉搜索树、有序二叉树;
  • B树:一种对读写操作进行优化的自平衡的二叉查找树,能够保持数据有序,拥有多于两个子树。

嵌套列表法实现

Python List来实现二叉树数据结构,[root, left, right]。
嵌套列表法优点:
子树的结构与树相同,是一种递归数据结构;
很容易扩展到多叉树,仅需要增加列表元素即可;

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)
print("r = BinaryTree(3): ",r)
insertLeft(r,4)
print("insertLeft(r,4):  ",r)
insertLeft(r,5)
print("insertLeft(r,5):  ",r)
insertRight(r,6)
print("insertRight(r,6):  ",r)
insertRight(r,7)
print("insertRight(r,7):  ",r)
l = getLeftChild(r)
print("l = getLeftChild(r):  ",l)
setRootVal(l,9)
print("setRootVal(l,9):  ",r)
insertLeft(l,11)
print("insertLeft(l,11):  ",r)
print("getRightChild(getRightChild(r)):  ",getRightChild(getRightChild(r)))
输出结果:
r = BinaryTree(3):  [3, [], []]
insertLeft(r,4):   [3, [4, [], []], []]
insertLeft(r,5):   [3, [5, [4, [], []], []], []]
insertRight(r,6):   [3, [5, [4, [], []], []], [6, [], []]]
insertRight(r,7):   [3, [5, [4, [], []], []], [7, [], [6, [], []]]]
l = getLeftChild(r):   [5, [4, [], []], []]
setRootVal(l,9):   [3, [9, [4, [], []], []], [7, [], [6, [], []]]]
insertLeft(l,11):   [3, [9, [11, [4, [], []], []], []], [7, [], [6, [], []]]]
getRightChild(getRightChild(r)):   [6, [], []]

节点连接法

在这里插入图片描述
每个节点保存根节点的数据项,以及指向左右子树的链接。

class BinaryTree:
    def __init__(self, x):
        self.val = x
        self.left = None
        self.right = None

    def insertLeft(self,newNode):
        if self.left == None:
            self.left = BinaryTree(newNode)
        else:
            t = BinaryTree(newNode)
            t.left = self.left
            self.left = t

    def insertRight(self,newNode):
        if self.right == None:
            self.right = BinaryTree(newNode)
        else:
            t = BinaryTree(newNode)
            t.right = self.right
            self.right = t

    def getRightChild(self):
        return self.right

    def getLeftChild(self):
        return self.left

    def setRootVal(self,val):
        self.val = val

    def getRootVal(self):
        return self.val
r = BinaryTree('a')
r.insertLeft('b')
r.insertRight('c')
r.getRightChild().setRootVal('hello')
r.getLeftChild().insertRight('d')

在这里插入图片描述

树的应用

表达式解析树

规则:从左到右扫描全括号表达式的每个单词,依据规则建立解析数。

  • 如果当前单词是“( ”:为当前节点添加一个新节点作为其左子节点,当前节点下降为这个新节点;
  • 如果当前单词是操作符“+,-,*,/ ”:将当前节点的值设为此符号,为当前节点添加一个新节点作为其右子节点,当前节点下降为这个新节点;
  • 如果当前单词是操作数:将当前节点的值设为此数,当前节点上升到父节点;
  • 如果当前单词是“ )”:则当钱节点上升到父节点;

建立表达式解析树:

  • 创建左右子树可以调用insertLeft/Right;
  • 当前节点设置值,可以调用setRootVal;
  • 下降到左右子树可调用getLeft/RightChild;
  • 上升到父节点,我们需要用一个栈来记录跟踪父节点;

当前节点下降时,将下降前的节点push入栈,当前节点需要上升到父节点时,上升到pop出栈的节点即可。

def buildParseTree(fpexp):
    fplist = fpexp.split()
    pStack = Stack()
    eTree = BinaryTree('')
    pStack.push(eTree)
    curTree = eTree
    for i in fplist:
        if i == "(":
            curTree.insertLeft('')
            pStack.push(curTree)
            curTree = curTree.getLeftChild()
        elif i not in ['+','-','*','/',')']:
            curTree.setRootVal(int(i))
            parent = pStack.pop()
            curTree = parent
        elif i in ['+','-','*','/']:
            curTree.setRootVal(i)
            curTree.insertRight('')
            pStack.push(curTree)
            curTree = curTree.getRightChild()
        elif i == ')':
            curTree = pStack.pop()
        else:
            raise ValueError

    return eTree

buildParseTree("( 2 + ( 3 * 5 ) )")

在这里插入图片描述

利用表达式解析树求值

  • 基本结束条件: 叶节点是最简单的子树,没有左右子节点,其根节点的数据项即为子表达式树的值;
  • 缩小规模: 将表达式树分为左子树、右子树,即为缩小规模;
  • 调用自身: 分别调用evaluate计算左子树和右子树的值,然后将左右子树的值依根节点的操作符进行计算,从而得到表达式的值;
import operator
op = operator.add
n = op(1,2)
print(n)

输出:2

import operator
def evaluate(parseTree):
    ops = {'+':operator.add,'-':operator.sub,'*':operator.mul,'/':operator.truediv}

    leftC = parseTree.getLeftChild()
    rightC = parseTree.getRightChild()

    if leftC and rightC:
        fn = ops[parseTree.getRootVal()]
        return fn(evaluate(leftC),evaluate(rightC))
    else:
        return parseTree.getRootVal()

树的遍历

在这里插入图片描述

前序遍历 Preorder Traversal

在前序遍历中,先访问节点自己,然后访问左子树,最后再访问右子树,对于每个节点迭代此操作:

def pre_order(tree):
    if tree:
        print(tree.getRootVal())
        pre_order(tree.getLeftChild())
        pre_order(tree.getRightChild())

中序遍历 Inorder Traversal

在中序遍历中,先访问左子树上的节点,再访问自己,最后再访问右子树上的节点:

def in_order(tree):
    if tree != None:
        in_order(tree.getLeftChild())
        print(tree.getRootVal())
        in_order(tree.getRightChild())

后序遍历 Postorder Traversal

在后序遍历中,先访问左右子树,最后再访问自己:

def post_order(tree):
    if tree != None:
        post_order(tree.getLeftChild())
        post_order(tree.getRightChild())
        print(tree.getRootVal())

利用后序遍历重写表达式求值:

def post_order_eval(tree):
    ops = {'+': operator.add, '-': operator.sub, '*': operator.mul, '/': operator.truediv}
    res1= res2 = None
    if tree:
        res1 = post_order_eval(tree.getLeftChild())
        res2 = post_order_eval(tree.getRightChild())
        if res1 and res2:
            return ops[tree.getRootVal()](res1,res2)
        else:
            return tree.getRootVal()

print(post_order_eval(parseTree))
;