AI Translated from Chinese

Introduction - What is a Binary Tree

In computer science, a binary tree is a tree data structure in which each node has at most two children (i.e., there is no node with degree greater than 2). These children are typically called “left subtree” or “right subtree.” The branches of a binary tree have left-right order and cannot be reversed arbitrarily. Unlike ordinary trees, ordinary trees have at least one node, while binary trees can have zero nodes; ordinary trees have no limit on the maximum degree of nodes, while binary tree nodes have a maximum degree of 2; ordinary tree nodes have no left-right order, while binary tree nodes do have left-right order. — From Wikipedia - Binary tree

Binary trees are very important data structures. Their unique structure gives them excellent performance in searching and processing data.

Binary Tree Traversal

As the name suggests, nodes are traversed from top to bottom, left to right. Using the image above as an example, the traversal order is 1~15. This method is called Breadth-First Search (BFS).

The blogger believes that recursive traversal of binary trees is the most wonderful part. Understanding its three recursive traversal methods gives a glimpse of the structural beauty of binary trees.

For a binary tree T, its pre-order, in-order, and post-order traversals can be recursively defined as follows:

  • PreOrder(T) = T’s root node + PreOrder(T’s left subtree) + PreOrder(T’s right subtree)
  • InOrder(T) = InOrder(T’s left subtree) + T’s root node + InOrder(T’s right subtree)
  • PostOrder(T) = PostOrder(T’s left subtree) + PostOrder(T’s right subtree) + T’s root node

These three traversals all belong to recursive traversal, or Depth-First Search (DFS), because it always prioritizes going deeper. — From Liu Rujia “Algorithm Competition Introduction Classic (2nd Edition)”

Still using the image above, let’s talk about how to traverse recursively: To traverse the entire binary tree, first treat 1, 2, 3 as a small binary tree to traverse, where 1 is the root and 2, 3 are nodes. When traversing nodes 2 or 3, treat 2, 4, 5 or 3, 6, 7 as small binary trees respectively, and so on. The specific traversal order depends on the traversal method.

Is it simple? Before looking at the answers below, try writing out the order for these three traversal methods yourself.

Note: “Root” — root node, “Left” — left subtree, “Right” — right subtree Still using the image above:

Pre-order Traversal

Order: Root --> Left --> Right 1 2 4 8 9 5 10 11 3 6 12 13 7 14 15

In-order Traversal

Order: Left --> Root --> Right 8 4 9 2 10 5 11 1 12 6 13 3 14 7 15

Post-order Traversal

Order: Left --> Right --> Root 8 9 4 10 11 5 2 12 13 6 14 15 7 3 1

Example: Deriving Pre-order Traversal

Given a binary tree’s in-order and post-order traversals, how to reconstruct the tree to write its pre-order traversal?

Using Python-style pseudocode, [] represents list (ordered), {} represents set (unordered).

InOrder   = [7, 8, 11, 3, 5, 16, 12, 18]
PostOrder = [8, 3, 11, 7, 16, 18, 12, 5]

From the characteristics of post-order traversal, the last element 5 is the root. Find 5 in the in-order traversal, treat {7, 8, 11, 3} as the left subtree and {16, 12, 18} as the right subtree, and construct a binary tree:

              5
             / \
{7, 8, 11, 3}   {16, 12, 18}

Next, look at post-order traversal, find roots in [8, 3, 11, 7] and [16, 18, 12] respectively, and similarly construct small binary trees:

              5
             / \
            7   12
            \   / \
    {8, 11, 3} 16 18

Final step:

              5
             / \
            7   12
            \   / \
            11 16 18
            /\
           8  3

So we can get its pre-order traversal:

PreOrder = [5, 7, 11, 8, 3, 12, 16, 18]

Example Problems

Deriving Pre-order Traversal

This is the code implementation of the example above. Similar problems: Lia out playing, and reference answer

pre_order.py

pre_order, in_order, post_order = [], '', ''
def read_list():
    global pre_order, in_order, post_order
    in_order = input()
    post_order = input()
    pre_order = []
    in_order = list(map(int, in_order.split()))
    post_order = list(map(int, post_order.split()))


