From DFS to visitor mode

Time:2020-11-17

1、 Walk and visit thought

Here, we use an intuitive real-world example to illustrate the idea of walk and visit

1. A metaphor

We assume that there is such a community, the houses in the community are independent villas. The organizational form of this community is a little strange. It is organized in a tree structure, as shown in the figure below

From DFS to visitor mode

ad locum

The node of the tree — > Villa

Branches of trees — roads connecting Villas

2. Walk order

Suppose Zhang San enters the community from the gate, and he wants to take a walk in the community. The habit of walking is depth first. Then the order of the villas he passes through is as follows,

A->B->Null->D-Null->Null->C->Null->Null

(the reason why null is added is that it is convenient to discuss the visit opportunity in the following section, and also make the code easier to understand and simplify, which can be ignored here.)

From DFS to visitor mode

And the above order is calledWalk orderIn fact, this is the order of depth traversal (note that this is only through the villa, not into the villa)

For a given tree, the order of depth traversal is fixed.

3. Visit opportunity

If Zhang San decides to enter the villa to visit the owner or do some other things when he passes through a villa, what we are going to do is abstract the visit operation. For each villa, there are three possibilities of visiting time, as shown in the figure below

From DFS to visitor mode

For a, for a, the timing can be

Timing 1: I didn’t plan to walk there before I got to B

Opportunity 2: return from B, but not yet ready to go to C

Timing 3: return from C

If this code is used to describe the time, then the order of visit is

void walk(TreeNode node) {

    //Time 1, not to B yet
    
    walk( node.left ); // at this point, B is reached

    //Time 2, has returned from B
    
    walk( node.right ); // at this point, C is reached

    //Opportunity 3, also back from C
    
    return;

}

When you call walk (TreeNode A) in the program, you can think that you have reached the node A..

When it comes to the structure of the tree, one or more times can be selected according to different needs, and different operations can be carried out for each time

4. Talk about the traversal of pre order, middle order and post order

What is the use of introducing walk and visit, and the three opportunities of visit?

Let’s look at a practical application. That is to use the above template to rewrite the pre order, middle order, and post order traversal code. First define the structure of treenode

class TreeNode {
  int val;
    TreeNode left;
    TreeNode right;

    TreeNode(int x) {val = x;}
}

Let’s first look at how to write the preceding code.

Here, we need to understand what the operation of visit is in the preorder traversal. We may as well print out the value of the node.

So, when will the visit operation be done? In fact, it’s a matter of timing. So, preorder traversal is to operate as soon as we arrive at the node before accessing the left and right child nodes. Therefore, we can write the following code

//Preface
void walk(TreeNode node) {
    //Go to the empty node and do nothing
    if (node == null) return;

    print( node.val ); // it has not entered the left and right nodes
    walk(node.left);
    walk(node.right);
    return;
}

Similarly, the middle order is to access from the left node and print it. The code is as follows

//Middle Preface
void walk(TreeNode node) {
    //Go to the empty node and do nothing
    if (node == null) return;

    walk(node.left);
    print( node.val ); // back from the left node
    walk(node.right);
    return;
}

The same is true

//Afterword
void walk(TreeNode node) {
    //Go to the empty node and do nothing
    if (node == null) return;

    walk(node.left);
    walk(node.right);
    print(node.val);
    return;

}

So, what’s the difference between this and our usual traversal of the order before, in, and after? If we don’t use the idea of walk and visit, it’s like this

void preOrder(TreeNode node) {
    if(node == null) return;

    print(node.val);
    preOrder(node.left);
    preOrder(node.right);
    return;
}

void inOrder(TreeNode node) {
    if(node == null) return;

    print(node.val);
    inOrder(node.left);
    inOrder(node.right);
    return;
}

void postOrder(TreeNode node) {
    if(node == null) return;

    print(node.val);
    postOrder(node.left);
    postOrder(node.right);
    return;
}

As mentioned above, for a given tree, there is only one walk order, while for visit, there are three times. The code is as follows

