Algorithms and Data Structures 21: Morris Traversal

Morris traversal

Morris traversal is used to save space when traversing binary trees
If it is a conventional binary tree traversal, whether it is implemented recursively or iteratively, the space complexity is O(M), M is the height of the tree, because each node will return to it three times, the difference in the order of traversal is here Which of the three visits to this node

// the first time
process(node.left)
// the second time
process(node.right)
// the third time

Therefore, there is a need to store the current node, and pop it from the stack when returning to the current node
How many nodes will be pushed into the stack, and the height of the tree determines

Morris traversal is a space-complex optimization of binary tree traversal.

Details of the Morris Traversal

Morris traversal is to use the free right pointer of the node in the tree

Suppose you come to the current node cur, at the beginning cur comes to the position of the head node
1) If cur has no left child, cur moves to the right (cur = cur.right)
2) If cur has a left child, find the rightmost node mostRight on the left subtree
a. If the right pointer of mostRight points to null, let it point to cur, and then move cur to the left (cur = cur.left)
b. If the right pointer of mostRight points to cur, let it point to null, then cur moves to the right (cur = cur.right)
3) When cur is empty, the traversal stops

In fact, it uses the right pointer state of the rightmost node of the left subtree of the current node to mark how many times it has come to the current node
If the right pointer of the rightmost node of the left subtree of the current node is null, it means that it is the first time to come to the current node
If the right pointer of the rightmost node of the left subtree of the current node points to the current node, it means that it has been here once before, and now it is the second time to come to the current node

Morris traversal code implementation

    /**
     * Morris sequence
     * @param head
     */
    public static void process(Node head) {
        if (head == null) return;
        Node cur = head; // The cur pointer, initially at the head node
        Node mostRight = null; // the rightmost node of the left subtree
        while (cur != null) {
            mostRight = cur.left; // left tree
            if (mostRight != null) { // left tree is not empty
                // go right
                while (mostRight.right != null && mostRight.right != cur) mostRight = mostRight.right;
                // The right pointer of the rightmost node of the left subtree is empty, the first time
                if (mostRight.right == null) {
                    // Point to the current node, then move cur to the left
                    mostRight.right = cur;
                    cur = cur.left;
                    continue;
                } else {
                    // The right pointer of the rightmost node of the left subtree is not empty. For the second time, set the right pointer of mosrtRight to empty
                    mostRight.right = null;
                }
            }
            // cur pointer to the right
            cur = cur.right;
        }
    }

Morris preorder traversal

    /**
     * Morris preorder traversal
     * @param head
     */
    public static void processPre(Node head) {
        if (head == null) return;
        Node cur = head;
        Node mostRight = null;
        while (cur != null) {
            mostRight = cur.left;
            if (mostRight != null) {
                while (mostRight.right != null && mostRight.right != cur) mostRight = mostRight.right;
                if (mostRight.right == null) {
                    mostRight.right = cur;
                    // The node with the left tree will be printed when it comes for the first time
                    System.out.println(cur.value);
                    cur = cur.left;
                    continue;
                } else {
                    mostRight.right = null;
                }
            } else {
                // There is no node in the left tree, directly print
                System.out.println(cur.value);
            }
            cur = cur.right;
        }
    }

Morris inorder traversal

    /**
     * Morris Inorder traversal
     * @param head
     */
    public static void processIn(Node head) {
        if (head == null) return;
        Node cur = head;
        Node mostRight = null;
        while (cur != null) {
            mostRight = cur.left;
            if (mostRight != null) {
                while (mostRight.right != null && mostRight.right != cur) mostRight = mostRight.right;
                if (mostRight.right == null) {
                    mostRight.right = cur;
                    cur = cur.left;
                    continue;
                } else {
                    mostRight.right = null;
                }
            }
            // Regardless of whether there is a left tree or not, they will come here, and those who have a left tree will come here for the second time
            System.out.println(cur.value);
            cur = cur.right;
        }
    }