def build_pre(in_order, post_order):
    """Recursively construct binary tree pre-order traversal"""
    global pre_order

    # Visually see the recursion steps
    print("in_order   = ", in_order)
    print("post_order = ", post_order)
    print("pre_order  = ", pre_order, "\n")

    root = post_order[-1]
    pre_order.append(root)  # For pre-order traversal, just record the root when constructing subtrees

    root_in_order = in_order.index(root)
    in_left = in_order[:root_in_order]
    in_right = in_order[root_in_order+1 : len(in_order)]
    post_left = post_order[:len(in_left)]
    post_right = post_order[len(in_left) : -1]
    if len(post_left):
        build_pre(in_left, post_left)
    if len(post_right):
        build_pre(in_right, post_right)


while 1:
    try:
        read_list()
    except:
        break
    build_pre(in_order, post_order)
    print("PreOrder: ", end='')
    for i in pre_order:
        print(i, end=' ')
    print("\n")

Output Example:

7 8 11 3 5 16 12 18  # In-order
8 3 11 7 16 18 12 5  # Post-order
in_order   =  [7, 8, 11, 3, 5, 16, 12, 18]
post_order =  [8, 3, 11, 7, 16, 18, 12, 5]
pre_order  =  []  # Read data

in_order   =  [7, 8, 11, 3]
post_order =  [8, 3, 11, 7]
pre_order  =  [5]  # First recursion step, add root node 5

in_order   =  [8, 11, 3]
post_order =  [8, 3, 11]
pre_order  =  [5, 7]  # Second recursion step, add root node 7

in_order   =  [8]
post_order =  [8]
pre_order  =  [5, 7, 11]

in_order   =  [3]
post_order =  [3]
pre_order  =  [5, 7, 11, 8]

in_order   =  [16, 12, 18]
post_order =  [16, 18, 12]
pre_order  =  [5, 7, 11, 8, 3]

in_order   =  [16]
post_order =  [16]
pre_order  =  [5, 7, 11, 8, 3, 12]

in_order   =  [18]
post_order =  [18]
pre_order  =  [5, 7, 11, 8, 3, 12, 16]

PreOrder: 5 7 11 8 3 12 16 18

Level Order Traversal (Trees on the level, UVa 122)

Problem:

Analysis: Use dynamic structure to build tree. Code from the “Algorithm Competition Introduction Classic”:

trees_on_the_level.cpp

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<vector>
#include<queue>
using namespace std;

const int maxn = 256 + 10;

// Node type
struct Node
{
    bool have_value;
    int v;  // Node value
    Node *left, *right;
    Node() : have_value(false), left(NULL), right(NULL){}  // Constructor
};

Node *root;  // Root of binary tree

Node *newnode() { return new Node(); }

bool failed;
void addnode(int v, char *s)
{
    int n = strlen(s);
    Node *u = root;  // Traverse from root
    for (int i = 0; i < n; i++)
    {
        if (s[i] == 'L')
        {
            if (u->left == NULL) u->left = newnode();  // Node doesn't exist, create new node
            u = u->left;                               // Move left
        }
        else if (s[i] == 'R')
        {
            if (u->right == NULL) u->right = newnode();
            u = u->right;
        }
    }
    if (u->have_value) failed = true;
    u->v = v;
    u->have_value = true;  // Don't forget to mark
}

void remove_tree(Node *u)
{
    if (u == NULL) return;  // Early check is safer
    remove_tree(u->left);   // Recursively free left subtree space
    remove_tree(u->right);  // Recursively free right subtree space
    delete u;  // Call destructor and free node's memory
}

char s[maxn];
bool read_input()
{
    failed = false;
    remove_tree(root);  // Free previous tree's memory
    root = newnode();
    for (;;)
    {
        if (scanf("%s", s) != 1) return false;
        if (!strcmp(s, "()")) break;
        int v;
        sscanf(&s[1], "%d", &v);
        addnode(v, strchr(s, ',') + 1);
    }
    return true;
}

bool bfs(vector<int> &ans)  // Breadth-First Search
{
    queue<Node*> q;
    ans.clear();
    q.push(root);  // Initially only root
    while (!q.empty())
    {
        Node *u = q.front(); q.pop();
        if(!u->have_value) return false;  // Some node not assigned, input error
        ans.push_back(u->v);  // Add to output sequence
        if (u->left != NULL) q.push(u->left);  // Add left child to queue if exists
        if (u->right != NULL) q.push(u->right);  // Add right child to queue if exists
    }
    return true;  // Input correct
}

