In-depth understanding of C/C++ data structures: trees and binary trees: concepts, storage structures and traversal

Trees are a common data structure with wide applications in computer science and mathematics. The simplest form of a tree structure is a binary tree. This article will delve into the concepts of trees and binary trees, storage structures, and binary tree traversal, and provide some practical code examples to help understand these concepts.

The concept of trees and binary trees

Tree

A tree is a hierarchical data structure consisting of nodes (or vertices) and edges. One characteristic of a tree is that it has no loops, that is, there is no path that starts from a node and passes through several edges and then returns to the original node. A tree usually consists of a root node, which has no parent node, and several subtrees, each of which is also a tree. Nodes in the tree are divided into internal nodes and leaf nodes. Internal nodes have child nodes, while leaf nodes have no child nodes.

Binary Tree

A binary tree is a special tree structure in which each node has at most two child nodes, usually called left child node and right child node. There are many variations of binary trees, including Binary Search Tree and Balanced Binary Tree, which have important applications in data storage and retrieval.

The storage structure of trees and binary trees

Trees and binary trees can be stored in a variety of ways, the most common of which is using node and reference links. The following is the definition of a simple binary tree node:

struct BinaryTreeNode {
    int data;
    BinaryTreeNode* left;
    BinaryTreeNode* right;

    BinaryTreeNode(int value) : data(value), left(nullptr), right(nullptr) {}
};

Each node contains a data element and pointers to the left and right child nodes. In this way, the entire binary tree structure can be built recursively.

Binary tree traversal

Tree traversal refers to visiting all nodes in the tree according to certain rules in order to find, process or print their values. Commonly used binary tree traversal methods include pre-order traversal, in-order traversal and post-order traversal.

1. Preorder Traversal

Preorder traversal starts from the root node and traverses the nodes in root-left-right order. Here are examples of recursive and non-recursive C++ code:

Recursive way:
void preorderTraversal(BinaryTreeNode* node) {
    if (node != nullptr) {
        cout << node->data << " ";  // 先访问根节点
        preorderTraversal(node->left);  // 再访问左子树
        preorderTraversal(node->right);  // 最后访问右子树
    }
}
Non-recursive way (using stack):
#include <stack>

void iterativePreorderTraversal(BinaryTreeNode* root) {
    if (root == nullptr) return;

    stack<BinaryTreeNode*> nodeStack;
    nodeStack.push(root);

    while (!nodeStack.empty()) {
        BinaryTreeNode* current = nodeStack.top();
        nodeStack.pop();
        cout << current->data << " ";  // 访问当前节点

        if (current->right != nullptr) {
            nodeStack.push(current->right);  // 先将右子节点入栈
        }
        if (current->left != nullptr) {
            nodeStack.push(current->left);  // 再将左子节点入栈
        }
    }
}

2. Inorder Traversal

In-order traversal traverses nodes in left-root-right order. Here are examples of recursive and non-recursive C++ code:

Recursive way:
void inorderTraversal(BinaryTreeNode* node) {
    if (node != nullptr) {
        inorderTraversal(node->left);  // 先访问左子树
        cout << node->data << " ";  // 再访问根节点
        inorderTraversal(node->right);  // 最后访问右子树
    }
}
Non-recursive way (using stack):
void iterativeInorderTraversal(BinaryTreeNode* root) {
    stack<BinaryTreeNode*> nodeStack;
    BinaryTreeNode* current = root;

    while (current != nullptr || !nodeStack.empty()) {
        while (current != nullptr) {
            nodeStack.push(current);
            current = current->left;
        }

        current = nodeStack.top();
        nodeStack.pop();
        cout << current->data << " ";  // 访问当前节点
        current = current->right;
    }
}

3. Postorder Traversal

Postorder traversal traverses nodes in left-right-root order. Here are examples of recursive and non-recursive C++ code:

Recursive way:
void postorderTraversal(BinaryTreeNode* node) {
    if (node != nullptr) {
        postorderTraversal(node->left);  // 先访问左子树
        postorderTraversal(node->right);  // 再访问右子树
        cout << node->data << " ";  // 最后访问根节点
    }
}

Non-recursive way (using two stacks):
void iterativePostorderTraversal(BinaryTreeNode* root) {
    if (root == nullptr) return;

    stack<BinaryTreeNode*> s1, s2;
    s1.push(root);

    while (!s1.empty()) {
        BinaryTreeNode* current = s1.top();
        s1.pop();
        s2.push(current);

        if (current->left != nullptr) {
            s1.push(current->left);
        }
        if (current->right != nullptr) {
            s1.push(current->right);
        }
    }

    while (!s2.empty()) {
        BinaryTreeNode* current = s2.top();
        s2.pop();
        cout << current->data << " ";  // 访问当前节点
    }
}

Examples and Analysis

Consider the following binary tree: it doesn’t look good when drawn by hand and is typed out with spaces.

        1
       / \
      2   3
     / \
    4   5

The C++ code examples for pre-order, in-order and post-order traversal of this binary tree are as follows:

int main() {
    BinaryTreeNode* root = new BinaryTreeNode(1);
    root->left = new BinaryTreeNode(2);
    root->right = new BinaryTreeNode(3);
    root->left->left = new BinaryTreeNode(4);
    root->left->right = new BinaryTreeNode(5);

    cout << "前序遍历:";
    preorderTraversal(root);
    cout << endl;

    cout << "中序遍历:";
    inorderTraversal(root);
    cout << endl;

    cout << "后序遍历:";
    postorderTraversal(root);
    cout << endl;

    return 0;
}

These traversal methods will output the following results:

Preorder traversal: 1 2 4 5 3

In-order traversal: 4 2 5 1 3

Postorder traversal: 4 5 2 3 1

In other words, the three binary tree traversal methods are the traversal methods of the root node at different positions, and they have different application scenarios and uses when processing binary trees. Let's discuss these three traversal methods in more detail:

  1. Preorder Traversal: Preorder traversal starts from the root node, and then traverses the nodes in the order of root-left-right. In preorder traversal, the root node is visited first, then the left subtree is visited recursively, and finally the right subtree is visited recursively. This type of traversal is typically used to create copies of trees or to evaluate expressions.

  2. Inorder Traversal: Inorder traversal traverses nodes in the order of left-root-right. In inorder traversal, the left subtree is visited recursively first, then the root node is visited, and finally the right subtree is visited recursively. This traversal method is usually used to get the elements of a binary search tree in ascending order.

  3. Postorder Traversal: Postorder traversal traverses nodes in left-right-root order. In post-order traversal, the left subtree is visited recursively first, then the right subtree is visited recursively, and finally the root node is visited. This type of traversal is typically used to free memory in the tree or perform some calculations.

These C++ code examples clearly show how to create a binary tree and perform preorder, inorder, and postorder traversals using recursive and non-recursive methods. These traversal methods have different uses in different applications. For example, preorder traversal can be used to copy the entire tree structure, inorder traversal is used to obtain ordered data, and postorder traversal is often used to calculate expression trees.

Guess you like

Origin blog.csdn.net/qq_72290695/article/details/134095499