Morris post-order traversal

    /**
     * Morris Subsequent traversal
     * @param head
     */
    public static void processPost(Node head) {
        if (head == null) return;
        Node cur = head;
        Node mostRight = null;
        while (cur != null) {
            mostRight = cur.left;
            if (mostRight != null) {
                while (mostRight.right != null && mostRight.right != cur) mostRight = mostRight.right;
                if (mostRight.right == null) {
                    mostRight.right = cur;
                    cur = cur.left;
                    continue;
                } else {
                    mostRight.right = null;
                    //Each time the node is returned for the second time, print the right edge of its left tree, in reverse order
                    printEdge(cur.left);
                }
            }
            cur = cur.right;
        }
        //Finally, print the right boundary of the entire tree in reverse order
        printEdge(head);
    }

    // Print the right edge of the tree in reverse order
    private static void printEdge(Node head) {
        // right boundary of tree, linked list reversed
        Node tail = reverse(head);
        Node cur = tail;
        // Traverse the linked list to print
        while (cur != null) {
            System.out.println(cur.value);
            cur = cur.right;
        }
        // return the linked list
        reverse(tail);
    }

    // Reverse the right-bound list of a tree
    private static Node reverse(Node head) {
        Node pre = null;
        Node cur = head;
        Node right = null;
        while (cur != null) {
            right = cur.right;
            cur.right = pre;
            pre = cur;
            cur = right;
        }
        return pre;
    }

Morris traversal to determine whether a tree is a search binary tree

/**
 * Morris Traverse to determine whether a tree is a search binary tree
 * Created by huangjunyi on 2022/9/10.
 */
public class Morris02 {

    private static class Node {
        Node left;
        Node right;
        int value;
    }

    public static boolean isBST(Node head) {
        if (head == null) return true;
        Node cur = head;
        Node mostRight = null;
        //Take a variable preValue to record the value of the node traversed before
        Integer preValue = null;
        while (cur != null) {
            mostRight = cur.left;
            if (mostRight != null) {
                while (mostRight.right != null && mostRight.right != cur) mostRight = mostRight.right;
                if (mostRight.right == null) {
                    mostRight.right = cur;
                    cur = cur.left;
                    continue;
                } else {
                    mostRight.right = null;
                }
            }
            // In the order of Morris in-order traversal, judge whether the value of the previous node is greater than the current node, if yes, return false, indicating that it is not a BST
            // Here, I don’t care if the pointer of the tree is changed and messed up, all return false directly, otherwise it cannot return false directly, and return after traversing
            if (preValue != null && cur.value <= preValue) return false;
            preValue = cur.value;
            cur = cur.right;
        }
        return true;
    }

}

Morris traverses to find the height of the leaf node closest to the head node of a tree

/**
 * Morris Traverse to find the height of the leaf node closest to the head node in a tree
 * Created by huangjunyi on 2022/9/10.
 */
public class Morris03 {

    private static class Node {
        Node left;
        Node right;
        int value;
    }

    public static int getMinHigh(Node head) {
        if (head == null) return 0;
        Node cur = head;
        Node mostRight = null;
        int minHigh = Integer.MAX_VALUE;
        int leftRightBoardsize = -1;
        int curLevel = 0; // current node level
        while (cur != null) {
            mostRight = cur.left;
            leftRightBoardsize = 1;
            if (mostRight != null) {
                //current node has left tree
                while (mostRight.right != null && mostRight.right != cur) {
                    // Find the rightmost node of the left tree of the current node while recording the number of nodes at the right boundary of the left tree
                    mostRight = mostRight.right;
                    leftRightBoardsize++;
                }
                if (mostRight.right == null) {
                    // Entering here means coming to the current node for the first time
                    // Modify the right pointer of the rightmost node of the left tree to point to the current node, and move the cur pointer to the left node
                    mostRight.right = cur;
                    curLevel++;
                    cur = cur.left;
                    continue;
                } else {
                    // Entering here means coming to the current node for the second time
                    // See if the left node of the rightmost node of the left subtree of the current node is empty, which means it is a leaf node
                    // It is found that the rightmost node of the left tree is a child node, clear it, and update minHigh
                    if (mostRight.left == null) {
                        minHigh = Math.min(minHigh, curLevel);
                    }
                    // curLevel is currently the height of the rightmost node of the left tree. Subtract the number of nodes at the right border of the left tree from leftRightBoardsize to obtain the height of the current node
                    curLevel -= leftRightBoardsize;
                    // Restores the right pointer of the rightmost node of the current node's left subtree
                    mostRight.right = null;
                }
            } else {
                //The current node does not have a left tree, only the number of layers ++
                curLevel++;
            }
            cur = cur.right;
        }
        return minHigh;
    }

}

When to Traverse with Morris

Don’t use it in the written test, just use the most common and best recursion
During the interview, you can use Morris traversal to optimize

If the answers of the left and right subtrees are strictly required to integrate the answers of the current node, then only recursion can be used instead of Morris traversal
If it is not strictly dependent on the answers of the left and right subtrees, it can be optimized with Morris traversal

Tags: Java Algorithm data structure

Posted by bran on Mon, 05 Dec 2022 21:41:45 +0530