int main() {
    vector<int> ans;
    while(read_input())
    {
        if(!bfs(ans)) failed = 1;
        if(failed) printf("not complete\n");
        else
        {
            for(int i = 0; i < ans.size(); i++)
            {
                if(i != 0) printf(" ");
                printf("%d", ans[i]);
            }
            printf("\n");
        }
    }
    return 0;
}

Summary: Use struct + pointer to implement binary tree. Of course, array + index can also be used, but it depends on the situation.

Recursive Traversal of Binary Trees (Trees, UVa 548)

Problem:

Analysis: Given in-order and post-order traversals of a binary tree, derive pre-order traversal. Still from the classic book:

trees.cpp

#include <iostream>
#include <string>
#include <sstream>
#include <algorithm>
using namespace std;

const int maxv = 10000 + 10;
int in_order[maxv], post_order[maxv], lch[maxv], rch[maxv];
int n;

// Since all node weights are unique positive integers, use weights as node indices directly
bool read_list(int *a)
{
    string line;
    if (!getline(cin, line)) return false;
    stringstream ss(line);
    n = 0;
    int x;
    while (ss >> x) a[n++] = x;
    return n > 0;
}

// Build binary tree from in_order[L1..R1] and post_order[L2..R2], return tree root
int build(int L1, int R1, int L2, int R2)
{
    if (L1 > R1) return 0;  // Empty tree
    int root = post_order[R2];
    int p = L1;
    while (in_order[p] != root) p++;
    int cnt = p - L1;  // Number of nodes in left subtree
    lch[root] = build(L1, p-1, L2, L2+cnt-1);
    rch[root] = build(p+1, R1, L2+cnt, R2-1);
    return root;
}

int best, best_sum;  // Current best solution and corresponding weight sum

void dfs(int u, int sum)  // Depth First Search
{
    sum += u;
    if (!lch[u] && !rch[u])  // Leaf
    {
        if (sum < best_sum || (sum == best_sum && u < best))
        {
            { best = u; best_sum = sum; }
        }
        if (lch[u]) dfs(lch[u], sum);
        if (rch[u]) dfs(rch[u], sum);
    }
}

int main()
{
    while (read_list(in_order))
    {
        read_list(post_order);
        build(0, n-1, 0, n-1);
        best_sum = 1000000000;
        dfs(post_order[n-1], 0);
        cout << best << "\n";
    }
    return 0;
}

Summary: The difficulty lies in understanding how to use arrays lch and rch to store left and right subtrees of nodes and the tree-building process. It’s recommended to draw diagrams to help understanding.

Balance Scale (Not so Mobile, UVa 839)

Problem: PDF Analysis: Recursively defined binary tree. Still from the classic book, the code is quite concise and clever, worth learning:

not_so_mobile.cpp

#include <iostream>
using namespace std;

// Input a sub-balance, return if it's balanced, parameter W modified to total weight
bool solve(int &W)
{
    int W1, D1, W2, D2;
    bool b1 = true, b2 = true;
    cin >> W1 >> D1 >> W2 >> D2;
    if (!W1) b1 = solve(W1);
    if (!W2) b2 = solve(W2);
    W = W1 + W2;
    return b1 && b2 && (W1 * D1 == W2 * D2);  // b1: left, b2: right
}

int main()
{
    int T, W;
    cin >> T;
    while (T--)
    {
        if (solve(W)) cout << "YES\n"; else cout << "NO\n";
        if (T) cout << "\n";
    }
    return 0;
}

not_so_mobile.py

def solve(W):
    b1, b2 = True, True
    W1, D1, W2, D2 = map(int, input().split())
    if W1 == 0:
        b1 = solve(W1)
    if W2 == 0:
        b2 = solve(W2)
    W = W1 + W2
    return b1 and b2 and (W1*D1 == W2*D2)


W = 0
T = int(input())
while T > 0:
    T -= 1
    print("YES") if solve(W) else print("No")
    if T: print("\n")

