目录

  • 一、什么是树
  • 1.1 树结构相关术语
  • 1.2 树的定义
  • 二、树的实现
  • 2.1 嵌套列表实现
  • 2.2 链表实现
  • 三、树的应用:表达式解析
  • 3.1 解析树(语法树)
  • 3.2 建立表达式解析树
  • 3.3 利用表达式解析树求值
  • 四、树的遍历 Tree Traversals
  • 4.1 树的三种遍历
  • 4.2 利用后序遍历进行表达式求值
  • 4.3 利用中序遍历生成全括号中缀表达式


一、什么是树

树是一种基本的“非线性”数据结构。跟自然界中的树一样, 数据结构树也分为:根、 枝和叶等三个部分。一般数据结构的图示把根放在上方,叶放在下方。
分类树的三个特征:

  1. 分类体系是层次化的
  2. 一个节点的子节点与另一个节点的子节点相互之间是隔离、独立的
  3. 每一个叶节点都具有唯一性

1.1 树结构相关术语

术语

说明

根Root

树中唯一一个没有入边的节点

路径Path

由边依次连接在一起的节点的有序列表

子节点Children

入边均来自于同一个节点的若干节点, 称为这个节点的子节点

父节点Parent

一个节点是其所有出边所连接节点的父节点

兄弟节点Sibling

具有同一个父节点的节点之间称为兄弟节点

子树Subtree

一个节点和其所有子孙节点, 以及相关边的集合

叶节点Leaf

没有子节点的节点称为叶节点

层级Level

从根节点开始到达一个节点的路径,所包含的边的数量, 称为这个节点的层级

高度

树中所有节点的最大层级称为树的高度

1.2 树的定义

树由若干节点, 以及两两连接节点的边组成, 并有如下性质:

  • 其中一个节点被设定为根;
  • 每个节点n(除根节点),都恰连接一条来自节点p的边, p是n的父节点;
  • 每个节点从根开始的路径是唯一的,如果每个节点最多有两个子节点,这样的树称为“二叉树”

二、树的实现

2.1 嵌套列表实现

递归的嵌套列表实现二叉树, 由具有3个元素的 List 列表实现:

  1. 第1个元素为根节点的值;
  2. 第2个元素是左子树(所以也是一个列表);
  3. 第3个元素是右子树(所以也是一个列表)。

python C 数据结构 python 数据结构 树_python C 数据结构

【例】实现一个6节点的二叉树

根是myTree[0],左子树myTree[1],右子树myTree[2],子树的结构与树相同,是一种递归数据结构。

python C 数据结构 python 数据结构 树_列表_02

我们通过定义一系列函数来辅助操作嵌套列表

函数

含义

BinaryTree

创建仅有根节点的二叉树

insertLeft/insertRight

将新节点插入树中作为其直接的左/右子节点

get/setRootVal

则取得或返回根节点

getLeft/RightChild

返回左/右子树

【代码】:

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)  #[5, [4, [], []], []]
setRootVal(l,9)
print(r) #[3, [9, [4, [], []], []],[7, [], [6, [], []]]]
insertLeft(l,11)
print(r) #[3, [9, [11, [4, [], []], []], []], [7, [], [6, [], []]]]
print(getRightChild(getRightChild(r)))#[6, [], []]

图示结果如下:

python C 数据结构 python 数据结构 树_列表_03

2.2 链表实现

同样可以用节点链接法来实现树,每个节点保存根节点的数据项,以及指向左右子树的链接。

python C 数据结构 python 数据结构 树_列表_04

  • 成员key保存根节点数据项
  • 成员left/rightChild则保存指向左/右子树的引用(同样是BinaryTree对象)
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.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')

上述代码用图表示如下:

python C 数据结构 python 数据结构 树_列表_05

三、树的应用:表达式解析

3.1 解析树(语法树)

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

  • 语法分析树
    主谓宾,定状补
  • 程序设计语言的编译
    词法、语法检查
    从语法树生成目标代码
  • 自然语言处理
    机器翻译、语义理解
  • 我们还可以将表达式表示为树结构
    叶节点保存操作数,内部节点保存操作符
    由于括号的存在,需要计算*的话,就必须先计算7+3和5-2,表达式层次决定计算的优先级:越底层的表达式,优先级越高。树中每个子树都表示一个子表达式:
    我们可以利用树结构实现如下功能:
  • 从全括号表达式构建表达式解析树
  • 利用表达式解析树对表达式求值
  • 从表达式解析树恢复原表达式的字符串形式

3.2 建立表达式解析树

