zoukankan      html  css  js  c++  java
  • 基于Java的二叉树的三种遍历方式的递归与非递归实现

    二叉树的遍历方式包括前序遍历中序遍历后序遍历,其实现方式包括递归实现非递归实现

    前序遍历:根节点 | 左子树 | 右子树

    中序遍历:左子树 | 根节点 | 右子树

    后序遍历:左子树 | 右子树 | 根节点

    1. 递归实现

    递归方式实现代码十分简洁,三种遍历方式的递归实现代码结构相同,只是执行顺序有所区别。

    前序遍历:

    public class preOrderRecur {
        List<Integer> res = new ArrayList<>();
        public List<Integer> preOrderTraversal(TreeNode root) {
            if (root != null) {
                res.add(root.val); // 根节点
                preOrderTraversal(root.left); // 左子树
                preOrderTraversal(root.right); // 右子树
            }
            return res;
        }
    }
    

    中序遍历:

    public class inOrderRecur {
        List<Integer> res = new ArrayList<>();
        public List<Integer> inOrderTraversal(TreeNode root) {
            if (root != null) {
                inOrderTraversal(root.left); // 左子树
                res.add(root.val); // 根节点
                inOrderTraversal(root.right); // 右子树
            }
            return res;
        }
    }
    

    后序遍历:

    public class inOrderRecur {
        List<Integer> res = new ArrayList<>();
        public List<Integer> inOrderTraversal(TreeNode root) {
            if (root != null) {
                inOrderTraversal(root.left); // 左子树
                inOrderTraversal(root.right); // 右子树
                res.add(root.val); // 根节点
            }
            return res;
        }
    }
    

    2. 迭代实现

    2.1 使用辅助栈——空间复杂度O(N)

    2.1.1 中序遍历
    • 从当前结点一直向其最左孩子搜索,直到没有左孩子了停止,这个过程中将路程中的所有结点入栈;
    • 弹出栈顶元素,将其记录在答案中,并把当前结点置为弹出元素的右孩子并重复第一步过程。
    public class inOrderIterator {
        List<Integer> res = new ArrayList<>();
        public List<Integer> inOrderTraversal(TreeNode root) {
            Stack<TreeNode> stack = new Stack<>();
            while (root != null || !stack.isEmpty()) {
                if (root != null) {
                    stack.push(root);
                    root = root.left;
                } else {
                    TreeNode node = stack.pop();
                    res.add(node.val);
                    root = node.right;
                }
            }
            return res;
        }
    }
    
    2.1.2 前序遍历

    方法1:因为前序遍历访问顺序是“中-左-右”,所以可以先将根结点压栈,然后按照下列步骤执行。

    • 如果栈不为空,则弹出栈顶元素存入结果中;
    • 如果弹出元素的右孩子不为空则将右孩子压栈,然后如果其左孩子也不为空将其左孩子压栈(因为栈是后入先出的,所以为了达到“中-左-右”的顺序,需要先压入右孩子,再压入左孩子)。
    public class preOrderIterator {
        List<Integer> res = new ArrayList<>();
        public List<Integer> inOrderTraversal(TreeNode root) {
            if (root == null) return res;
            Stack<TreeNode> stack = new Stack<>();
            stack.push(root);
            while (!stack.isEmpty()) {
                root = stack.pop();
                res.add(root.val);
                // 右孩子压栈
                if (root.right != null) stack.push(root.right);
                // 左孩子压栈
                if (root.left != null) stack.push(root.left);
            }
            return res;
        }
    }
    

    方法2:根据中序遍历进行微调:

    public class preOrderIterator {
        List<Integer> res = new ArrayList<>();
        public List<Integer> inOrderTraversal(TreeNode root) {
            Stack<TreeNode> stack = new Stack<>();
            while (root != null || !stack.isEmpty()) {
                if (root != null) {
                    res.add(root.val);
                    stack.push(root);
                    root = root.left;
                } else {
                    TreeNode node = stack.pop();
                    root = node.right;
                }
            }
            return res;
        }
    }
    
    2.1.3 后序遍历

    因为前序遍历的顺序是“左-中-右”,而后序遍历顺序是“左-右-中”,不考虑左结点,区别只是在于中结点和右结点的顺序进行了反向而已,因此可以使用前序遍历的代码进行调整,只需要将前序遍历对左右孩子压栈的顺序反向即可,即先压入左孩子,再压入右孩子。除此之外,因为按照这种方法调整得到的遍历顺序为“中-右-左”,正好是后序遍历的反向顺序,因此在获得遍历序列后还需进行逆序操作。

    public class postOrderIterator {
        List<Integer> res = new LinkedList<>();
        public List<Integer> postOrderTraversal(TreeNode root) {
            if (root == null) return res;
            Stack<TreeNode> stack = new Stack<>();
            stack.push(root);
            while (!stack.isEmpty()) {
                root = stack.pop();
                // 头插法
                res.add(0, root.val);
                // 左孩子压栈
                if (root.left != null) stack.push(root.left);
                // 右孩子压栈
                if (root.right != null) stack.push(root.right);
            }
            return res;
        }
    }
    

    2.2 Morris遍历——空间复杂度O(1)

    该方法的思路简单说就是,对于每一个结点,找到它左孩子的最右子结点,因为按照正常访问顺序,其左孩子的最有子节点访问完后就应该访问其本身了,因此将其左孩子最右子节点的右指针指向它。基本步骤如下:

    • 如果当前结点左孩子为空,说明最左边访问完毕,将其置为其右孩子
    • 如果当前结点左孩子不为空,那么开始尝试找到该结点左孩子的最右子节点,建立连接关系
      • 如果找到的当前结点的左孩子的最右子节点右指针为空,说明还未建立连接关系,是首次访问当前结点,那么将该最右结点的右指针指向当前结点,然后当前结点向左孩子走一步继续重复所有步骤。
      • 如果找到的当前结点的左孩子的最右子节点右指针不为空,说明已建立过连接关系,是第二次访问当前结点,这意味着当前结点的左子树应该已经全部遍历完了,此时应恢复连接关系重新置为空,然后当前结点向右孩子走一步继续重复所有步骤。

    该方法虽然保证了O(1)的空间复杂度,但在遍历过程中改变了部分结点的指向,破坏了树的结构。

    2.2.1 中序遍历
    public class inOrderMorris {
        List<Integer> res = new ArrayList<>();
        public List<Integer> inOrderTraversal(TreeNode root) {
            TreeNode pre = null;
            TreeNode cur = root;
            while (cur != null) {
                if (cur.left == null) {
                    res.add(cur.val);
                    cur = cur.right;
                } else {
                    pre = cur.left;
                    while (pre.right != null && pre.right != cur) pre = pre.right;
                    if (pre.right == null) {
                        pre.right = cur;
                        cur = cur.left;
                    } else {
                        res.add(cur.val);
                        pre.right = null;
                        cur = cur.right;
                    }
                }
            }
            return res;
        }
    }
    
    2.2.2 前序遍历
    public class preOrderMorris {
        List<Integer> res = new ArrayList<>();
        public List<Integer> preOrderTraversal(TreeNode root) {
            TreeNode pre = null;
            TreeNode cur = root;
            while (cur != null) {
                if (cur.left == null) {
                    res.add(cur.val);
                    cur = cur.right;
                } else {
                    pre = cur.left;
                    while (pre.right != null && pre.right != cur) pre = pre.right;
                    if (pre.right == null) {
                        res.add(cur.val);
                        pre.right = cur;
                        cur = cur.left;
                    } else {
                        pre.right = null;
                        cur = cur.right;
                    }
                }
            }
            return res;
        }
    }
    
    2.2.3 后序遍历

    前序遍历反向的思想

    public class postOrderMorris {
        List<Integer> res = new LinkedList<>();
        public List<Integer> postOrderTraversal(TreeNode root) {
            TreeNode pre = null;
            TreeNode cur = root;
            while (cur != null) {
                if (cur.right == null) {
                    res.add(0, cur.val);
                    cur = cur.left;
                } else {
                    pre = cur.right;
                    while (pre.left != null && pre.left != cur) pre = pre.left;
                    if (pre.left == null) {
                        res.add(0, cur.val);
                        pre.left = cur;
                        cur = cur.right;
                    } else {
                        pre.left = null;
                        cur = cur.left;
                    }
                }
            }
            return res;
        }
    }
    
  • 相关阅读:
    常用PHP array数组函数
    每天学习30分钟新知识之html教程1
    laravel学习之路2: jwt集成
    JWT简介json web token bear token
    MDwiki 调研
    laravel学习之路1:认证相关
    OAuth 2.0介绍
    第一行代码 6.4 数据存储全方案-详解持久化数据- 数据库
    github(1)安装及使用图文详解
    Android集成讯飞语音、百度语音、阿里语音识别
  • 原文地址:https://www.cnblogs.com/chiaki/p/13543524.html
Copyright © 2011-2022 走看看