二叉树递归非递归遍历,层次遍历,反转,输出路径等常见操作详细总结

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/bitcarmanlee/article/details/85567552

1.序言

在实际工作中,很多业务场景其实也需要一些比较巧妙的算法来支撑,并不是业务逻辑就全是复制粘贴或者说重复的代码写一百遍。越是随着算法研究的深入,越是发现数据结构的重要性。或者说,数据结构中就蕴藏着无数精妙算法的思想,很多算法的思想在数据结构中体现得非常突出。而作为一种非线性的数据结构,二叉树是非常重要非常常见也非常牛逼的一种数据结构,里面包含有递归,栈,队列,dfs等等很多常见的操作。因此,特意写一篇比较长的文章,记录一下二叉树里面的一些常见操作以及里面包含的思想。

2.二叉树节点定义

二叉树内部是由一个一个的Node组成的。因此我们一般定义一个Node类。这个没什么好说的,直接上代码。

    static class Node<T> {
        T data;
        Node left = null;
        Node right = null;

        Node(T data) {
            this.data = data;
        }
    }

3.初始化二叉树

这个也没太多好说的,直接上代码。

    public static Node init() {
        Node n1 = new Node(1);
        Node n2 = new Node(2);
        Node n3 = new Node(3);
        Node n4 = new Node(4);
        Node n5 = new Node(5);
        Node n6 = new Node(6);
        Node n7 = new Node(7);
        Node n8 = new Node(8);
        n1.left = n2;
        n1.right = n3;
        n2.left = n4;
        n2.right = n5;
        n3.left = n6;
        n3.right = n7;
        n4.left = n8;
        return n1;
    }

返回的是树的根节点。

4.前序中序后续遍历递归

这个其实也没有太多好说的。但还是稍微说两句。
前序遍历是按根节点->左子树->右子树的顺序访问二叉树。
中序遍历是按左子树->根节点->右子树的顺序访问二叉树。
后序遍历是按左子树->根节点->右子树的顺序访问二叉树。
还是直接上代码

    public static void preOrder(Node root) {
        if (root != null) {
            System.out.print(root.data + " ");
            preOrder(root.left);
            preOrder(root.right);
        }
    }
    public static void midOrder(Node root) {
        if (root != null) {
            midOrder(root.left);
            System.out.print(root.data + " ");
            midOrder(root.right);
        }
    }
    public static void postOrder(Node root) {
        if (root != null) {
            postOrder(root.left);
            postOrder(root.right);
            System.out.print(root.data + " ");
        }

    }

递归的方式简单明了,相信也不用过多解释。

5.前序中序非递归

前序中序遍历非递归的方式较为简单一些。先说说这两种情况。

    public static void preOrder2(Node root) {
        Stack<Node> stack = new Stack();
        if (root == null) {
            return;
        }
        stack.push(root);
        while (stack.size() > 0) {
            Node tmp = stack.pop();
            System.out.print(tmp.data + " ");
            if (tmp.right != null) {
                stack.push(tmp.right);
            }
            if (tmp.left != null) {
                stack.push(tmp.left);
            }
        }
    }

    public static void midOrder2(Node root) {
        Stack<Node> stack = new Stack<>();
        while (root != null || ! stack.empty()) {
            while(root != null) {
                stack.push(root);
                root = root.left;
            }
            if (! stack.empty()) {
                Node tmp = stack.pop();
                System.out.print(tmp.data + " ");
                root = tmp.right;
            }
        }
    }

核心思想是利用栈这种数据结构的特点来模拟递归。
在前序遍历中,因为每一次遇到新的节点就要访问,所以直接用一个栈模拟即可。每次pop出一个最后一个压入栈顶端的节点并且访问,然后将该节点的左右子节点压入栈。注意因为栈的特点是后进先出,而前序遍历是先访问左节点再访问右节点,所以压栈的时候应该是先压右子节点再压左子节点,这样保证pop的时候是先pop出的左子节点。
而在中序遍历中,可以想象是先沿着左子树一直遍历直到某个节点的左子树为空,在这个过程中所有的节点自然都被压入栈中。当某个节点的左子树为空时,将该节点pop访问,并开始遍历该节点的右子树。

6.后续遍历的非递归

后续遍历的非递归方式最复杂,先上代码。

    private void postorder2(Node node) {
        if (node == null) return;

        Node cur = node;
        Node pre = node;

        Stack<Node> stack = new Stack<>();
        // cur移动到左子树最下面
        while (cur != null) {
            stack.push(cur);
            cur = cur.left;
        }
        while (! stack.isEmpty()) {
            // pop栈顶
            cur = stack.pop();
            if (cur.right != null && cur.right != pre) {
                // 根节点再入栈
                stack.push(cur);
                // 处理右子树
                cur = cur.right;
                while (cur != null) {
                    // 到右子树最下面
                    stack.push(cur);
                    cur = cur.left;
                }
            } else {
                System.out.print(cur.data + " ");
                pre = cur;
            }
        }
    }

后续遍历要先将左右子树都访问完毕以后再访问根节点。所以步骤如下:
1.首先肯定是沿着左子树往下搜索,并且一直做压栈操作。
2.当达到左子树为空以后,此时栈顶元素出栈。如果该元素右子树不为空,并且该元素的右子树未被访问,则先将该元素再压栈回去(因为该元素此时未被访问)。
3.将该元素的右子树也压栈,并且沿右子树的的左子树继续搜索。
4.当右子树为空或者已经被访问,此时元素可以被访问,出栈,访问,并且将当前访问节点标记。

7.层次遍历

层次遍历是从根节点开始,沿着二叉树的宽度一层一层往下遍历。由这个特点不难看出,我们可以利用队列先进先出的特点来模拟层次遍历。
更具体地说,我们先让根节点入队列,并且访问根节点。然后让根节点的左节点入队,再让右节点入队。这样左结点就存储在队头的位置,将首先被访问。
访问完根节点以后,左节点出队,同时访问左节点。访问完毕让左节点的左右节点依次入队。此时队列中的头节点为根节点的右节点。
上述过程循环,一直到队列为空即可。

    public static void levelOrder(Node root) {
        if (root == null) {
            return;
        }
        Queue<Node> queue = new LinkedList<>();
        queue.offer(root);
        while (!queue.isEmpty()) {
            Node tmp = queue.poll();
            System.out.print(tmp.data + " ");
            if (tmp.left != null) {
                queue.offer(tmp.left);
            }
            if (tmp.right != null) {
                queue.offer(tmp.right);
            }
        }
    }

猜你喜欢

转载自blog.csdn.net/bitcarmanlee/article/details/85567552