Skip to content

Latest commit

 

History

History
572 lines (451 loc) · 14 KB

26-算法面试专题-二叉树.md

File metadata and controls

572 lines (451 loc) · 14 KB
  • 基础
package com.xiaodai.algorithm;

import java.util.LinkedList;
import java.util.Queue;
import java.util.Stack;

/**
 * Author :dai
 * Date :2020/12/25 2:56 下午
 * Description:二叉树基本结构和算法
 */
public class TreeBaseUtil {

    /**
     * 二叉树节点定义
     */
    public static class Node {
        public int value;
        public Node left;
        public Node right;

        public Node(int v) {
            this.value = v;
        }
    }


    /**
     * 递归先序遍历
     *
     * @param head
     */
    public static void pre(Node head) {

        if (head == null) {
            return;
        }

        System.out.println(head.value);

        pre(head.left);

        pre(head.right);

    }

    /**
     * 递归中序遍历
     *
     * @param head
     */
    public static void mid(Node head) {
        if (head == null) {
            return;
        }

        mid(head.left);

        System.out.println(head.value);

        mid(head.right);
    }

    /**
     * 递归后续遍历
     *
     * @param head
     */
    public static void pos(Node head) {
        if (head == null) {
            return;
        }

        pos(head.left);

        pos(head.right);

        System.out.println(head.value);
    }

    /**
     * 非递归先序
     *
     * @param head
     */
    public static void NotRRre(Node head) {

        System.out.print("pre-order: ");

        if (head != null) {
            // 借助栈结构,手动压栈
            Stack<Node> stack = new Stack<>();
            stack.add(head);

            while (!stack.isEmpty()) {
                // 弹出就打印
                head = stack.pop();
                System.out.println(head.value);

                // 右孩子不为空,先压入右孩子。右孩子就会后弹出
                if (head.right != null) {
                    stack.push(head.right);
                }

                // 左孩子不为空,压入左孩子,左孩子在右孩子之后压栈,先弹出
                if (head.left != null) {
                    stack.push(head.left);
                }
            }

        }

    }

    /**
     * 非递归中序
     *
     * @param head
     */
    public static void NotRMid(Node head) {

        System.out.print("mid-order: ");

        if (head != null) {
            Stack<Node> stack = new Stack<>();
            while (!stack.isEmpty() || head != null) {

                // 整条左边界以此入栈
                if (head != null) {
                    stack.push(head);
                    // head滑到自己的左孩子,左孩子有可能为空,但空的节点不会加入栈,下一个分支会判空处理
                    head = head.left;
                    // 左边界到头弹出一个打印,来到该节点右节点,再把该节点的左树以此进栈
                } else {// head为空的情况,栈顶是上次头结点的现场,head等于栈顶,回到上一个现场。打印后,head往右树上滑动
                    head = stack.pop();
                    System.out.println(head.value);
                    head = head.right;
                }
            }
        }

    }


    /**
     * 非递归后序,借助两个栈,比借助一个栈容易理解
     *
     * @param head
     */
    public static void NotRPos(Node head) {
        System.out.print("pos-order: ");

        if (head != null) {
            Stack<Node> s1 = new Stack<>();
            // 辅助栈
            Stack<Node> s2 = new Stack<>();
            s1.push(head);
            while (!s1.isEmpty()) {
                head = s1.pop();
                s2.push(head);
                if (head.left != null) {
                    s1.push(head.left);
                }
                if (head.right != null) {
                    s1.push(head.right);
                }
            }
            while (!s2.isEmpty()) {
                System.out.print(s2.pop().value + " ");
            }
        }
        System.out.println();
    }


    /**
     * 非递归后序,仅借助一个栈,比较有技巧
     *
     * @param head
     */
    public static void NotRPos2(Node head) {
        System.out.print("pos-order: ");
        if (head != null) {
            Stack<Node> stack = new Stack<>();
            stack.push(head);
            Node c = null;
            while (!stack.isEmpty()) {
                c = stack.peek();
                if (c.left != null && head != c.left && head != c.right) {
                    stack.push(c.left);
                } else if (c.right != null && head != c.right) {
                    stack.push(c.right);
                } else {
                    System.out.print(stack.pop().value + " ");
                    head = c;
                }
            }
        }
        System.out.println();
    }


