python数据结构与算法 38 分析树

分析树

树的结构完成以后,该是时候看看它能做点什么实事儿了。这一节里,我们研究一下分析树。分析树能够用于真实世界的结构表示,象语法或数学表达式一类的。

图1
一个简单语句的分析树

图1所示是一个简单语句的层级结构,把语句表示为树结构可以让我们用子树来分析句子的组成部分。

图2 ((7+3)?(5?2))的分析树

我们也可以把数学表达式如((7+3)?(5?2))表示为分析树,如图2.此前我们研究过完全括号表达式,这个表达式表达了什么呢?我们知道乘法的优先级比加减要高,但因为括号的关系,在做乘法之前,要先完成加减法的计算。树的层级结构能帮助我们理解整个表达式的计算顺序。左子树是加法,求值得10,减法是右子树,求值得3.因为树的层级结构,可以用求得的值代替左右子树,这样就可以直接计算乘法了。

图3 ((7+3)?(5?2))的简化分析树

本章其余部分我们研究分析树的更多细节,特别是要研究:

  • 对完全括号表达式怎样构建分析树。
  • 怎样对分析树的表达式求值
  • 怎样从分析树还原数学表达式。

构建分析树,第一步是把表达式分解成符号保存在列表里。这里面有4种符号:左括号,右括号,操作符,操作数。我们知道每当读到一个左括号,就是新开一个表达式,这时就要新建一个子树来对应括号内的表达式。相反地,每读到一个右括号,这个子表达式就结束了。另外我们也要知道,操作数总是操作符的叶子。最后我们要知道,每个操作符都有左右两个孩子。

知道了以上信息,我们可以给出以下规则:

1.   如果当前符号是’(‘,新增一个节点作为当前节点的左孩子,并下沉到这个左孩子。

2.   如果当前符号在  [‘+‘,‘-‘,‘/‘,‘*‘],把当前节点的值赋为当前符号,并且为当前节点增加一个右孩子,并下沉到这个右孩子。

3.   如果当前符号是一个数字,把当前节点值设为这个数,返回到父母节点。

4.   如果当前符号是‘)’,返回到父母节点。

开始写代码之前,我们先看一看上面的规则是怎样运作的。以表达式 (3+(4?5))为例,我们把它分析成字符列表[‘(‘,
‘3‘, ‘+‘, ‘(‘, ‘4‘, ‘*‘, ‘5‘,‘)‘,‘)‘].,我们从一个只含有空根节点的分析树开始,如图4所示为结构,和每一次操作后的内容。

新建空节点

读到左括号,规则1

读到3,规则3

读到+,规则2

读到左括号,规则1

读到数字4,规则3

读到*,规则2

读到数字5,规则3

图4
跟踪分析树构建

按图4,步骤是这样的的:

  1. 建一个空树
  2. 读入第一个符号(,按规则1,新建一个节点作为根的左孩子。把左孩子作为当前节点。
  3. 读入第二个符号3,按规则3,将节点值设为3,并返回到它的父母。
  4. 读入第三个符号+,按规则2,将节点值设为+并增加右孩子,并作为当前节点
  5. 读入第四个符号(,按规则1,新建一个节点,作为当前节点的左孩子,并设为当前节点
  6. 读入第五个符号4,按规则3,节点值设为4,将它的父母作为当前节点。
  7. 读入下一个符号*,按规则2,当前节点值设为*并新建一个右节点。新节点设为当前节点。
  8. 读入下一个符号5,按规则3,当前节点值设为5,它的父母作为当前节点。
  9. 读入下一个符号),按规则4,*的父母成为当前节点
  10. 读入下一个符号),按规则4,+的父母作为当前节点。+没有父母,操作结束。

从上面的例子很清楚地看到,我们必须保持对“当前节点”和它的“父母节点”的跟踪。树的接口已经提供了获得孩子节点的方法,getLeftChild 和 getRightChild 
但是怎样跟踪父母呢?一个简单方法就是使用栈,每当下沉到当前节点的孩子时,先把当前节点压栈,当要返回到当前节点的父母时,从栈中弹出。

使用以上规则,加上 Stack 和 BinaryTree 的操作,现在我们可以写分析树的代码了。如下:

from pythonds.basic.stack import Stack
from pythonds.trees.binaryTree import BinaryTree

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 = 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 = buildParseTree("( ( 10 + 5 ) * 3 )")
pt.postorder()  #下节讲到

上面代码中,我们讲过的四规则,分别体现在if语句的四个分枝中,即11,15,19和24行。在每种情况下,都可以看到规则的实现,包括几次对 BinaryTree 或 Stack 的调用。唯一的错误检查在else 子句中,升起一个 ValueError 异常,应对从列表中得到一个不能识别的字符的情况。

现在分析树已经建起来了,怎样用呢?作为第一个例子,我们写一个函数来求分析树的值,返回计算结果。写这个函数要用到树的层级结构,回头看看图2记得我们用图3中的简化树代替了原来的树,这提示我们写一个递归算法对子树求值。

以前我们做过递归算法了,这次我们从设计递归的基点开始。树的自然基点是叶子。在分析树中,叶子节点总是操作数,象整数或浮点数之类的数字对象不需要更多操作,所以 evaluate 函数可以直接返回它的值。递归走向基点的的方法是对左右孩子使用 evaluate 函数。

要把两个递归调用的结果合在一起,只需要简单地对这两个结果应用存在父母节点的操作符,在图3的例子中,我们可以看到两个孩子自己求值,各得10和3,对他们应用乘法,得到最终结果30

递归函数 evaluate 见linsting
1。首先,要得到左右孩子的引用,如果左右孩子都是None,那么当前节点是叶子。这个检查过程在第7行。如果不是叶子,查找当前节点的操作符,并用到它左右孩子的返回值上。

在算法中,使用了字典数据类型,其中键值是 ‘+‘, ‘-‘, ‘*‘和 ‘/‘。数值部分是python中operator模块的函数。Operator模块提供了常用操作符的函数版,这样当我们查找操作符的时候,返回相应的函数。既然是函数,我们可以用函数方式来计算算式。比如我们查找 opers[‘+‘](2,2) ,等价于operator.add(2,2).

Listing 1

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()

最后,我们用图4中的例子来跟踪求值函数evaluate
 。当调用evaluate,,我们把一整个子树作为参数传递过去。然后我们得到左右子树的引用以确认他们是存在的。递归调用发生在第9行,开始查找根节点的操作符,这里是 ‘+‘,它对应的函数是operator.add ,需要两个参数。象一般的python函数一样,它要做的第一件事情就是计算两个参数的函数值。在本例中,两个参数都是对 evaluate 的递归调用。从左到右求值,第一个递归函数得到了左子树,发现这个节点没有孩子,所以是叶子。在叶子节点上,仅仅返回它的数值就可以,这里返回了整数3。

此时我们的顶级调用 operator.add的一个参数已经算出来了,但还没完。继续从左到右的求值,现在求右子树的值,发现它有左右孩子,根值是*,再对它的两个孩子调用函数,这时发现它的左右孩子是叶子,分别返回两个整数4和5,用这两个参数计算operator.mul(4,5)。这时,顶级调用+的左右子树已经计算出来,这时要计算 operator.add(3,20)。最终的结果是23.

python数据结构与算法 38 分析树,布布扣,bubuko.com

时间: 2024-10-19 00:57:40

python数据结构与算法 38 分析树的相关文章

python数据结构与算法 39 树的遍历

树的遍历 在学习完成树的基本结构以后,我们开始研究一些树的应用模式.访问树的全部节点,一般有三种模式,这些模式的不同之处,仅在于访问节点的顺序不同.我们把这种对节点的访问称为"遍历",这三种遍历模式叫做前序.中序和后序.下面我们对遍历模式作更仔细的定义,同时研究使用这延续模式的例子. 前序遍历 在前序遍历中,先访问根节点,然后用递归方式前序遍历它的左子树,最后递归方式前序遍历右子树. 中序遍历 在中序遍历中,先递归中序遍历左子树,然后访问根节点,最后递归中序遍历右子树. 后序遍历 在后

python数据结构与算法 36 树的基本概念

