十二、二叉树的遍历

2022-01-21  本文已影响0人  咸鱼Jay

简介

前序遍历(Preorder Traversal)

前序遍历(Preorder Traversal)
访问顺序: \color{#ed7d30}{根节点}、前序遍历\color{#00afef}{左子树}、前序遍历\color{#00b050}{右子树}
\color{#ed7d30}{7}、\color{#00afef}{4、2、1、3、5}、\color{#00b050}{9、8、11、10、12}

前序遍历 – 递归

public void preorderTraversal() {
    preorderTraversal(root);
}

private void preorderTraversal(Node<E> node) {
    if(node == null) return;
    System.out.println(node.element);
    preorderTraversal(node.left);
    preorderTraversal(node.right);
}

前序遍历 – 非递归

前序遍历 – 非递归
public void preorder(Visitor<E> visitor) {
    if(visitor == null || root == null) return;
    Node<E> node = root;
    Stack<Node<E>> stack = new Stack<>();
    while(true) {
        if(node != null) {
            if(visitor.visit(node.element)) return;
            // 将右子节点入栈
            if(node.right != null) {
                stack.push(node.right);
            }
            // 向左走
            node = node.left;
        }else if(stack.isEmpty()) {
            return;
        }else {
            node = stack.pop();
        }
    }
}

非递归前序遍历的另一种思路

非递归前序遍历的另一种思路
public void preorder(Visitor<E> visitor) {
    if(visitor == null || root == null) return;
    Stack<Node<E>> stack = new Stack<>();
    stack.push(root);
    while(!stack.isEmpty()) {
        Node<E> node = stack.pop();
        if(visitor.visit(node.element)) return;
        if(node.right != null) {
            stack.push(node.right);
        }
        if(node.left != null) {
            stack.push(node.left);
        }
    }
}

中序遍历(Inorder Traversal)

中序遍历(Inorder Traversal)

访问顺序: 中序遍历\color{#00afef}{左子树}\color{#ed7d30}{根节点}、中序遍历\color{#00b050}{右子树}
\color{#00afef}{1、2、3、4、5}、\color{#ed7d30}{7}、\color{#00b050}{8、9、10、11、12}

\color{red}{如果访问顺序是下面这样呢?}
访问顺序:中序遍历\color{#00b050}{右子树}\color{#ed7d30}{根节点}、中序遍历\color{#00afef}{左子树}
\color{#00b050}{12、11、10、9、8 }、\color{#ed7d30}{7}、\color{#00afef}{5、4、3、2、1}

二叉搜索树的中序遍历特点:
二叉搜索树的中序遍历结果是升序或者降序的

中序遍历 – 递归

public void inorderTraversal() {
    inorderTraversal(root);
}

private void inorderTraversal(Node<E> node) {
    if(node == null) return;
    inorderTraversal(node.left);
    System.out.println(node.element);
    inorderTraversal(node.right);
}

中序遍历 – 非递归

中序遍历 – 非递归
public void inorder(Visitor<E> visitor) {
    if (visitor == null || root == null) return;
    Node<E> node = root;
    Stack<Node<E>> stack = new Stack<>();
    while (true) {
        if (node != null) {
            stack.push(node);
            // 向左走
            node = node.left;
        } else if (stack.isEmpty()) {
            return;
        } else {
            node = stack.pop();
            // 访问node节点
            if (visitor.visit(node.element)) return;
            // 让右节点进行中序遍历
            node = node.right;
        }
    }
}

后序遍历(Postorder Traversal)

后序遍历(Postorder Traversal)

访问顺序: 后序遍历\color{#00afef}{左子树}、后序遍历\color{#00b050}{右子树}\color{#ed7d30}{根节点}
\color{#00afef}{1、3、2、5、4}、\color{#00b050}{8、10、12、11、9}、\color{#ed7d30}{7}

后序遍历 – 递归

public void postorderTraversal() {
    postorderTraversal(root);
}

private void postorderTraversal(Node<E> node) {
    if(node == null) return;
    postorderTraversal(node.left);
    postorderTraversal(node.right);
    System.out.println(node.element);
}

后序遍历 – 非递归

后序遍历 – 非递归
public void postorder(Visitor<E> visitor) {
    if (visitor == null || root == null) return;
    Stack<Node<E>> stack = new Stack<>();
    stack.push(root);
    Node<E> prev = null;// 记录上一次弹出访问的节点
    while(!stack.isEmpty()) {
        Node<E> top =stack.peek();
        if(top.isLeaf() || (prev != null && prev.parent == top) ) {
            prev = stack.pop();
            if (visitor.visit(prev.element)) return;
        }else {
            if(top.right != null) {
                stack.push(top.right);
            }
            if(top.left != null) {
                stack.push(top.left);
            }
        }
    }
}
public void postorder(Visitor<E> visitor) {
    if (visitor == null || root == null) return;
    Stack<E> resStack = new Stack<>();
    Stack<Node<E>> stack = new Stack<>();
    stack.push(root);
    while(!stack.isEmpty()) {
        Node<E> node = stack.pop();
        resStack.push(node.element);
        if(node.left != null) {
            stack.push(node.left);
        }
        if(node.right != null) {
            stack.push(node.right);
        }
    }
    while (!resStack.isEmpty()) {
        E element = resStack.pop();
        if (visitor.visit(element)) return;
    }
}

层序遍历(Level Order Traversal)

层序遍历(Level Order Traversal)
访问顺序: 从上到下、从左到右依次访问每一个节点
\color{#ed7d30}{7}、\color{#00afef}{4、9}、\color{#00b050}{2、5、8、11}、1、3、10、12

实现思路:使用队列

  1. 将根节点入队
  2. 循环执行以下操作,直到队列为空
    • 将队头节点 A 出队,进行访问
    • 将 A 的左子节点入队
    • 将 A 的右子节点入队
public void levelOrderTraversal() {
    if(root == null) return;
    Queue<Node<E>> queue = new LinkedList<>();
    queue.offer(root);
    
    while(!queue.isEmpty()) {
        Node<E> node = queue.poll();
        System.out.println(node.element);
        if(node.left != null) {
            queue.offer(node.left);
        }
        if(node.right != null) {
            queue.offer(node.right);
        }
    }
}

设计遍历接口

思考
前面虽然写了前中后层序遍历,但是这写遍历,只是打印了节点数据,那么外界如何访问到这个节点数据呢?
如果允许外界遍历二叉树的元素?你会如何设计接口?

定义Visitor接口

public static interface Visitor<E> {
    void visit(E element);
}

修改前序遍历

public void preorder(Visitor<E> visitor) {
    preorder(root,visitor);
}

private void preorder(Node<E> node,Visitor<E> visitor) {
    if(node == null || visitor == null) return;
    visitor.visit(node.element);
    preorder(node.left,visitor);
    preorder(node.right,visitor);
}

修改中序遍历

public void  inorder(Visitor<E> visitor) {
    inorder(root,visitor);
}

private void  inorder(Node<E> node,Visitor<E> visitor) {
    if(node == null || visitor == null) return;
    inorder(node.left,visitor);
    visitor.visit(node.element);
    inorder(node.right,visitor);
}

修改后序遍历

public void  postorder(Visitor<E> visitor) {
     postorder(root,visitor);
}

private void  postorder(Node<E> node,Visitor<E> visitor) {
    if(node == null || visitor == null) return;
     postorder(node.left,visitor);
     postorder(node.right,visitor);
     visitor.visit(node.element);
}

修改层序遍历

public void levelOrder(Visitor<E> visitor) {
    if(root == null || visitor == null) return;
    Queue<Node<E>> queue = new LinkedList<>();
    queue.offer(root);
    
    while(!queue.isEmpty()) {
        Node<E> node = queue.poll();
        visitor.visit(node.element);
        if(node.left != null) {
            queue.offer(node.left);
        }
        if(node.right != null) {
            queue.offer(node.right);
        }
    }
}

使用

bst.preorder(new Visitor<Integer>() {
    @Override
    public void visit(Integer element) {
        System.out.print(element+" ");
    }
});

增强遍历接口

现在提供了给外界遍历的接口,但是如果遍历的中间想停掉遍历,该怎么处理呢?
修改Visitor接口里的方法和层序遍历


但是这样修改了Visitor接口后,层序遍历可以完美的兼容,那么前、中、后序遍历就没法兼容了。

这里拿中序遍历来举例:
如果visitor.visit(node.element);的返回值用一个类似全局变量的记录一下,下次遍历的时候就可以根据上次的进行判断,但是如果直接使用全局遍历的话,在多线程操作下就会有问题。所以这里可以在Visitor里面新增一个变量进行使用就可以。

遍历的应用

根据遍历结果重构二叉树


举例:


计算二叉树的高度

二叉树的高度其实就是根结点的高度。什么叫做高度呢?从当前结点开始一直到最远的叶子结点,所经历的结点数量就是高度。

递归方法

计算某个结点的高度其实就是看它左右结点的高度的最大值。

public int height() {
    return height(root);
}

private int height(Node<E> node) {
    if(node == null) return 0;
    return 1 + Math.max(height(node.left),height(node.right));
}

非递归方法

使用非递归的方法,就是迭代实现,可以用层序遍历来实现。
这里使用层序遍历,当每访问一层就使局部变量height++,那我们怎么知道什么时候访问完一层呢?通过层序遍历的特点,可以知道每访问完一层,就可以通过队列知道下一层的结点数量。因此可以定义局部变量levelSize来表示每一层的结点数量。

public int height() {
    if(root == null) return 0;
    
    //树的高度
    int height = 0;
    // 存储着每一层的元素数量
    int levelSize = 1;
    Queue<Node<E>> queue = new LinkedList<>();
    queue.offer(root);
    
    while(!queue.isEmpty()) {
        Node<E> node = queue.poll();
        levelSize--;//levelSize减为0,代表这一层就访问完了。
        
        if(node.left != null) {
            queue.offer(node.left);
        }
        if(node.right != null) {
            queue.offer(node.right);
        }
        
        if(levelSize == 0) {//意味着即将要访问下一层
            levelSize = queue.size();
            height++;
        }
    }
    return height;
}

判断一棵树是否为完全二叉树

这里为了方便实现,需要在Node结点类中新增加是否是叶子结点isLeaf方法,和是否有左右两个结点hasTwoChildren方法

private static class Node<E> {
    E element;
    Node<E> left;
    Node<E> right;
    Node<E> parent;
    public Node(E element, Node<E> parent) {
        this.element = element;
        this.parent = parent;
    }
    
    // 是否是叶子结点
    public boolean isLeaf() {
        return left == null && right == null;
    }
    
    // 是否有左右两个结点
    public boolean hasTwoChildren() {
        return left != null && right != null;
    }
}
public boolean isComplete() {
    if(root == null) return false;
    Queue<Node<E>> queue = new LinkedList<>();
    queue.offer(root);
    
    boolean leaf = false;
    while(!queue.isEmpty()) {
        Node<E> node = queue.poll();
        if(leaf && !node.isLeaf()) {
            //如果要求你是叶子结点,但是你不是叶子结点
            return false;
        }
        
        if(node.hasTwoChildren()) {
            queue.offer(node.left);
            queue.offer(node.right);
        }else if (node.left == null && node.right != null) {
            return false;
        }else {// 后面遍历的结点都必须是叶子结点
            leaf = true;
            if(node.left != null) {
                queue.offer(node.left);
            }
        }
    }
    return true;
}

下面我们简化代码逻辑

public boolean isComplete() {
    if(root == null) return false;
    Queue<Node<E>> queue = new LinkedList<>();
    queue.offer(root);
    
    boolean leaf = false;
    while(!queue.isEmpty()) {
        Node<E> node = queue.poll();
        if(leaf && !node.isLeaf()) {
            //如果要求你是叶子结点,但是你不是叶子结点
            return false;
        }
        
        if(node.left != null) {
            queue.offer(node.left);
        }else if(node.right != null) {
            // node.left == null && node.right != null
            return false;
        }
        
        if(node.right != null) {
            queue.offer(node.right);
        }else {// node.right == null
            leaf = true;
        }
    }
    return true;
}

226. 翻转二叉树

翻转一棵二叉树。

示例:

输入:

     4
   /   \
  2     7
 / \   / \
1   3 6   9

输出:

     4
   /   \
  7     2
 / \   / \
9   6 3   1

方法一:递归

前序遍历

public TreeNode invertTreePreorder(TreeNode root) {
    // 采用前序遍历
    if(root == null) return root;
    
    // 交换一下左右
    TreeNode tmp = root.left;
    root.left = root.right;
    root.right = tmp;
    
    invertTreePreorder(root.left);// 遍历左
    invertTreePreorder(root.right);// 遍历右
    return root;
}

中序遍历

public TreeNode invertTreeInorder(TreeNode root) {
    // 采用中序遍历
    if(root == null) return root;
    
    invertTreeInorder(root.left);
    
    // 交换一下左右
    TreeNode tmp = root.left;
    root.left = root.right;
    root.right = tmp;
    
    invertTreeInorder(root.left);// 注意这里不是right了,
    return root;
}

后序遍历

public TreeNode invertTreePostorder(TreeNode root) {
    // 采用后序遍历
    if(root == null) return root;
    
    invertTreePostorder(root.left);// 遍历左
    invertTreePostorder(root.right);// 遍历右
    
    // 交换一下左右
    TreeNode tmp = root.left;
    root.left = root.right;
    root.right = tmp;
    return root;
}

方法二:迭代

层序遍历

public TreeNode invertTreeLevelOrder(TreeNode root) {
    // 采用层序遍历
    if(root == null) return root;
    Queue<TreeNode> queue = new LinkedList<>();
    queue.offer(root);
    
    while(!queue.isEmpty()) {
        TreeNode node = queue.poll();
        
        // 交换一下左右
        TreeNode tmp = node.left;
        node.left = node.right;
        node.right = tmp;
        
        if(node.left != null) {
            queue.offer(node.left);
        }
        if(node.right != null) {
            queue.offer(node.right);
        }
    }
    return root;
}
上一篇下一篇

猜你喜欢

热点阅读