    /**
     * 按层遍历,即宽度优先遍历
     *
     * @param head
     */
    public static void level(Node head) {

        if (head == null) {
            return;
        }

        // 借助队列
        Queue<Node> queue = new LinkedList<>();

        queue.add(head);

        while (!queue.isEmpty()) {

            Node cur = queue.poll();
            // 打印当前
            System.out.println(cur.value);

            // 当前节点有左孩子,加入左孩子进队列
            if (cur.left != null) {
                queue.add(cur.left);
            }

            // 当前节点有右孩子,加入右孩子进队列
            if (cur.right != null) {
                queue.add(cur.right);
            }
        }

    }


    /**
     * 二叉树先序序列化;除了先序,中序,后续,按层都可,但是序列化和反序列化规则要对应
     * @param head
     * @return
     */
    public static Queue<String> preSerial(Node head) {
        Queue<String> ans = new LinkedList<>();

        pres(head, ans);

        return ans;

    }

    private static void pres(Node head, Queue<String> ans) {

        if (head == null) {
            ans.add(null);
        } else {
            ans.add(String.valueOf(head.value));
            pres(head.left, ans);
            pres(head.right, ans);
        }
    }


    /**
     * 根据先序序列化的结构,还原树
     *
     * @param prelist
     * @return
     */
    public static Node buildByPreQueue(Queue<String> prelist) {

        if (prelist == null || prelist.size() == 0) {
            return null;
        }
        return preb(prelist);
    }

    public static Node preb(Queue<String> prelist) {
        String value = prelist.poll();
        // 如果头节点是空的话,返回空
        if (value == null) {
            return null;
        }
        // 否则根据第一个值构建先序的头结点
        Node head = new Node(Integer.valueOf(value));
        // 递归建立左树
        head.left = preb(prelist);
        // 递归建立右树
        head.right = preb(prelist);
        return head;
    }
}
  • 二叉树相关练习
package com.xiaodai.algorithm;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;

/**
 * Author :dai
 * Date :2020/12/30 2:56 下午
 * Description:解决二叉树的具体问题,递归思维的建立
 */
public class TreeSolvingUtil {

    /**
     * 节点信息
     */
    public static class Node {
        public int value;
        public Node left;
        public Node right;

        public Node(int data) {
            this.value = data;
        }
    }


    /**
     * 1、判断二叉树是否是平衡的
     *
     * @param head
     * @return
     */
    public static boolean isBalanced(Node head) {

        return isBalancedProcess(head).isBalanced;

    }

    /**
     * 1. 递归调用,head传入整体需要返回一个信息
     * 2. 解决当前节点的Info信息怎么得来
     *
     * @param head
     * @return
     */
    private static IsBalancedInfo isBalancedProcess(Node head) {

        if (head == null) {
            return new IsBalancedInfo(true, 0);
        }

        IsBalancedInfo leftInfo = isBalancedProcess(head.left);

        IsBalancedInfo rightInfo = isBalancedProcess(head.right);

        // 当前节点的高度,等于左右树最大的高度,加上当前节点高度1
        int currentHeight = Math.max(leftInfo.height, rightInfo.height) + 1;

        boolean isBalanced = true;

        // 左树不平衡,或者右树不平衡,或者左右树高度差大于1
        if (!leftInfo.isBalanced || !rightInfo.isBalanced || Math.abs(leftInfo.height - rightInfo.height) > 1) {
            isBalanced = false;
        }

        return new IsBalancedInfo(isBalanced, currentHeight);
    }

    /**
     * 递归过程中需要整合的信息体
     */
    public static class IsBalancedInfo {

        // 是否平衡
        boolean isBalanced;

        // 高度多少
        int height;

        IsBalancedInfo(boolean b, int height) {
            this.isBalanced = b;
            this.height = height;
        }
    }


    /**
     * 2、二叉树中,获取任意两个节点的最大距离
     *
     * @param head
     * @return
     */
    public static int maxDistance(Node head) {

        return maxDistanceProcess(head).maxDistance;

    }

    /**
     * 任意节点需要返回的信息体:以该节点为头的高度,整棵树的最大距离
     */
    public static class MaxDistanceInfo {
        public int maxDistance;
        public int height;

        public MaxDistanceInfo(int dis, int h) {
            this.maxDistance = dis;
            this.height = h;
        }
    }