树 学习目标 理解什么是树及使用方法 学会使用树实现映射 用列表实现树 用类和引用实现树 用递归实现树 用堆实现优先队列 树的例子 前面我们学习过栈和队列这类线性数据结构,并且体验过递归,现在我们学习另一种通用数据结构,叫做树.树在计算机科学中应用广泛,象操作系统.图形学.数据库系统.网络等都要用到树.树和他们在自然界中的表哥--植物树--非常相似,树也有根,有分枝,有叶子.不同之处是,数据结构的树,根在顶上,而叶子在底部. 在开始学习之前,我们来研究几个普通的例子.第一个是生物学上的分级树.图

python数据结构与算法 37 树的实现

树的实现 记住上一节树的定义,在定义的基础上,我们用以下的函数创建并操作二叉树: BinaryTree() 创建一个二叉树实例 getLeftChild() 返回节点的左孩子 getRightChild() 返回节点的右孩子 setRootVal(val) 把val变量值赋给当前节点 getRootVal() 返回当前节点对象. insertLeft(val) 创建一个新二叉树作为当前节点的左孩子 insertRight(val) 创建一个新二叉树作为当前节点的右孩子. 实现树的关键点是合适的存

python数据结构与算法 34 归并排序

归并排序 在提高排序算法性能的方法中,有一类叫做分而治之.我们先研究其中第一种叫做归并排序.归并排序使用递归的方法,不停地把列表一分为二.如果列表是空或只有一个元素,那么就是排好序的(递归基点),如果列表有超过1个的元素,那么切分列表并对两个子列表递归使用归并排序.一旦这两个列表排序完成,称为"归并"的基本操作开始执行.归并是把两个有序列表合并成一个新的有序列表的过程.图10是我们熟悉的列表样例分解过程,图11是归并的过程. 图10  切分过程 图11  归并过程 以下是mergeSo

python数据结构与算法 35 快速排序

快速排序 快速排序也使用了分而治之的策略来提高性能,而且不需要额外的内存,但是这么做的代价就是,列表不是对半切分的,因而,性能上就有所下降. 快速排序选择一个数值,一般称为"轴点",虽然有很多选取轴点的方法,我们还是简单地把列表中第一个元素做为轴点了.轴点的作用是帮助把列表分为两个部分.列表完成后,轴点所在的位置叫做"切分点",从这一点上把列表分成两部分供后续调用. 图12所示,54将作为轴点.这个例子我们已经排过多次了,我们知道54在排好序后将处于现在31的位置上

Python数据结构与算法--算法分析

在计算机科学中,算法分析(Analysis of algorithm)是分析执行一个给定算法需要消耗的计算资源数量(例如计算时间,存储器使用等)的过程.算法的效率或复杂度在理论上表示为一个函数.其定义域是输入数据的长度,值域通常是执行步骤数量(时间复杂度)或者存储器位置数量(空间复杂度).算法分析是计算复杂度理论的重要组成部分. 本文地址:http://www.cnblogs.com/archimedes/p/python-datastruct-algorithm-analysis.html,转

Python数据结构与算法--List和Dictionaries

Lists 当实现 list 的数据结构的时候Python 的设计者有很多的选择. 每一个选择都有可能影响着 list 操作执行的快慢. 当然他们也试图优化一些不常见的操作. 但是当权衡的时候,它们还是牺牲了不常用的操作的性能来成全常用功能. 本文地址:http://www.cnblogs.com/archimedes/p/python-datastruct-algorithm-list-dictionary.html,转载请注明源地址. 设计者有很多的选择,使他们实现list的数据结构.这些选

Python数据结构与算法

数据结构与算法(Python) 冒泡排序 冒泡排序(英语:Bubble Sort)是一种简单的排序算法.它重复地遍历要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来.遍历数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成.这个算法的名字由来是因为越小的元素会经由交换慢慢"浮"到数列的顶端. 冒泡排序算法的运作如下: 比较相邻的元素.如果第一个比第二个大(升序),就交换他们两个. 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对.这步做完后,

Python 数据结构和算法

一.写在前面 这篇文章主要介绍了python 内置的数据结构(list.set以及字典),从一些实际的场景中来说明解决方案,主要是阅读<python cookbook>时写下的阅读记录,提高自己在Python开发方面的理解,记录在这里是为了方便可以随时查阅使用.因为时间仓促以及个人理解有限,固有错误的地方请指出,谢谢! 如果转载,请保留作者信息. 邮箱地址:[email protected] 个人博客:http://www.smallartisan.site/ CSDN博客:http://bl