Let you understand how to analyze the binary tree algorithm through examples (question: Pat tree isomorphism)

Time:2021-1-17

Basic experiment 4-2.1 isomorphism of trees

  given two trees T1 and T2. If T1 can be changed into T2 by several times of left and right child exchange, then we call two trees “isomorphic”. For example, the two trees shown in Figure 1 are isomorphic, because after we exchange the left and right children of nodes a, B and g of one tree, we get another tree. And Figure 2 is not isomorphic.
Figure 1 (isomorphism)

Figure 2 (non isomorphic)

Basic experiment 4-2.1 isomorphism of trees (25 points)

algorithm analysis

For the binary tree algorithm, I summed up two very important ideas in the process of doing the problem.

  1. The recursive definition of tree itself leads to degradation analysis and generalization verification.
  2. “Sentry” thought.

Degradation analysis, generalization verification

  since the tree itself is recursively defined, assuming that the algorithm required by the problem is true on the complex tree $t $, then every subtree of $t $must be true. More popular, for any tree that meets the conditions of the topic, it must be true, even if the tree can not be simplified any more.
  the following uses this topic as an example to describe in detail how to analyze and verify.

analysis

First, we draw an extremely degenerate tree to study the isomorphism condition in the topic. As shown in the figure below, there are only two nodes in the tree.

Let you understand how to analyze the binary tree algorithm through examples (question: Pat tree isomorphism)

Obviously, when the data domain of the node is not considered, the binary tree of two nodes has only two cases as shown in the figure above. There are only two cases of isomorphism.

The first case
  for a binary tree on the left of a graph $t_ 1 $, it must be isomorphic with itself. Similarly, for the binary tree on the right of the graph $t_ 2 $, which is also isomorphic with oneself. According to the recursive property of binary tree, all its subtrees must be isomorphic with itself.

The second situation
  in another case, we found that $t_ 1 $and $t_ Two dollars is also isomorphic. That’s $t_ Left subtree of $1 and $t_ The right subtree of $2 is isomorphic_ Right subtree of $1 (null) and $t_ The left subtree (null) of 2 $is isomorphic.

By analyzing the binary tree of two nodes, we find the condition of isomorphism. Namely:

  1. Two trees $t_ 1 $and $t_ 2 $the data fields of the current node are equal.
  2. $T_ Left subtree of $1 and $t_ Left subtree isomorphism of 2 $, $t_ 1 $right subtree and $t_ 2 $right subtree is isomorphic.
  3. $T_ Left subtree of $1 and $t_ The right subtree of $2 is isomorphic_ 1 $right subtree and $t_ 2 $left subtrees are isomorphic.

  two trees are isomorphic as long as they satisfy the condition of $1 \ & \ & 2 $or $1 \ & \ & 3 $respectively.
So. By simplifying the form of binary tree, we can intuitively and quickly analyze a row efficient algorithm (regardless of performance).

Generalization verification

Then we can take our algorithm into a more complex binary tree for verification, and see if we can get a counterexample. Recently, we are busy reviewing the postgraduate entrance examination, and the time is limited. This step is for you to try.

Sentinel thought

  in the last step, I don’t know if you have noticed one small detail. In the second case, if a subtree is empty, we don’t judge it as empty. We only default that the empty subtree is isomorphic, that is, null is isomorphic to null.
  this idea brings great convenience to our code implementation. We don’t need to judge whether it is a leaf node or not, and we don’t need to worry about whether a certain subtree needs special treatment when recursion occurs. Instead, we use a logical “sentry” to judge.
The concept of “sentry” is used in many classical algorithms. For example, quick sort, KMP algorithm and so on. Using sentinel flexibly is helpful to simplify the implementation of our recursive algorithm.

Input processing

  in addition, there is a small pitfall in this problem, that is, the input data is not given in the order of first, middle and last or in the order of hierarchical traversal. It should be given at random. We need to find the root node in the input data.
  because it is a tree structure, we know that the tree is a semi-linear structure, each node has $1 $to $n $successors, but each node has only $1 $precursors at most. That is, except for the root node, the penetration of each node is $1 $. In other words, except for the root node, each node has a pointer or reference to it. So we can judge which is the root node by counting the in degree or the reference to the node. The only one that is not pointed to is the root node.

code

#include <stdio.h>

typedef struct Node{
    char c;
    int lchild;
    int rchild;
}BTNode;

int input_tree(int n, BTNode node[]);

/*
   @Params node1 tree 1, node2 tree 2, I, subscript of current node of tree 1, and subscript of current node of J tree 2 
   @Return isomorphism returns 1, otherwise returns 0 
*/
int is_same(BTNode node1[], BTNode node2[], int i, int j);

int main(){
    BTNode node1[11]={0}, node2[11]={0};
    int n;
    scanf("%d", &n);
    int root1 = input_ Tree (n, node1); // read the tree and return the subscript of the root of the tree 
    scanf("%d", &n);
    int root2 = input_tree(n, node2);
    int ans = is_ Same (node1, node2, root1, root2); // judge whether node1 and node2 are isomorphic 
    if(ans == 1) printf("Yes\n");
        else printf("No\n");
    return 0;
}

int input_tree(int n, BTNode node[]){
    char ch, lc, rc;
    int a[11]={0};
    for(int i=0; i<n; i++){
        scanf(" %c %c %c", &ch, &lc, &rc);
        node[i].c = ch;
        node[i].lchild = -1;
        node[i].rchild = -1;
        if (lc != '-'){
            Node [i]. Lchild = LC - 48; // subtracting 48 from the ASCII code of a number is the value of the number 
            a[lc-48] = 1;
        }
        if (rc != '-'){
            node[i].rchild = rc - 48;
            a[rc-48] = 1;
        }
    }
    int root = -1;
    For (int i = 0; I < n; I + +) {// the subscript of the array 0 is the subscript of the root node 
          if(a[i]==0){
              root = i;
              break;
          }
    }
    return root;
}

int is_same(BTNode node1[], BTNode node2[], int i, int j){
    If ((I = = - 1 & & J! = - 1) | (I = = - 1 & & J! = - 1)) return 0; // one side is null and the other side is not null 
    If (I = = - 1 & & J = = - 1) return 1; // all current nodes are null 
    If (node1 [i]. C! = node2 [J]. C) return 0; // the characters are not equal 
    int ll = is_ Same (node1, node2, node1 [i]. Lchild, node2 [J]. Lchild); // left and left isomorphism 
    int rr = is_ Same (node1, node2, node1 [i]. Rchild, node2 [J]. Rchild); // right and right isomorphism
    int lr = is_ Same (node1, node2, node1 [i]. Lchild, node2 [J]. Rchild); // left and right isomorphism 
    int rl = is_ Same (node1, node2, node1 [i]. Rchild, node2 [J]. Lcchild); // right and left isomorphism
    If ((ll = = 1 & & RR = = 1) | (LR = = 1 & & RL = = 1)) return 1; // left left right isomorphism or left right left isomorphism, then the subtree is isomorphic 
    else return 0;
}

Recommended Today

DK7 switch’s support for string

Before JDK7, switch can only support byte, short, char, int or their corresponding encapsulation classes and enum types. After JDK7, switch supports string type. In the switch statement, the value of the expression cannot be null, otherwise NullPointerException will be thrown at runtime. Null cannot be used in the case clause, otherwise compilation errors will […]