A thorough analysis of non-recursive methods for Binary Tree pre-order, middle-order, and post-order traversal

Source: Internet
Author: User
Preface

In the first two articles, binary tree and binary search tree have involved three types of binary tree traversal. Recursive writing, as long as you understand the idea, a few lines of code. However, non-recursive writing is not easy. Here we will make a special conclusion to thoroughly analyze their non-recursive writing methods. Among them, the non-recursive method of sequential traversal is the simplest, and the latter is the most difficult. The basis of our discussion is as follows:

// Binary Tree nodetypedef struct node {int data; struct node * lchild; // left child struct node * rchild; // right child} btnode;

First of all, it is clear that non-recursive writing will definitely use stacks. This should not be explained too much. Let's first look at the sequential traversal:

Sequential traversal Analysis

Recursive definition of sequential traversal: First left subtree, then root node, and then right subtree. How to Write non-recursive code? One sentence:Let code follow your mind. What is our thinking? Thinking is the path of sequential traversal. Assume that you have a binary tree in front of you, and you need to write its ordinal traversal sequence. If you fully understand the Middle-order traversal, you must first find the bottom node of the Left subtree. The following code is taken for granted:

Code segment in the middle order (I)
Btnode * P = root; // P points to the root stack <btnode *> S; // The stack in STL // always traverses to the bottom of the Left subtree, while traversing and saving the root node to the stack while (p) {S. push (p); P = p-> lchild ;}

The reason for saving the root node that has traveled all the way is: to traverse the left subtree, you need to use the root node to enter the right subtree after traversing the left subtree. The Code goes here and the pointer P is null. There are only two situations:


Note:

  1. Only necessary nodes and edges are given. Other edges and nodes are irrelevant to the discussion and do not need to be drawn.
  2. You may think that the last saved node in Figure A is not the root node. If you have read the tree and binary tree basics and use the Extended Binary Tree concept, you can explain it. In short, you don't have to worry about this meaningless problem.
  3. If the entire binary tree has only one root node, it can be included in Figure.
Think about it. Are there two situations at the bottom of the Left subtree of a binary tree? In any case, the stack is output and the node is accessed. This node is the first node of the central sequence. According to our thinking, the code should be like this:
p = s.top();s.pop();cout << p->data;

Our thinking goes on. The two pictures are different in different situations: 1. In Figure A, a left child is accessed, traversing in the middle order, and then accessing its root node. That is, another node in Figure A, happy that it has been saved in the stack. We only need the same code as the code in the previous step:
p = s.top();s.pop();cout << p->data;
The access to the left and root is complete, and then the right child is finished, right. Next, you only need to write the following code: P = p-> rchild. In the right subtree, a new round of code segments (I), code segments (ii )...... Until the stack is empty and P is empty.
2. see Figure B. Because there is no left child, the root node is the first in the middle sequence, and then directly enters the right subtree: P = p-> rchild; In the right subtree, A new round of code segments (I), code segments (ii )...... Until the stack is empty and P is empty. It seems that the thinking is not clear. Do you really want to differentiate it? The following code segment (II) in Figure A is as follows:
p = s.top();s.pop();cout << p->data;p = s.top();s.pop();cout << p->data;p = p->rchild;

According to Figure B, the code segment (ii) is as follows:
p = s.top();s.pop();cout << p->data;p = p->rchild;

We can conclude that the traversal process is a loop, and a loop is formed by code segment (I) and code segment (II) until the stack is empty and P is empty. Different processing methods are crazy. Can we handle them in a unified manner? It's really okay! Looking back at the expanded Binary Tree, can each node be considered as a root node? Then, the Code must be written in the form of Figure B. That is to say, the code segment (ii) is unified as follows: the code segment in the middle order (II)
p = s.top();s.pop();cout << p->data;p = p->rchild;

