Python parsing tree and tree traversal, python parsing tree

Source: Internet
Author: User

Python parsing tree and tree traversal, python parsing tree

Resolution tree

After implementing the tree, let's look at an example to show you how to use the tree to solve some practical problems. In this chapter, we will study the resolution tree. The parsing tree is often used to represent the structure of the real world, such as sentences or mathematical expressions.

Figure 1: resolution tree of a simple sentence

Figure 1 shows the hierarchical structure of a simple sentence. Representing a sentence as a tree enables us to process each independent structure in a sentence by using a subtree.

Figure 2: resolution tree (7 + 3) * (5−2)

As shown in 2, a mathematical expression similar to (7 + 3) * (5-2) can be used to represent a parsing tree. We have already studied the brackets expression. How can we understand this expression? We know that multiplication plus or subtraction has a higher priority. Because of the relationship between parentheses, We need to calculate the addition or subtraction in the brackets before performing the multiplication operation. The hierarchical structure of the tree helps us understand the operation sequence of the entire expression. Before calculating the top Multiplication operation, we need to calculate the addition and subtraction operations in the subtree. The addition result of the Left subtree is 10, and the subtraction Result of the right subtree is 3. Using the hierarchy of the tree, once we calculate the expression results in the subnode, we can replace the entire subtree with one node. Using this replacement step, we get a simple tree, as shown in 3.

Figure 3: Simplified resolution tree (7 + 3) * (5−2)

In the rest of this chapter, we will study the parsing tree in more detail. Especially:

How to Create a resolution tree based on a fully-enclosed Mathematical Expression

How to calculate the mathematical expression value in the resolution tree

How to restore a mathematical expression based on an analytical tree

The first step to establish a resolution tree is to break down the expression string into symbols and save them in the list. There are four types of symbols to consider: left parentheses, operators, and operands. We know that when we read a left brace, we will start a new expression, so we create a subtree to correspond to this new expression. Instead, every time we read an angle bracket, we have to end the expression. In addition, the operands will be child nodes of leaf nodes and operators to which they belong. Finally, we know that each operator should have a left subnode and a right subnode. Through the above analysis, we define the following four rules:

If the currently read characters are'(', Add a new node as the left child node of the current node, and drop to the left child node.

If the currently read characters are in the list['+', '-', '/', '*'], Set the root value of the current node to the character currently read. Add a new node as the right sub-node of the current node and drop to the right sub-node.

If the character currently read is a number, set the root value of the current node to this number and return it to its parent node.

If the currently read character is ')', return the parent node of the current node.

Before writing Python code, let's take a look at the above example. We will use (3 + (4*5 ))
This expression. We break down the expression into the following character list:['(', '3', '+', '(', '4', '*', '5' ,')',')']. At the beginning, we started from a parsing tree that only contains an empty root node. 4. The figure shows the content and structure of the resolution tree after each new character is read.








Figure 4: Steps for parsing the Tree Structure

Observe Figure 4. Let's repeat it step by step:

  1. Create an empty tree.
  2. Read as (as the first character, create a new node as the left subnode of the current node according to rule 1, and change the current node to this new subnode.
  3. Read 3 as the next character. According to rule 3, assign the root value of the current node to 3 and return the parent node of the current node.
  4. Read + as the next character. According to Rule 2, assign the root value of the current node to +, add a new node as its right child node, and change the current node to this new child node.
  5. Read (as the next character. Based on rule 1, create a new node as the left child node of the current node and change the current node to this new child node.
  6. Read 4 as the next character. According to rule 3, assign the root value of the current node to 4 and return the parent node of the current node.
  7. Read * as the next character. According to Rule 2, assign the root value of the current node to *, add a new node as its right child node, and change the current node to this new child node.
  8. Read 5 as the next character. According to rule 3, assign the root value of the current node to 5 and return the parent node of the current node.
  9. Read) as the next character. According to rule 4, the current node is changed to the parent node of the current node.
  10. Read) as the next character. According to rule 4, we change the current node to the parent node of the current node + because the current node does not have a parent node, so we have completed the construction of the parsing tree.

Through the example given above, we obviously need to track the current node and the parent node of the current node. The tree provides us with a method to obtain subnodes-throughgetLeftChildAndgetRightChildMethod, but how do we track the parent node of a node? A simple method is to use the stack to track the parent node while traversing the entire tree. When we want to drop to the subnode of the current node, we first press the current node into the stack. When we want to return the parent node of the current node, the parent node pops up from the stack.

