数据结构与算法2-二叉树

数据结构与算法2-二叉树

1. 二叉树

  • 定义:一颗二叉树由一个根节点和左右两个子树组成
  • 结构变体:
    • 满二叉树:所有分支节点都存在左子树和右子树,而且所有的叶子节点都在同一层
    • 完全二叉树:二叉树的叶子节点只出现在最下层和次下层,而且最下层的叶节点集中在树的左部
  • 树节点定义:节点保存有指向父节点和左、右子节点的指针

 

class Node<K> {
    Node(K data) {
        this.data = data;
        this.left = null;
        this.right = null;
        this.parent = null;
    }
   Node(K data, Node<K> left, Node<K> right, Node<K> parent) {
            this.data = data;
            this.left = left;
            this. right = right;
            this.parent = parent;
        }        
    K data;
    Node<K> left;
    Node<K> right;
    Node<K> parent;
}

2. 二叉树的四种遍历方法:

  • 遍历:依次访问树的每个节点,一般规定对节点的左子树的遍历总是在右子树之前。依据父节点被访问的次序可以分为:前序遍历中序遍历后序遍历。此外按层从左至右访问层序遍历
    • 前序、中序和后序遍历的迭代版都是用作为辅助数据结构
    • 层次遍历使用队列作为辅助数据结构
  • 前序遍历:(1)先访问根节点(2)遍历左子树(3)遍历右子树
    • 具体步骤:
      • S1: 从根节点开始,将根节点压入栈中
      • S2: 更新栈顶元素作为当前节点cur = root,弹出栈顶元素
      • S3: 访问当前节点
      • S4: 当前节点的右子节点入栈
      • S5: 当前节点的左子节点入栈
      • S6: GOTO S2
public void preOrderTraversal(Consumer<E> action) {
        preOrderTraversal(root, action);
}

private void preOrderTraversal(Node<E> node, Consumer<E> action) {       
        if (Objects.isNull(node)) {
            return;
        }

        Deque<Node<E>> tube = new ArrayDeque<>();
        tube.push(node);

        while (!tube.isEmpty()) {
            node = tube.poll();
            action.accept(node.data);

            // 先让右子节点入栈
            if (!Objects.isNull(node.right)) {
                tube.push(node.right);
            }
            // 再让左子节点入栈
            if (!Objects.isNull(node.left)) {
                tube.push(node.left);
            }
        }
}
  • 中序遍历
    • 迭代版实现要点:
      • 每次遇到一个新节点都必须先将它的左子树遍历完毕才能对该节点进行访问
      • 访问完该节点之后,对节点的右子树进行遍历,继续上述步骤
    • 具体步骤:
      • S1. 从根节点开始,将根节点设置为当前节点cur = root
      • S2. 一路遍历到最左下端的节点,将它们全部压入栈中,直至全部遍历完毕
      • S3. 弹出栈顶元素,作为当前节点cur,访问该节点。当前节点的左子树必定已经遍历完毕,此时应该深入到右子树
      • S3. 深入右子树:将右子节点设置为当前节点cur = cur.right,GOTO S2
      • 循环终止条件:栈为空,且当前节点为null(栈不为空或者当前节点cur不是空指针则说明还有节点没有访问完毕)
public void inOrderTraversal(Consumer<E> action) {
        inOrderTraversal(root, action);
}