The Python version seems more readable (

Python Class Implementation of Binary Tree

Object-oriented programming is really interesting. Building a fully functional class from scratch has the same excitement as building an automated farm in Minecraft (

tree.py

class Node(object):
    """Node class"""
    def __init__(self, data=None, left_child=None, right_child=None):
        self.data = data
        self.left_child = left_child
        self.right_child = right_child



class Tree(object):
    def __init__(self):
        self.root = Node()


    def rec_pre_order(self, node=None):
        """Recursive pre-order traversal"""
        if node:
            print(node.data, end=" ")
            self.rec_pre_order(node.left_child)
            self.rec_pre_order(node.right_child)


    def pre_order(self):
        """Non-recursive pre-order traversal"""
        if self.root:
            ls = [self.root]
        while ls:
            node = ls.pop()
            print(node.data, end=" ")
            if node.right_child:
                ls.append(node.right_child)
            if node.left_child:
                ls.append(node.left_child)


    def pre_order2(self):
        """Non-recursive pre-order traversal"""
        stack = []
        node = self.root
        while node or stack:
            while node:
                print(node.data, end=" ")
                stack.append(node)
                node = node.left_child
            if stack:
                node = stack.pop()
                node = node.right_child


    def rec_in_order(self, node):
        """Recursive in-order traversal"""
        if node:
            self.rec_in_order(node.left_child)
            print(node.data, end=" ")
            self.rec_in_order(node.right_child)


    def in_order(self):
        """Non-recursive in-order traversal"""
        ls = []
        node = self.root
        while node or len(ls):
            while node:
                ls.append(node)
                node = node.left_child
            if len(ls):
                node = ls.pop()
                print(node.data, end=" ")
                node = node.right_child


    def in_order2(self):
        """Non-recursive in-order traversal"""
        stack = []
        node = self.root
        while node:
            while node:
                if node.right_child:
                    stack.append(node.right_child)
                stack.append(node)
                node = node.left_child
            node = stack.pop()
            while stack and (not node.right_child):
                print(node.data, end=" ")
                node = stack.pop()
            print(node.data, end=" ")
            if stack:
                node = stack.pop()
            else:
                node = None


    def rec_post_order(self, node):
        """Recursive post-order traversal"""
        if node:
            self.rec_post_order(node.left_child)
            self.rec_post_order(node.right_child)
            print(node.data, end=" ")


    def post_order(self, node):
        """Non-recursive post-order traversal"""
        q = node
        ls = []
        while node:
            while node.left_child:
                ls.append(node)
                node = node.left_child

            while node and (node.right_child is None or node.right_child == q):
                print(node.data, end=" ")
                q = node
                if not ls:
                    return
                node = ls.pop()
            ls.append(node)
            node = node.right_child


    def get_depth(self):
        """Calculate tree depth, recursive left and right nodes, take larger depth"""
        def _depth(node):
            if not node:
                return 0
            else:
                left_depth = _depth(node.left_child)
                right_depth = _depth(node.right_child)
                if left_depth > right_depth:
                    return left_depth + 1
                else:
                    return right_depth + 1

        return _depth(self.root)


    def get_leaves(self, node):
        """Recursively output all leaf nodes"""
        if node:
            if not node.left_child and not node.right_child:
                print(node.data, end=" ")
            else:
                self.get_leaves(node.left_child)
                self.get_leaves(node.right_child)


if __name__ == '__main__':
    tree = Tree()
    tree.root = Node('A')
    tree.root.left_child = Node('B')
    tree.root.right_child = Node('C')
    tree.root.left_child.left_child = Node('D')
    tree.root.left_child.right_child = Node('E')
    tree.root.left_child.right_child.right_child = Node('F')
    tree.root.left_child.left_child.right_child = Node('G')

    print("""
                 A
                 |
           -------------
           |           |
           B           C
           |
     -------------
     |           |
     D           E
     |           |
  -------     -------
  |     |     |     |
(None)  G   (None)  F
""")

    print("#Pre-order Traversal")
    print("Recursive:")
    tree.rec_pre_order(tree.root)
    print("\nNon-recursive 1:")
    tree.pre_order()
    print("\nNon-recursive 2:")
    tree.pre_order2()

    print("\n\n#In-order Traversal")
    print("Recursive:")
    tree.rec_in_order(tree.root)
    print("\nNon-recursive 1:")
    tree.in_order()
    print("\nNon-recursive 2:")
    tree.in_order2()


    print("\n\n#Post-order Traversal")
    print("Recursive:")
    tree.rec_post_order(tree.root)
    print("\nNon-recursive:")
    tree.post_order(tree.root)

    print("\n\nBinary tree depth:", tree.get_depth())

    print("\nLeaf nodes:", end="")
    tree.get_leaves(tree.root)

Binary Search Tree

(I’ll leave this for later updates)

640

References

  1. Wikipedia - Binary tree
  2. Liu Rujia “Algorithm Competition Introduction Classic (2nd Edition)”