Using the above rules and operating using stacks and binary trees, we now write functions to create a resolution tree. The code for the function generated by the parsing tree is as follows.

from pythonds.basic.stack import Stackfrom pythonds.trees.binaryTree import BinaryTreedef 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 eTreept = buildParseTree("( ( 10 + 5 ) * 3 )")pt.postorder() #defined and explained in the next section

These four rules for creating a resolution tree are embodied in fourifClause. They are in rows 11,15, 19,24. As mentioned above, you can see the code implementation of the rule in these places, and you need to call someBinaryTreeAndStack. The only error check in this function iselseStatement, once the characters we read from the list are unrecognizable, we will reportValueError. Now we have built a resolution tree. What can we do with it? In the first example, we write a function to calculate the value of the resolution tree and return the calculated numerical result. To implement this function, we need to use the hierarchy of the tree. Let's repeat Step 2 and think about how we can replace the original tree with the simplified tree (Figure 3 ). This prompts us to write a value that calculates the value of each subtree recursively to calculate the value of the entire resolution tree.

Just as we used to implement recursive algorithms, we will design functions for Recursive Calculation of expression values from the basis point. The natural basis of this recursive algorithm is to check whether the operator is a leaf node. In the resolution tree, leaf nodes are always operands. Because numeric variables such as integers and floating-point numbers do not require more operations, this evaluate function only needs to simply return the numbers stored in the leaf node. The recursive process of moving a function to the base point is to call the evaluate function to calculate the values of the left and right subtree of the current node. Recursive calls make us move toward the leaf node and drop down along the tree.

To integrate the values of two recursive calls, we only need to simply apply the operators in the parent node to the results returned by the two subnodes. In Figure 3, we can see the values of the two subnodes: 10 and 3. Use the multiplication operation on them to get the final result 30.

The code of the recursive evaluate function is shown in Listing1. The parameters of the left and right subnodes of the current node are obtained. If the values of the left and right subnodes are None, we can know that the current node is a leaf node. This check is performed on 7th rows. If the current node is not a leaf node, search for the operator of the current node and use it to return values of left and right children.

To implement this algorithm, we use a dictionary with the key values'+','-','*'And'/'. The value in the dictionary is a function in the Python operand module. This operand module provides many operators for common functions. When we look for an operator in the dictionary, the corresponding operand variable is retrieved. Since it is a function, we can call the function to calculate the formula, as shown in figurefunction(param1,param2). So findopers['+'](2,2)It is equivalentoperator.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()

Finally, we will traverse and evaluate the value in the resolution tree created in Figure 4. When we call the evaluate function for the first time, we pass the resolution tree ParameterparseTreeAs the root of the entire tree. Then we get the reference of left and right subtree to make sure they exist. Recursive calls are performed on 9th rows. We start by viewing the operators in the root of the tree. This is'+'. This'+'Operator foundoperator.addFunction call with two parameters. Generally, for a Python function call, The first thing Python does is calculate the parameter value passed to the function. Through the left-to-right evaluation process, the first recursive call starts from the left. In the first recursive call, the evaluate function is used to calculate the left subtree. We found that this node has no left or right subtree, so we are on a leaf node. When we are on a leaf node, we only return the value stored on this leaf node as the result of the evaluate function. Therefore, we return an integer of 3.

