Data Structure Notes: Binary Tree Traversal and Skills

introduction

This article is a topic I have encountered recently. I happen to have some experience and skills about the traversal of binary trees, so I will open a post to record it.

Introduction to Binary Tree Traversal

Traversal is a common operation in data structures, mainly to visit all elements once. For linear structures, traversal is divided into two types: forward order traversal and reverse order traversal. For the tree structure, according to the order of node access, the traversal of the binary tree is divided into the following four types:

  • Preorder Traversal
  • Inorder Traversal
  • Postorder Traversal
  • Level Order Traversal

The data structure of a binary tree is:

struct Node
{
    
    
    Node* left;
    Node* right;
    int data;
};

Binary tree preorder traversal

The order of general pre-order traversal is root, left child node, right child node → root (left child node), left leaf node, right leaf node → root (right child node), left leaf node, right leaf node (PS: The order here is three layers, all below, but the picture is 4 layers). That is, root first, then left, and finally right, referred to as root left and right, as shown in the following figure:

insert image description here

Then we can write its recursive form code:

void preorderTraversal(Node* p)
{
    
    
    if (!p) return;
    cout << p->data << " ";
    preorderTraversal(p->left);
    preorderTraversal(p->right);
}

Binary tree inorder traversal

The order of inorder traversal is left leaf node, root (left child node), right leaf node → left child node, root, left leaf node → left leaf node, root (right child node), right leaf node, That is, first left, then root, and finally right, referred to as left root and right, the schematic diagram is:
insert image description here
also write the recursive form code:

void inorderTraversal(Node* p)
{
    
    
    if (!p) return;
    inorderTraversal(p->left);
    cout << p->data << " ";
    inorderTraversal(p->right);
}

Binary tree post order traversal

The order of inorder traversal is left leaf node, right leaf node, root (left child node) → left leaf node, right leaf node, root (right child node) → left child node, left child node, root, That is, first left and then right, the last root, referred to as the left and right root, the schematic diagram is:
insert image description here
then its recursive form code is:

void postorderTraversal(Node* p)
{
    
    
    if (!p) return;
    postorderTraversal(p->left);
    postorderTraversal(p->right);
    cout << p->data << " ";
}

Binary tree level order traversal

There is also a layer sequence traversal, which literally means, from top to bottom, from left to right, and directly gives the schematic diagram as follows:
insert image description here

Then the code is:

void levelOrderTraversal(Node* root)
{
    
    
    queue<Node*> q;
    q.push(root);
    while (!q.empty())
    {
    
    
        Node* p = q.front(); q.pop();
        cout << p->data << " ";
        if (p->left)  q.push(p->left);
        if (p->right) q.push(p->right);
    }
}

The function uses a queue to keep track of the nodes that need to be visited, it first pushes the root node onto the queue, then, it enters a loop that continues until the queue is empty. At each iteration of the loop, it removes the preceding node p from the queue, prints its data, and pushes its left and right children onto the queue, thus achieving a hierarchical effect.

Binary tree traversal full code

Here, because the structure defined above is not divided into two parts, only a single node is defined, and the multi-relationship problem is not considered, but the code idea will be clearer instead. In addition to defining test cases, it will be more complicated. Here we build a binary tree structure:

Node* root = new Node{
    
     
    new Node{
    
     new Node{
    
     nullptr, nullptr, 4 }, new Node{
    
     nullptr, nullptr, 5 }, 2 },
    new Node{
    
     new Node{
    
     nullptr, nullptr, 6 }, new Node{
    
     nullptr, nullptr, 7 }, 3 },
    1
};

I happen to be learning cpp recently, and nullptr is NULL in c language, a null pointer. Expand it to the following form:

        1
       / \
      /   \
     /     \
    2       3
   / \     / \
  4   5   6   7

Of course, this form can change the construction method of the structure, such as mentioned in the four traversal methods of the binary tree :

  struct treenode {
    
    
      int val;
     treenode *left;
      treenode *right;
     treenode() : val(0), left(nullptr), right(nullptr) {
    
    }
      treenode(int x) : val(x), left(nullptr), right(nullptr) {
    
    }
      treenode(int x, treenode *left, treenode *right) : val(x), left(left), right(right) {
    
    }
 };

However, the initial value is still generated by customizing the binary tree structure, so the above code is summarized as follows:

#include <iostream>
#include <queue>
#include <stack>

using namespace std;

struct Node
{
    
    
    Node* left;
    Node* right;
    int data;
};