(1) 全括号表达式要分解为单词Token列表
其单词分为括号“() ”、操作符“±*/”和操作数“0~9”这几类,左括号就是表达式的开始,而右括号是表达式的结束。
python C 数据结构 python 数据结构 树_python C 数据结构_06分解为单词表
python C 数据结构 python 数据结构 树_列表_07

(2)创建表达式解析树过程
创建空树,当前节点为根节点
读入’(’, 创建了左子节点,当前节点下降
读入’3’,当前节点设置为3, 上升到父节点
读入’+’,当前节点设置为+, 创建右子节点,当前节点下降

python C 数据结构 python 数据结构 树_python_08

读入’(’, 创建左子节点,当前节点下降

读入’4’,当前节点设置为4, 上升到父节点

读入’’,当前节点设置为, 创建右子节点,当前节点下降

python C 数据结构 python 数据结构 树_列表_09

读入’5’,当前节点设置为5, 上升到父节点

读入’)’, 上升到父节点

读入’)’,再上升到父节点

python C 数据结构 python 数据结构 树_二叉树_10

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

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

从图示过程中我们看到, 创建树过程中关键的是对当前节点的跟踪:

  • 创建左右子树可调用insertLeft/Right
  • 当前节点设置值,可以调用setRootVal
  • 下降到左右子树可调用getLeft/RightChild

但是, 上升到父节点,这个没有方法支持!我们可以用一个来记录跟踪父节点:

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

【代码】:

def buildParseTree(fpexp):
    flist = fpexp.split()
    pStack = Stack()
    eTree = BinaryTree('')
    pStack.push(eTree)  #入栈下降
    for i in fplist:
        if i == '(': #表达式开始
            currentTree.insertLeft('')
            pStack.push(currentTree)  #入栈下降
            currentTree = currrentTree.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

3.3 利用表达式解析树求值

由前述对子表达式的描述,可从树的底层子树开始,逐步向上层求值(递归算法),最终得到整个表达式的值。
【递归三要素】

  1. **基本结束条件:**叶节点是最简单的子树,没有左右子节点,其根节点的数据项即为子表达式树的值;
  2. **缩小规模:**将表达式树分为左子树、右子树,即为缩小规模;
  3. **调用自身:**分别调用evaluate计算左子树和右子树的值,然后将左右子树的值依根节点的操作符进行计算,从而得到表达式的值.
import operator
def evaluate(parseTree):
    opers = {'+':operator.add, '-':operator.sub,'*':operator.mul,'/':operator.truediv}
    #缩小规模
    leftC = praseTree.getLeftChild()
    rightC = parseTree.getRightChild()
    if leftC and rightC:
        fn = opers[parseTree.getRootVal()]
        #递归调用
        return fn(evaluate(leftC),evaluate(rightC))
    else:
        return parseTree.getRootVal()  #基本结束条件

四、树的遍历 Tree Traversals

4.1 树的三种遍历

对一个数据集中的所有数据项进行访问的
操作称为“遍历Traversal”,线性数据结构中, 对其所有数据项的访问比较简单直接,按照顺序依次进行即可。树的非线性特点, 使得遍历操作较为复杂,我们按照对节点访问次序的不同来区分3种遍历:

  1. 前序遍历(preorder):先访问根节点,再递归地前序访问左子树、最后前序访问右子树;(根左右)
    ABDGHCEIF
  2. 中序遍历(inorder):先递归地中序访问左子树,再访问根节点,最后中序访问右子树;(左根右)
    GDHBAEICF
  3. 后序遍历(postorder):先递归地后序访问左子树,再后序访问右子树,最后访问根节点。(左右根)
    GHDBIEFCA

【代码】

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

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

4.2 利用后序遍历进行表达式求值

采用后序遍历法重写表达式求值代码

def postordereval(tree):
    opers = {'+':operator.add, '-':operator.sub,'*':operator.mul,'/':operator.truediv}
    res1 = None
    res2 = None
    if tree:  #基本结束条件
    #缩小规模
    res1 = postordereval(tree.getLeftChild())
    res2 = postordereval(tree.getRightChild())
    if res1 and res2:     
        #递归调用
        return opers[tree.getRootVal()](res1,res2)
    else:
        return tree.getRootVal()  #基本结束条件

4.3 利用中序遍历生成全括号中缀表达式

下列代码中对每个数字也加了括号,请自行修改代码去除。

def printexp(tree):
    sVal = ""
    if tree:
        sVal = '(' + printexp(tree.getLeftChild())
        sVal += str(tree.getRoolVal())
        sVal += printexp(tree.getRightChild()) +')'
    return sVal