Now, for top-level callsoperator.addFunction. We have calculated one of the parameters, but we have not finished it yet. The parameters are calculated from left to right. Now, the evaluate function is called recursively to calculate the right subnode of the root node. We found that this node has both left and right nodes, so we can find the operators stored in this node, which is'*'Then, call this operand function and use its left and right subnodes as two parameters of the function. Then, call the function on the two nodes, and find that the Left and Right subnodes of the function are leaves, and return two integers 4 and 5 respectively. After the two parameter values are obtained, we returnoperator.mul(4,5). Now, we have calculated the top-level operators.'+'And all you need to do is to call the function.operator.add(3,20)You can. The result is the value of the entire Expression Tree (3 + (4*5), which is 23.

Tree traversal

We have learned about the basic functions of the tree before. Now let's look at some application modes. Nodes can be accessed in three modes. These three methods are often used to access Tree nodes. The difference between them is that the order of access to each node is different. This access to all nodes is called traversal (traversal). These three types of traversal are called first-order traversal (preorder(inorder) And backward traversal (postorder). Let's give them a detailed definition, and then take an example to see their application.

First-order traversal

In first-order traversal, we first access the root node, then recursively use first-order traversal to access the left subtree, and then recursively use first-order traversal to access the right subtree.

Sequential Traversal

In the middle-order traversal, We recursively use the Middle-order traversal to access the left subtree, then access the root node, and then recursively use the Middle-order traversal to access the right subtree.

Post-order traversal

In the post-order traversal, We recursively use the post-order traversal to access the left and right subtree, and finally access the root node.

Now we use several examples to illustrate these three different traversal methods. First, let's look at the first sequential traversal. We use a tree to represent a book and look at the method of first-order traversal. The book is the root node of the tree. Each chapter is the child node of the root node, each section is the child node of the chapter, each section is the child node of each chapter, and so on. Figure 5 shows a book that consists of only two chapters. Although the traversal algorithm applies to the tree structure containing any number of sub-trees, we only talk about binary trees so far.

Figure 5: A book is represented by a tree structure.

Suppose you want to read this book from start to end. The first traversal exactly matches this order. Starting from the root node (book), we read it in the order of first traversal. We recursively traverse the left subtree first. Here is the first chapter. We continue recursively traverse the first section 1.1 of the Left subtree first. Section 1 1.1 has no subnodes, so we will not recursion. After Reading Section 1.1, we will return to Chapter 1. At this time, we also need to recursively access section 1.2 of the right subtree in Chapter 1. Because we first access the left subtree, we should first read section 1.2.1 and then section 1.2.2. After reading section 1.2, we will go back to chapter 1. Then we return to the root node (book) and follow the steps above to access Chapter 2.

Because we use recursion to write traversal, the code for first-order traversal is concise and elegant. Listing 2 provides Python code for first-order traversal of a binary tree.

Listing 2

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

We can also use sequential traversalBinaryTreeClass, as shown in Listing 3. Pay attention to the changes caused by this Code moving from the external to the internal. In general, we justtreeChangedself. But we also need to modify the base point of the Code. The built-in method must check whether the left and right subtree exists before performing recursive first-order traversal.

Listing 3

def preorder(self):  print(self.key)  if self.leftChild:    self.leftChild.preorder()  if self.rightChild:    self.rightChild.preorder()

Which of the following is a better built-in and external method? GenerallypreorderAs an external method, it is better because we seldom traverse for traversal. In this process, we always need to do something else. In fact, we will immediately see that the algorithm for post-sequential traversal is very similar to the Code for evaluating the expression tree we wrote earlier. However, we will write the traversal code in the form of external functions. The code for subsequent traversal is shown in Listing 4.printThe statement is almost the same as the code for first-order traversal.

Listing 4

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

We have seen general applications of post-sequential traversal, that is, evaluate values through the Expression Tree. Let's take a look at Listing 1. First, calculate the value of the Left subtree, then calculate the value of the right subtree, and then connect them together using the operation of the root node. Assume that our binary tree only stores the data of the Expression Tree. Let's rewrite the evaluate function and try to imitate the code of post-sequential traversal, as shown in Listing 5.

Listing 5

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

We found that the Listing 5 format is the same as Listing 4. The difference is that we output the key value in Listing 4 and return the key value in Listing 5. This allows us to store the values obtained recursively by rows 6th and 7th. Then we use these stored values and the 9th-row operator to calculate them together.

At the end of this section, let's take a look at the ordinal traversal. In the middle-order traversal, we first access the left subtree, followed by the root node, and finally access the right subtree. Listing 6 provides the code for the middle-order traversal. We found that the code of the three traversal functions only changes the position of the output statement without modifying the recursive statement.

Listing 6

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

When we perform a sequential traversal on an parsing tree, we get the original form of the expression without any parentheses. We try to modify the algorithm of the ordinal traversal so that we can get the parentheses expression. Make the following changes: output the left brackets before recursive access to the left subtree, and then output the right parenthesis after access to the right subtree. For the modified Code, see Listing 7.

Listing 7

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

We found thatprintexpThe function also adds parentheses for each number, which obviously do not need to be added.

Articles you may be interested in:
  • Implementation of python binary tree traversal
  • Python Data Structure-binary tree traversal instance
  • Python3 implements directory tree traversal

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.