// preorder traversal
void preorderTraversal(Node* p)
{
    
    
    if (!p) return;
    cout << p->data << " ";		// 先输出根
    preorderTraversal(p->left);	// 次输出左子树
    preorderTraversal(p->right);// 最后右子树
}

// inorder traversal
void inorderTraversal(Node* p)
{
    
    
    if (!p) return;
    inorderTraversal(p->left);
    cout << p->data << " ";		
    inorderTraversal(p->right);
}

// postorder traversal
void postorderTraversal(Node* p)
{
    
    
    if (!p) return;
    postorderTraversal(p->left);
    postorderTraversal(p->right);
    cout << p->data << " ";
}

// level-order traversal
void levelOrderTraversal(Node* root)
{
    
    
    queue<Node*> q;
    q.push(root);
    while (!q.empty())
    {
    
    
        Node* p = q.front(); q.pop();
        cout << p->data << " ";
        if (p->left)  q.push(p->left);
        if (p->right) q.push(p->right);
    }
}


int main()
{
    
    
    // create binary tree
    Node* root = new Node{
    
     
        new Node{
    
     new Node{
    
     nullptr, nullptr, 4 }, new Node{
    
     nullptr, nullptr, 5 }, 2 },
        new Node{
    
     new Node{
    
     nullptr, nullptr, 6 }, new Node{
    
     nullptr, nullptr, 7 }, 3 },
        1
    };

    // perform traversals
    cout << "Preorder traversal: ";
    preorderTraversal(root);
    cout << endl;

    cout << "Inorder traversal: ";
    inorderTraversal(root);
    cout << endl;

    cout << "Postorder traversal: ";
    postorderTraversal(root);
    cout << endl;

    cout << "Level-order traversal: ";
    levelOrderTraversal(root);
    cout << endl;

    return 0;
}

After compiling, print out:

Preorder traversal: 1 2 4 5 3 6 7 
Inorder traversal: 4 2 5 1 6 3 7 
Postorder traversal: 4 5 2 6 7 3 1 
Level-order traversal: 1 2 3 4 5 6 7 

Some skills of hand calculation traversal

Generally, from the perspective of multiple-choice questions, you need to give the front, middle, or middle to determine the shape of the binary tree, and then push the middle order traversal, that is:

Preorder, inorder --> postorder
Postorder, inorder --> preorder

If the sequence is given, then the question becomes how many binary tree structures to find, such as the 2011 unified examination questions:

[2011 Unified Examination Question] The pre-order traversal sequence and post-order traversal sequence of a binary tree are 1, 2, 3, 4 and 4, 3, 2, 1 respectively, the in-order traversal sequence of the binary tree will not be ( ).

A . A. A.:1、2、3、4

B . B. B.:2、3、4、1

C . C. C.:3、2、4、1

D . D. D.:4、3、2、1

This question is because the pre-order NLR and the post-order LRN sequence are just opposite, so a complete binary tree cannot be determined, but the ancestral relationship of the nodes in the binary tree can be determined. Based on this, the order of the in-order nodes can be roughly inferred. For example, here, only the subtree with node 2 as the root node is considered, and it can only have left children.

So here we only consider another case under the premise of inorder, and the whole process can be expressed as the following figure:

insert image description here

This is a more official approach, and the root node is also very obvious, which can quickly locate and further divide and conquer. The other is to traverse the tree based on the super simple-projection method . One of the traversal methods is quoted as follows:

Post-order traversal is like blowing the branches of the binary tree to the left in the case of strong winds on the right, and the order is left subtree, right subtree, root, and the sun is directly shining, projecting all nodes to the ground. The binary tree in the figure, its post-order sequence projection is shown in the figure. The post-order traversal sequence is: DEBGFCA.

insert image description here

But this method actually only considers a one-dimensional vector, or considers the horizontal line as a coordinate axis, and only considers the change in the direction of x. The following method is the only one established through the horizontal axis and the vertical axis. Binary tree, skip the rules here, just look at the following picture I drew:
insert image description here
(PS: I drew the coordinate axis, but it is much uglier than when I didn’t draw it, emmm, I have to learn how to draw it)

This plan is derived from the brainless instant solution of station B! Given the pre/post-order traversal and in-order traversal, find the post/pre-order traversal. We found that the output of in-order traversal is placed on the y-axis, the pre-order and post-order are placed on the x-axis, and then connected point-to-point, the result is no different from the normal practice. Here we can also use my previous data structure design questions Summary of the big topic (non-code) A Baidu Encyclopedia picture quoted in this article is also applicable:

Of course, I counted another one. These 20 sequences are too many. I drew a little simpler on the iPad, which can express the complete question on one page:
insert image description here

The proof of this solution is also very simple, because the preorder is about the root, and the middle order is the left root and right, so:

insert image description here
And the known postorder (left and right roots), after being reversed, becomes (root right and left), which is also the above picture.

Binary tree traversal coding

There are actually two questions here:

Construct a binary tree from preorder and inorder traversal sequences

Construct binary tree from inorder and postorder traversal sequences

Here are the pre-order and middle-order questions as examples, the titles are:

insert image description here
Input: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
Output: [3,9,20,null,null,15,7]

输入: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
输出: [3,9,20,null,null,15,7]

Here is a dynamic graph demo:
insert image description here

The idea of ​​solving the problem is what I mentioned earlier plus a little detail:

When locating the root node in the in-order traversal, a simple method is to directly scan the results of the entire in-order traversal and find the root node, but the time complexity of doing so is relatively high. We can consider using a hash table to help us quickly locate the root node. For each key-value pair in the hash map, the key represents an element (the value of the node), and the value represents its occurrence position in the inorder traversal. Before the process of constructing a binary tree, we can scan the list traversed in order to construct this hash map. In the process of constructing the binary tree afterwards, we only need O(1)O(1) time to locate the root node.

So the code is:

class Solution {
    
    
private:
    unordered_map<int, int> index;

public:
    TreeNode* myBuildTree(const vector<int>& preorder, const vector<int>& inorder, int preorder_left, int preorder_right, int inorder_left, int inorder_right) {
    
    
        if (preorder_left > preorder_right) {
    
    
            return nullptr;
        }
        
        // 前序遍历中的第一个节点就是根节点
        int preorder_root = preorder_left;
        // 在中序遍历中定位根节点
        int inorder_root = index[preorder[preorder_root]];
        
        // 先把根节点建立出来
        TreeNode* root = new TreeNode(preorder[preorder_root]);
        // 得到左子树中的节点数目
        int size_left_subtree = inorder_root - inorder_left;
        // 递归地构造左子树,并连接到根节点
        // 先序遍历中「从 左边界+1 开始的 size_left_subtree」个元素就对应了中序遍历中「从 左边界 开始到 根节点定位-1」的元素
        root->left = myBuildTree(preorder, inorder, preorder_left + 1, preorder_left + size_left_subtree, inorder_left, inorder_root - 1);
        // 递归地构造右子树,并连接到根节点
        // 先序遍历中「从 左边界+1+左子树节点数目 开始到 右边界」的元素就对应了中序遍历中「从 根节点定位+1 到 右边界」的元素
        root->right = myBuildTree(preorder, inorder, preorder_left + size_left_subtree + 1, preorder_right, inorder_root + 1, inorder_right);
        return root;
    }

    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
    
    
        int n = preorder.size();
        // 构造哈希映射,帮助我们快速定位根节点
        for (int i = 0; i < n; ++i) {
    
    
            index[inorder[i]] = i;
        }
        return myBuildTree(preorder, inorder, 0, n - 1, 0, n - 1);
    }
};

The same is true for post-order and mid-order positioning. The end node of the post-order is the root node. In addition, a hash map can also be constructed to help us quickly locate the root node:

class Solution {
    
    
    int post_idx;
    unordered_map<int, int> idx_map;
public:
    TreeNode* helper(int in_left, int in_right, vector<int>& inorder, vector<int>& postorder){
    
    
        // 如果这里没有节点构造二叉树了,就结束
        if (in_left > in_right) {
    
    
            return nullptr;
        }

        // 选择 post_idx 位置的元素作为当前子树根节点
        int root_val = postorder[post_idx];
        TreeNode* root = new TreeNode(root_val);

        // 根据 root 所在位置分成左右两棵子树
        int index = idx_map[root_val];

        // 下标减一
        post_idx--;
        // 构造右子树
        root->right = helper(index + 1, in_right, inorder, postorder);
        // 构造左子树
        root->left = helper(in_left, index - 1, inorder, postorder);
        return root;
    }
    TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
    
    
        // 从后序遍历的最后一个元素开始
        post_idx = (int)postorder.size() - 1;

        // 建立(元素,下标)键值对的哈希表
        int idx = 0;
        for (auto& val : inorder) {
    
    
            idx_map[val] = idx++;
        }
        return helper(0, (int)inorder.size() - 1, inorder, postorder);
    }
};

Guess you like

Origin blog.csdn.net/submarineas/article/details/130185590