private void inOrderTraversal(Node<E> node, Consumer<E> action) {
        if (Objects.isNull(node)) {
            return;
        }
        Deque<Node<E>> tube = new ArrayDeque<>();
        Node<E> cur = node;
        while (!tube.isEmpty() || !Objects.isNull(cur)) {
            if (!Objects.isNull(cur)) {
                tube.push(cur);
                cur = cur.left;
            } else {
                cur = tube.poll();
                action.accept(cur.data);
                cur = cur.right;
            }
        }
}
  • 后序遍历
    • 迭代版实现要点:类似于中序遍历,但是略微复杂
      • 每次遇到一个新节点,必须先将节点的左子树遍历完毕,然后再将右子树遍历完毕,最后才能访问该节点
      • 访问节点需要满足下列苛刻的条件之一:(1)该节点没有右子节点;(2)该节点的右子节点刚刚被访问完毕
      • 必须增加一个变量lastVisited用于记录刚刚访问完毕的节点
      • 当节点访问完之后,返回到上一层的未访问节点继续上述步骤
    • 具体步骤:
      • S1. 从根节点开始,根节点设置为当前节点cur = root
      • S2. 一路遍历到最左下端的节点,将它们全部压入栈中,直至全部遍历完毕
      • S3. 将栈顶元素作为当前节点cur,但不访问该节点。当前节点的左子树必定已经遍历完毕。此时如果达到条件,接下来应当遍历右子树
      • S4. 判断当前节点是否满足下列条件之一(1) 当前节点没有右子节点(左子节点无需考虑,因为左子树已经遍历完毕);(2) 或者,当前节点的右子节点刚刚被访问
      • S4.1 满足条件:
      • S4.1.1. 访问满足条件的当前节点
      • S4.1.2. 从栈顶弹出元素(也就是当前节点,因为已经访问结束,所以必须从栈中删除)
      • S4.1.3. 将lastVisited更新为当前节点lastVisited = cur
      • S4.1.4. 将当前节点更新null防止下一次循环继续遍历该节点的左子树cur = null
      • S4.2. 不满足条件:
      • S4.2.1. 遍历右子树:深入右子树,将当前节点更新为右子节点cur = cur.right
      • S5. GOTO S2
      • 循环终止条件:栈为空且当前节点为null
public void postOrderTraversal(Consumer<E> action) {
        postOrderTraversal(root, action);
}

private void postOrderTraversal(Node<E> node, Consumer<E> action) {
        if (Objects.isNull(node)) {
            return;
        }

        Deque<Node<E>> tube = new ArrayDeque<>();
        Node<E> cur = node;
        Node<E> lastVisited = null;
        while (!tube.isEmpty() || !Objects.isNull(cur)) {
            if (!Objects.isNull(cur)) {
                tube.push(cur);
                cur = cur.left;
            } else {
                cur = tube.peek();
                if (Objects.isNull(cur.right) || lastVisited == cur.right) {
                    action.accept(cur.data);
                    lastVisited = cur;
                    tube.poll();
                    // 此处十分关键,否则会导致严重错误!
                    cur = null;
                } else {
                    cur = cur.right;
                }
            }
        }
}

 

  • 层次遍历
    • 实现要点:使用队列作为辅助结构
public void levelOrderTraversal(Consumer<E> action) {
        levelOrderTraversal(root, action);
    }
    
private void levelOrderTraversal(Node<E> node, Consumer<E> action) {
        if (Objects.isNull(node)) {
            return;
        }
        /*
        层次遍历使用队列作为辅助结构
        1. 访问父节点
        2. 依次将左子节点和右子节点压入队列(如果有的话)
        3. 从队列中提取元素并访问
        4. GOTO 1
        循环终止条件:队列为空
         */
        Deque<Node<E>> tube = new ArrayDeque<>();
        tube.offer(node);
        while (!tube.isEmpty()) {
            Node<E> cur = tube.peek();
            if (!Objects.isNull(cur.left)) {
                tube.offer(cur.left);
            }
            if (!Objects.isNull(cur.right)) {
                tube.offer(cur.right);
            }
            action.accept(cur.data);
            tube.poll();
        }
}

3. 树的高度

  • 实现要点:
    • 树的高度等于层次遍历时的总层数
    • 设置变量currentLevelSize记录当前层的节点数目,显然第一层的节点数量为1
    • 每次访问完一层的节点后:
      • 队列的长度等于即将要访问的下一层的节点数目
      • 高度加1
public int getHeight() {
        return getHeight(root);

    }
private int getHeight(Node<E> startNode) {
        if (Objects.isNull(startNode)) {
            return 0;
        }
        /*
        height = max(LeftHeight, RightHeight) + 1
        使用层次遍历的思路,层数即树高
        实现要点:确保队列的长度刚好等于该层的节点数量
         */

        Deque<Node<E>> tube = new ArrayDeque<>();
        tube.offer(startNode);
        int height = 0;

        while (!tube.isEmpty()) {
            ++height;
            int currentLevelSize = tube.size();
            for (int count = 1; count <= currentLevelSize; ++count) {
                Node<E> cur = tube.poll();
                if (!Objects.isNull(cur.left)) {
                    tube.offer(cur.left);
                }
                if (!Objects.isNull(cur.right)) {
                    tube.offer(cur.right);
                }
            }
        }
        return height;
}

 

posted @ 2018-10-11 20:29  NaiveCoder  阅读(221)  评论(0)    收藏  举报