This article is to introduce the Python implementation of the parse tree and the implementation of the two-fork tree three kinds of traversal, the first sequence traversal, the sequence traversal, post-order traversal example, very detailed, the need for small partners can refer to the next.
Parse tree
Once the tree has been implemented, let's look at an example to show you how to use the tree to solve some practical problems. In this chapter, we will look at the parse tree. Parse trees are often used for real-world structural representations, such as sentences or mathematical expressions.
Figure 1: Parse tree for a simple sentence
Figure 1 shows the hierarchy of a simple sentence. To represent a sentence as a tree allows us to manipulate each individual structure in a sentence by using a subtree.
Figure 2: Parse tree ((7+3) * (5−2))
As shown in 2, we can represent a parse tree with a mathematical expression similar to ((7+3) * (5−2)). We have already studied the expression of parentheses, so how do we understand this expression? We know that multiplication has a higher priority than plus or minus. Because of the relationship between parentheses, we need to calculate the addition or subtraction in parentheses before we do the multiplication. The hierarchical structure of the tree helps us understand the order of operations of the entire expression. Before calculating the topmost multiplication, we first calculate the addition and subtraction in the subtree. The result of addition operation of Zuozi is 10, and the subtraction operation result of right subtree is 3. Using the tree hierarchy, once we have calculated the results of the expressions in the child nodes, we are able to replace the entire subtree with one node. Using this replacement step, we get a simple tree, shown in 3.
Figure 3: ((7+3) * (5−2)) after the simplification of the parse tree
In the remainder of this chapter, we will look at the parse tree in more detail. Especially:
How to set up a corresponding parse tree based on an all-parenthesis mathematical expression
How to calculate the value of a mathematical expression in a parse tree
How to restore mathematical expressions based on a parse tree
The first step in establishing the parse tree is to break the expression string into symbols and save it in the list. There are four kinds of symbols that we need to consider: opening parenthesis, operators, and operands. We know that when we read an opening parenthesis, we start a new expression, so we create a subtree that corresponds to the new expression. Instead, whenever we read a closing parenthesis, we have to end the expression. In addition, the operands become the child nodes of the leaf nodes and the operators to which they belong. Finally, we know that each operator should have a left child node and a right child node. Through the above analysis we define the following four rules:
If the currently read-in character is '('
, add a new node as the left child node of the current node and descend to the left child node.
If the currently-read character is in the list, the current ['+', '-', '/', '*']
node's root value is set to the currently read-in character. Add a new node as the right child node of the current node and descend to the right child node.
If the currently-read character is a number, the root value of the current node is set to that number and returned to its parent node.
If the currently-read character is ') ', returns the parent node of the current node.
Before we write the Python code, let's take a look at one of the above examples. We will use (4*5)
This expression. We decompose the expression into the following word list characters: ['(', '3', '+', '(', '4', '*', '5' ,')',')']
. At first, we start with a parse tree that includes only an empty root node. 4, the figure illustrates the content and structure of the parse tree as each new character is read into.
Figure 4: Step diagram of the parse tree structure
Look at Figure 4, let's go through it step-by-step:
Create an empty tree.
Read as (as the first character, according to Rule 1, create a new node as the current node's left dial hand nodes, and change the current node to this new child node.)
Read in 3 as the next character. According to Rule 3, the root value of the current node is assigned to 3 and then the parent node of the current node is returned.
Read In + 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.
Read in (as the next character.) According to Rule 1, create a new node as the left Dial hand node of the current one and change the current node to this new child node.
Read in 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
Read in * as the next character. According to rule 2, assign the root value of the current node to *, then add a new node as its right child node, and change the current node to this new child node.
Read in 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
Read in) as the next character. According to rule 4, we change the current node to the parent node of the current node *.
Read in) as the next character. According to rule 4, we make the current node 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 parse tree.
With the example given above, it is clear that we need to keep track of the parent node of the current node and the current node. The tree gives us a way to get the child nodes-through getLeftChild
and getRightChild
methods, but how do we track the parent node of a node? One simple approach is to use the stack to track the parent node as we traverse the entire tree. When we want to drop to the child node 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, we pop the parent node out of the stack.
With the above rules, using stacks and two-fork trees, we now write functions to create parse trees. The code for the parse tree generation function is shown below.
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< C5/>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 ("((5) * 3)") Pt.postorder () #defined and explained in the next section
These four rules for establishing an analytic tree are embodied in four if
clauses, respectively, in line 11,15,19,24. As mentioned above, in these places you can see the code implementation of the rule, and you need to call some BinaryTree
Stack
of the methods. The only error check in this function is in the else
statement, and once the characters we read from the list are unreadable, we will report an ValueError
exception. Now that we've built a parse tree, what can we do with it? In the first example, we write a function that calculates the value of the parse tree and returns the numerical result of the calculation. To implement this function, use the hierarchical structure of the tree. Looking back at 12, recall that we were able to replace the original tree with a simplified tree (Figure 3). This prompts us to write a value that computes the entire parse tree by recursively calculating the value of each subtree.
Just as we have implemented recursive algorithms before, we will design a function that recursively evaluates the value of an expression from a base point. The natural base point of this recursive algorithm is to check whether the operator is a leaf node. In the parse tree, leaf nodes are always operands. Because numeric variables such as integers and floating-point numbers do not require more action, the evaluation function simply returns the number stored in the leaf node. The recursive process that makes the function go to the base point is to call the evaluation function to calculate the value of the Zuozi and right subtree of the current node. Recursive invocation allows us to move toward the leaf node, descending along the tree.
In order to combine the values of the two recursive calls, we simply apply the operators that exist in the parent node to the results returned by the two child nodes. In Figure 3, we can see the values of two child nodes, 10 and 3, respectively. For them to use the multiplication operation to get the final result 30.
The code for the recursive evaluation function, as shown in Listing1, we get the parameters of the left child node and right child node of the current node. If the values of the left and right child nodes are none, we can know that the current node is a leaf node. This check is on line 7th. If the current node is not a leaf node, find the operator of the current node and use it to the left and right of the child's return value.
In order to implement this algorithm, we used the dictionary, and the key values are respectively '+','-','*'
'/'
. The value in the existing dictionary is a function in the Python operand module. This operand module provides us with operators for many common functions. When we look up an operator in the dictionary, the corresponding operand variable is retrieved. Since it is a function, we can calculate the calculation by calling the function, such as function(param1,param2)
. So finding opers['+'](2,2)
is equivalent to operator.add(2,2)
.
Listing 1
def evaluate (Parsetree): opers = {' + ': operator.add, '-': operator.sub, ' * ': Operator.mul, '/': Operator.truep} 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 iterate through the evaluation on the parse tree created in Figure 4. When we first call the evaluation function, we pass the parse tree parameter parseTree
as the root of the entire tree. Then we get references to the left and right subtrees to make sure they are there. The recursive call is on line 9th. Let's start by looking at the operator in the tree root, which is one '+'
. This '+'
operator finds a operator.add
function call and has two parameters. Usually the first thing Python does for a Python function call is to calculate the value of the parameter passed to the function. From left to right, the first recursive call starts from the left. In the first recursive call, the evaluation 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 simply return the value stored by this leaf node as the result of the evaluation function. So we return the integer 3.
Now, for the top call operator.add
function, we've calculated one of the parameters, but we're not done yet. Continuing to calculate parameters from left to right, recursive call evaluation functions are now used to calculate the right child node of the root node. We find that this node has both a left and a right node, so we look for the operator stored in the node, and '*'
then call the operand function and use its left-and-right child nodes as two parameters of the function. At this point, the function is called to its two nodes, then it is found that the left and right child nodes are leaves, and return two integers 4 and 5, respectively. After we find out the values of these two parameters, we return operator.mul(4,5)
the values. At this point, we have calculated the top-level operators '+'
of the two operands, all we need to do is to complete the call function operator.add(3,20)
. The result is the entire expression tree (4*5) value, which is 23.
The traversal of a tree
Before we learned about the basic functions of the tree, let's look at some application patterns. Depending on how the nodes are accessed, the pattern can be divided into 3 types. These three methods are often used to access the nodes of the tree, and they differ in the order in which each node is accessed. We call this access to all nodes as traversal ( traversal
). These three types of traversal are called first-order traversal ( preorder
), middle-sequence traversal (), inorder
and post-order traversal ( postorder
). Let's give them a detailed definition, and then take a look at their application.
First Order traversal
In the sequence traversal, we first access the root node, then recursively use the first-order traversal to access the left subtree, and then recursively use the first-order traversal to access the right sub-tree.
Middle Sequence traversal
In the middle sequence traversal, we recursively use the middle order traversal to access the left subtree, then access the root node, and finally recursively use the middle order traversal to access the right subtree.
Post-post traversal
In the post-operation traversal, we first recursively use the sequential traversal to access the Saozi right subtree, and finally access the root node.
Now let's use a few examples to illustrate these three different traversal types. First we look at first order traversal. We use a tree to represent a book, to see the way in which the first order is traversed. The book is the root node of the tree, each chapter is a child node of the root node, each section is a child node of the chapter, each subsection is a child node of each chapter, and so on. Figure 5 is a book that only takes part of two chapters. Although the traversal algorithm is suitable for tree structures with any number of subtrees, we have so far only talked about binary trees.
Figure 5: Using a tree structure to represent a book
Imagine you want to read this book from beginning to end. The first order traversal coincides with this sequence. Starting with the root node (the book), we read in the order of the first sequence traversal. We recursively iterate through the left subtree, here is the first chapter, we continue to recursively first order traversal access to Zuozi section 1.1. Section 1.1 No child nodes, we are no longer recursive. When we read the end of 1.1, we go back to chapter one, where we also need to recursively access the right subtree of section 1.2 of the first chapter. Since we first visited the left subtree, we first looked at the 1.2.1 festival and then the 1.2.2 Festival. When the 1.2 verse is finished, we go back to chapter one. We then return to the root node (book) and follow the steps above to access Chapter two.
Because of the recursive writing traversal, the first-order traversal of the code exception of the simple elegance. Listing 2 gives a binary tree's first-order traversal of the Python code.
Listing 2
def preorder (tree): if tree: print (Tree.getrootval ()) preorder (Tree.getleftchild ()) preorder ( Tree.getrightchild ())
We can also use the first-order traversal as a BinaryTree
built-in method in the class, as shown in Listing 3. Note that this code moves from the outside to the internal changes that are generated. Generally speaking, we are just going to tree
replace it self
. But we also want to change the base point of the code. The built-in method must check for the existence of the left and right subtrees before recursively iterating through the 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 built-in and external methods is better? Generally speaking, preorder
as an external method, the reason is that we seldom traverse simply to traverse, and always do something else in the process. In fact, we'll see that the algorithm for the post-order traversal is similar to the code we wrote earlier about the expression tree. It is just that we will then write the traversed code in the form of an external function. The code for the post-order traversal, as shown in Listing 4, is almost the same as the print
code that moves the statement beyond the end and the first-ordered traversal.
Listing 4
def postorder (tree): if tree! = None: postorder (Tree.getleftchild ()) Postorder (Tree.getrightchild ()) print (Tree.getrootval ())
We've seen the general application of post-order traversal, which is evaluated by expression trees. Let's look at Listing 1, we first seek the values of the left subtree, then the values of the right subtree, and then connect them with the operations of the root node. Suppose our two-fork tree stores only the data of an expression tree. Let's rewrite the evaluation function and try to imitate the code of the post-order traversal, as shown in Listing 5.
Listing 5
def postordereval (tree): opers = {' + ': operator.add, '-': operator.sub, ' * ': Operator.mul, '/': Operator.truep} 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 form is the same as Listing 4, except that we output the key value in Listing 4 and we return the key value in Listing 5. This allows us to store the values recursively obtained by line 6th and 7th row. We then use these saved values and the operator of Line 9th to operate together.
At the end of this section we look at the fancy order traversal. In the middle sequence traversal, we first access the left subtree, then the root node, and finally the right subtree. Listing 6 gives the code for the middle sequence traversal. We found that the three traversal function codes simply swapped the position of the output statement without altering the recursive statement.
Listing 6
def inorder (tree): if tree! = None: inorder (Tree.getleftchild ()) print (Tree.getrootval ()) inorder ( Tree.getrightchild ())
When we do a sequence traversal of a parse tree, we get the original form of the expression without any parentheses. We try to modify the algorithm of the middle order traversal so that we get the full bracket expression. Just make the following modifications: Output the opening parenthesis before recursively accessing Zuozi, and then output the closing parenthesis after accessing the right subtree. The revised code is shown in Listing 7.
Listing 7
def printexp (tree): Sval = "" If tree: sval = ' (' + printexp (Tree.getleftchild ()) sval = Sval + str (tree.getrootva L ()) Sval = Sval + printexp (Tree.getrightchild ()) + ') ' Return sval
We find that the printexp
function has parentheses for each number, which is clearly not necessary to add.