    private static MaxDistanceInfo maxDistanceProcess(Node head) {

        if (head == null) {
            return new MaxDistanceInfo(0, 0);
        }

        MaxDistanceInfo leftInfo = maxDistanceProcess(head.left);

        MaxDistanceInfo rightInfo = maxDistanceProcess(head.right);

        // 当前节点为头的情况下,高度等于左右树较大的高度,加上1
        int height = Math.max(leftInfo.height, rightInfo.height) + 1;

        // 当前节点为头的情况下,最大距离等于,左右树距离较大的那个距离(与当前节点无关的情况)
        // 和左右树高度相加再加上当前节点距离1的距离(与当前节点有关的情况)取这两种情况较大的那个
        int maxDistance = Math.max(Math.max(leftInfo.maxDistance, rightInfo.maxDistance)
                , (leftInfo.height + rightInfo.height + 1));

        return new MaxDistanceInfo(maxDistance, height);
    }


    /**
     * 3、判断一颗树是否是满二叉树
     *
     * @param head
     * @return
     */
    public static boolean isFull(Node head) {

        if (head == null) {
            return true;
        }

        IsFullInfo all = isFullProcess(head);

        // 递归拿到整棵树的头结点个数,根据满二叉树的公式,验证。(高度乘以2) - 1 = 节点个数
        return (1 << all.height) - 1 == all.nodes;
    }


    /**
     * 判断一棵树是否是满二叉树,每个节点需要返回的信息
     */
    public static class IsFullInfo {
        public int height;

        public int nodes;

        public IsFullInfo(int height, int nodes) {
            this.height = height;
            this.nodes = nodes;
        }
    }

    private static IsFullInfo isFullProcess(Node head) {

        // base 空节点的高度为0,节点数量也0
        if(head == null) {
            return new IsFullInfo(0,0);
        }

        // 左树信息
        IsFullInfo leftInfo = isFullProcess(head.left);

        // 右树信息
        IsFullInfo rightInfo = isFullProcess(head.right);

        // 当前节点为头的树,高度
        int height = Math.max(leftInfo.height, rightInfo.height) + 1;

        // 当前节点为头的树,节点数量
        int nodes = leftInfo.nodes + rightInfo.nodes + 1;

        return new IsFullInfo(height, nodes);

    }

    /**
     * 4、找到二叉树中节点和等于sum的最长路径
     * @param head
     * @param sum
     * @return
     */
    public int getMaxLength(Node head, int sum) {
        Map<Integer, Integer> sumMap = new HashMap<>();
        sumMap.put(0, 0); // 重要
        return preOrder(head, sum, 0, 1, 0, sumMap);
    }

    private int preOrder(Node head, int sum, int preSum, int level, int maxLen, Map<Integer, Integer> sumMap) {
        if(head == null) {
            return maxLen;
        }

        int curSum = preSum + head.value;
        if(!sumMap.containsKey(curSum)) {
            sumMap.put(curSum, level);
        }

        if(sumMap.containsKey(curSum - sum)) {
            maxLen = Math.max(level - sumMap.get(curSum - sum), maxLen);
        }
        maxLen = preOrder(head.left, sum, curSum, level + 1, maxLen, sumMap);
        maxLen = preOrder(head.right, sum, curSum, level + 1, maxLen, sumMap);

        if(level == sumMap.get(curSum)) {
            sumMap.remove(curSum);
        }

        return maxLen;
    }

    /**
     * 5、二叉树按层打印
     *
     *  last:表示正在打印的当前行的最右节点
     *  nLast:表示下一行的最右节点
     */
    public void printByLevel(Node head) {
        if(head == null) {
            return;
        }

        Queue<Node> queue = new LinkedList<>();
        int level = 1;
        Node last = head;
        Node nLast = null;
        queue.offer(head);
        System.out.println("Level " + (level++) + " : ");
        while (!queue.isEmpty()) {
            head = queue.poll();
            System.out.println(head.value + " ");
            if(head.left != null) {
                queue.offer(head.left);
                nLast = head.left;
            }
            if(head.right != null) {
                queue.offer(head.right);
                nLast = head.right;
            }
            if(head == last && !queue.isEmpty()) {
                System.out.println("\nLevel " + (level++) + " ");
                last = nLast;
            }
        }
        System.out.println();
    }

    /**
     * 6、二叉树zigzag打印
     */

    /**
     * 7、二叉树,给定量给节点,求这两个节点的最近公共祖先
     */
}