Without making a statement, I had to go through theoretical tests. The reason why the code segment (II) in Figure A can also be written as Figure B is: because it is a leaf node, P =-= p-> rchild; then P is definitely empty. Is it empty. Do I need to go through a new round of code segment (I? Obviously not required. (Because the cycle condition is not met), you can directly enter the code segment (II ). Look! The last thing is the same. Or two consecutive outbound stacks. Think about it! I believe you will understand.
At this time, it is not difficult to write the traversal body:
Btnode * P = root; stack <btnode *> S; while (! S. empty () | P) {// code segment (I) is traversed to the bottom of the Left subtree, while traversing and saving the root node to the stack while (p) {S. push (p); P = p-> lchild;} // code segment (ii) when P is null, it indicates that it has reached the bottom of the Left subtree, in this case, if (! S. empty () {P = S. top (); S. pop (); cout <SETW (4) <p-> data; // enter the right subtree and start a new round of left subtree traversal (this is a recursive self-implementation) P = p-> rchild ;}}

Think about it. Is the above code written based on our thinking? With the detection of the boundary condition, the complete code of the non-recursive form of the central order traversal is as follows: the central order traversal code 1
// Traverse void inorderwithoutrecursion1 (btnode * root) {// empty tree if (root = NULL) return; // The tree is not empty btnode * P = root; stack <btnode *> S; while (! S. empty () | P) {// always traverse to the bottom of the Left subtree, while traversing and saving the root node to the stack while (p) {S. push (p); P = p-> lchild;} // when P is null, it indicates that it has reached the bottom of the Left subtree. In this case, if (! S. empty () {P = S. top (); S. pop (); cout <SETW (4) <p-> data; // enter the right subtree and start a new round of left subtree traversal (this is a recursive self-implementation) P = p-> rchild ;}}}

Congratulations, you have finished traversing non-recursive code in the middle order. Is it difficult to review? The following code is essentially the same. You can understand it without my explanation. Traverse Code 2 in the middle order
// Traverse void inorderwithoutrecursion2 (btnode * root) {// empty tree if (root = NULL) return; // The tree is not empty btnode * P = root; stack <btnode *> S; while (! S. empty () | P) {If (p) {S. push (p); P = p-> lchild;} else {P = S. top (); S. pop (); cout <SETW (4) <p-> data; P = p-> rchild ;}}}

Recursive definition of pre-order traversal analysis: First root node, then left subtree, and then right subtree. With the foundation of the Middle-order traversal, you don't need to be guided like the middle-order traversal. First, we traverse the left subtree, print it while traversing it, and store the root node in the stack. Then we need to use these nodes to open a new round of loop in the right subtree. You have to repeat the following statement: All nodes can be considered as root nodes. Write the code segment (I) According to the thought direction: the code segment in the forward sequence (I)
// Print and store the data in the stack by traversing the edge. You need to use these root nodes (do not doubt this) to go to the right subtree while (P) {cout <SETW (4) <p-> data; S. push (p); P = p-> lchild ;}

Next, go to the right subtree Based on the top node of the stack. Code segment (II)
// When P is null, it indicates that the root and left subtree have been traversed. If (! S. Empty () {P = S. Top (); S. Pop (); P = p-> rchild ;}

Similarly, code segments (I) (ii) constitute a complete loop body. At this point, it is not difficult to write a complete non-recursive method of forward traversal. Traverse code 1 in the forward order
Void preorderwithoutrecursion1 (btnode * root) {If (root = NULL) return; btnode * P = root; stack <btnode *> S; while (! S. empty () | P) {// print while traversing and storing the data in the stack. You need to use these root nodes (do not doubt this) go to the right subtree while (p) {cout <SETW (4) <p-> data; S. push (p); P = p-> lchild;} // when P is null, it indicates that the root and left subtree have been traversed, and it is time to enter the right subtree if (! S. Empty () {P = S. Top (); S. Pop (); P = p-> rchild ;}} cout <Endl ;}

Next we will show another piece of code that is essentially the same: traversing Code 2 in the forward order
// Traverse void preorderwithoutrecursion2 (btnode * root) {If (root = NULL) return; btnode * P = root; stack <btnode *> S; while (! S. empty () | P) {If (p) {cout <SETW (4) <p-> data; S. push (p); P = p-> lchild;} else {P = S. top (); S. pop (); P = p-> rchild ;}} cout <Endl ;}

The binary tree uses this method, which is slightly different and essentially the same: traversing code 3 in the forward order

Void preorderwithoutrecursion3 (btnode * root) {If (root = NULL) return; stack <btnode *> S; btnode * P = root; S. Push (Root); While (! S. empty () // The loop end condition is different from the first two {// This statement indicates that P is always a non-empty cout in the loop <SETW (4) <p-> data;/* stack features: the right subtree of the root node that is first accessed after being accessed */If (p-> rchild) s. push (p-> rchild); If (p-> lchild) P = p-> lchild; else {// The left subtree is accessed, and the right subtree is accessed P = S. top (); S. pop () ;}} cout <Endl ;}

Finally, go to the most difficult post-order traversal: Post-order traversal analysis post-order traversal recursive definition: First left subtree, then right subtree, and then root node. The difficulty of post-order traversal is: Determine whether the last accessed node is located in the left subtree or the right subtree. If it is in the left subtree, You need to skip the root node, first go to the right subtree, and then access the root node. If it is in the right subtree, you can directly access the root node. You can directly view the code, with detailed comments in the code. Code 1 in descending order
// Traverse void postorderwithoutrecursion (btnode * root) {If (root = NULL) return; stack <btnode *> S; // pcur: Current Access Node, plastvisit: btnode * pcur, * plastvisit; // pcur = root; plastvisit = NULL; // first move pcur to the bottom of the Left subtree while (pcur) {S. push (pcur); pcur = pcur-> lchild;} while (! S. empty () {// here, pcur is empty and has been traversed to the bottom of the Left subtree (as an Extended Binary Tree, It is blank and the left child of a tree) pcur = S. top (); S. pop (); // the premise for a root node to be accessed is: if (pcur-> rchild = NULL | pcur-> rchild = plastvisit) {cout <SETW (4) <pcur-> data; // modify the recently accessed node plastvisit = pcur;}/* here, the else statement can be changed to the conditional else if: else if (pcur-> lchild = plastvisit) // If the left subtree has just been accessed, You need to first enter the right subtree (the root node needs to go to the stack again) because: if the above conditions fail, the following conditions must be met. Think about it! */Else {// The root node re-enters stack S. push (pcur); // enter the right subtree, and it is certain that the right subtree is not empty. pcur = pcur-> rchild; while (pcur) {S. push (pcur); pcur = pcur-> lchild ;}} cout <Endl ;}

The code in another way is provided below. The idea is to add a tag (left, right) to each node ). If the left subtree of the node has been accessed, it is marked as left. If the right subtree has been accessed, it is marked as right. Obviously, you can access a node only when its flag position is right. Otherwise, you must first enter its right subtree. For details, see the comments in the code. Code 2 in descending order
// Define the enumeration type: tagenum tag {left, right}; // customize the new type, encapsulate the binary tree node and tag together with typedef struct {btnode * node; tag ;} tagnode; // post-order traversal void postorderwithoutrecursion2 (btnode * root) {If (root = NULL) return; stack <tagnode> S; tagnode; btnode * P = root; while (! S. empty () | P) {While (p) {tagnode. node = P; // The left subtree of the node has been accessed by the tagnode. tag = tag: Left; S. push (tagnode); P = p-> lchild;} tagnode = S. top (); S. pop (); // If (tagnode. tag = tag: Left) {// replace tag tagnode. tag = tag: Right; // re-import stack S. push (tagnode); P = tagnode. node; // enter the right subtree P = p-> rchild;} else // if the right subtree has been accessed, you can access the current node {cout <SETW (4) <(tagnode. node)-> data; // leave it blank and exit the stack again (this step is difficult to understand) P = NULL ;}} cout <Endl ;} <span style = "font-family: 'courier new';"> </span>
There is always a huge gap between summative thinking and code. It is usually correct and clear, but it is not easy to write the correct code. To bridge this gap, there is no way to try and learn more. The following are the key points to understanding the above Code:
  1. All nodes can be considered as parent nodes (leaf nodes can be considered as parent nodes with two children blank ).
  2. Compare the code of the same algorithm. The essence of the algorithm is often seen in the differences.
  3. Try to modify the code according to your understanding. Write the code that you understand. If it is written, it is actually mastered.

Reprint please indicate the source, this article address: http://blog.csdn.net/zhangxiangdavaid/article/details/37115355

Column Directory: Data Structure and algorithm directory



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.