void walk(TreeNode node) {

    if(node == null) return;

    //operateA()
    walk(node.left);
    //operateB()
    walk(node.right);
    //operateC()
    return;

}

void operateA() {}
void operateB() {}
void opearteC() {}

Then you can tell the difference between the two

Using the walk order to write three traversal, in fact, the three traversal are the same order, but the timing of visit is different.

Using three traversal ideas to write three traversal algorithms, you see three different orders

In fact, there are great differences between the above two points of view. When you use the walk order to write different visit times, you can often write clearer and easier code. For simple printing, it may not be obvious. Here is a more complex example.

5. Print all paths of 2-tree

Please refer to

binary-tree-paths

To analyze the problem, we use a variable path to record the path from root to current node

The key here is to choose the time of operation and what operations to do, that is, the timing of visit and what to do specifically.

First of all, when we reach a node, if the node is a non leaf node, we need to node.val +”- >” is added to the path

If it is a leaf node, you need to set the node.val Add to the path and put it in the final path collection paths

The above can be the operation of opportunity 1, and the code is as follows:

static public void append(TreeNode node) {

    //Leaf node
    if (node.left == null && node.right == null) {
        path.append(node.val);
        paths.add(path.toString());
        return;
    }

    //Non leaf node
    path.append(node.val + "->");
    return;

}

Is there anything else that needs to be done?

What we have done above is to add to the path. If we only add but not subtract, the path will be included in other nodes. This is wrong. Therefore, when we leave a node and instant machine 3, do the following operations

 static public void strip(TreeNode node) {

    int valSize = Integer.toString(node.val).length();

    if (node.left == null && node.right == null) {
        path.delete(path.length() - valSize, path.length());
        return;
    }

    path.delete(path.length() - (2 + valSize), path.length());
    return;
}

The walk code, plus the operation of opportunity one and opportunity three, is as follows

static public void walk(TreeNode node) {

    if (node == null) {
        return;
    }

    append(node);
    walk(node.left);
    walk(node.right);
    strip(node);

    return;
}

It can be seen that using walk to solve problems is mainly to consider two points

  1. Choose the timing of the operation
  2. What is the operation

If it is, using the idea of “traversal”, the following code may be written

public void innerBinaryTreePaths(TreeNode root) {
    
    int valSize = Integer.toString(root.val).length();
    path.append(root.val);

    if(root.left == null && root.right == null) {
        paths.add(path.toString());
        path.delete(path.length() - valSize, path.length());
        return;
    }

    path.append("->");
    if(root.left != null) {
        innerBinaryTreePaths(root.left);
    }

    if(root.right != null) {
        innerBinaryTreePaths(root.right);

    }
    
    path.delete(path.length() - (2 + valSize), path.length());
    return;
    
}

This solution is obviously not as clear as the first one.

Compare the solution of the two ideas.

  1. The idea of walk and visit: we decouple the access order and operation. When we write code, we just care,When to operate and what to doWrite clearer code
  2. General traversal idea: then the access order and operation are coupled together, and the code is not clear

2. Further

The above only introduces binary trees. In fact, this kind of walk and visit have more general applicability. For example, the template of a multi tree can be like this

void walk(TreeNode node) {
  
  //Before entering the first child node
  
  for(int i = 0; i < node.childRen.length; i++){
    //Before entering each child node
    
    walk(node.childRen[i]);
    
    //After returning from each child node
    }
  
  //Returned from all child nodes
}

In fact, whether it is a binary tree, a multi tree, or a graph, the walk order can be reduced toDepth priority.

For binary trees, there are three access times, n-tree and N + 1 access times

Therefore, the operation of these data structures can be solved by using the ideas discussed above

Three further: Visitor mode

The above only illustrates its application in tree and graph structure, but this idea can be applied to more general structure. We can give any structure, and then specify an order, and then specify the operation time and specific operation. This is the visitor pattern.

In fact, the more general problem of this problem is visitor pattern, which separates algorithm from structure

For details, see

Visitor mode

4. Summary

The essence of walk and visit is as follows

Separate operations on a data structure from that data structure