常用算法的总结——二叉树

1 先序、中序、后序

1.1 递归

递归方式实现二叉树的先序、中序、后序遍历

1)理解递归序

2)先序、中序、后序都可以在递归序的基础上加工出来

3)第一次到达一个节点就打印就是先序、第二次打印即中序、第三次即后序

1.2 非递归

利用栈去解决:

  1.2.1 先序:1)先压入头结点到栈里;

      2)弹出一个就打印;

      3)弹出的有右就压入右结点到栈;

      4)弹出的有左就压入左结点到栈;

  1.2.2 头->右->左(后序的逆序):只需要把1.2.1的2)和3)调换顺序即可;

  1.2.3 后序遍历:在1.2.2里面,弹出的时候不打印,而是把值放到另外一个栈(栈2)里面去,等待栈1全部完事之后,再从这个栈2里面一个一个弹出并打印即可;

  1.2.4 中序遍历:1)先压入头结点到栈里;

          2)头结点的左结点不为空则压入该左结点;

          3)再看左结点的左结点是否为空,为空则压入;

          。。。

          4)等到左结点全部压入,则弹出最上面的结点,并打印该弹出的结点;

          5)将当前结点的右节点赋值给当前结点,继续循环

        代码如下:

    public static void in(Node head) {
        System.out.print("in-order: ");
        if (head != null) {
            Stack<Node> stack = new Stack<Node>();
            while (!stack.isEmpty() | head != null) {
                if (head != null) {
                    stack.push(head);
                    head = head.left;
                } else {
                    head = stack.pop();
                    System.out.print(head.value + " ");
                    head = head.right;
                }
            }
            System.out.println();
        }
    }

2 实现二叉树的按层遍历

1)其实就是宽度优先遍历,用队列即可;

[进阶] 返回二叉树的最大宽度,用队列解决,可以通过设置flag变量的方式,来发现某一层的结束

  -用Map时,代码如下:

    public static int maxWidthUseMap(Node head) {
        if (head == null) {
            return 0;
        }
        Queue<Node> queue = new LinkedList<>();
        queue.add(head);
        // key在哪一层,就是那个value
        HashMap<Node, Integer> levelMap = new HashMap<>();
        levelMap.put(head, 1);
        int curLevel = 1; //当前你正在统计哪一层的宽度,入队列就看
        int curLevelNodes = 0; //当前层curLeve1层,宽度目前是多少,出队列时再看的
        int max = 0;//宽度的最大值
        while (!queue.isEmpty()) {
            Node cur = queue.poll();
            int curNodeLevel = levelMap.get(cur);
            if (cur.left != null) {
                //将左叶子结点放进并设置层数为:在当前层+1
                levelMap.put(cur.left, curNodeLevel + 1);
                queue.add(cur.left);
            }
            if (cur.right != null) {
                levelMap.put(cur.right, curNodeLevel + 1);
                queue.add(cur.right);
            }

            //判断当前出队列的结点是否还在这一层
            //是,则说明当前结点还没结束,当前层结点数目+1
            if (curNodeLevel == curLevel) {
                curLevelNodes++;
            } else {//否,则说明当前层已经完事了,当前出队列的结点已经是下一层的结点了
                max = Math.max(max, curLevelNodes);
                //当前层数+1
                curLevel++;
                //需要重置当前层的结点数目
                curLevelNodes = 1;
            }
        }
        max = Math.max(max, curLevelNodes);
        return max;
    }

  -不用Map,用有限几个变量的方法,代码如下:

public static int maxWidthNoMap(Node head) {
        if (head == null) {
            return 0;
        }
        Queue<Node> queue = new LinkedList<>();
        queue.add(head);
        Node curEnd = head; //当前层,最右节点是谁
        Node nextEnd = null; // 下一一层,最右节点是谁
        int max = 0;
        int curLevelNodes = 0; //当前层的节点数
        while (!queue.isEmpty()) {
            Node cur = queue.po11();
            if (cur.left != null) {
                queue.add(cur.left);
                nextEnd = cur.left;
            }
            if (cur.right != null) {
                queue.add(cur.right);
                nextEnd = cur.right;
            }
            curLevelNodes++;
            if (cur == curEnd) {
                max = Math.max(max, curLevelNodes);
                curLevelNodes = 0;
                curEnd = nextEnd;
            }
        }
        return max;
    }

3 序列化、反序列化二叉树

3.1 按照先序遍历的方式

中序、后序遍历只需要调整顺序即可。

    public static Queue<String> preSerial(Node head) {
        Queue<String> ans = new LinkedList<>();
        pres(head, ans);
        return ans;
    }

    public 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);
        }
    }

反序列化,代码如下:

    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;
    }

 

3.2 按层遍历序列化二叉树

public static Queue<String> levelSerial(Node head) {
        Queue<String> ans = new LinkedList<>();
        if (head == null) {
            ans.add(null);
        } else {
            ans.add(String.valueOf(head.value));
            Queue<Node> queue = new LinkedList<Node>();
            queue.add(head);
            while (!queue.isEmpty()) {
                head = queue.poll();
                if (head.left != null) {
                    ans.add(String.valueOf(head.left.value));
                    queue.add(head.left);
                } else {
                    ans.add(null);
                }
                if (head.right != null) {
                    ans.add(String.valueOf(head.right.value));
                    queue.add(head.right);
                } else {
                    ans.add(null);
                }
            }
        }
        return ans;
    }

反序列化:

    public static Node buildByLeve1Queue(Queue<String> levelList) {
        if (levelList == null || levelList.size() == 0) {
            return null;
        }
        Node head = generateNode(levelList.poll());
        Queue<Node> queue = new LinkedList<Node>();
        if (head != null) {
            queue.add(head);
        }
        Node node = null;
        while (!queue.isEmpty()) {
            node = queue.poll();
            node.left = generateNode(levelList.poll());
            node.right = generateNode(levelList.poll());
            if (node.left != null) {
                queue.add(node.left);
            }
            if (node.right != null) {
                queue.add(node.right);
            }
        }
        return head;
    }
    //获取结点
    public static Node generateNode(String val) {
        if (val == null) {
            return null;
        }
        return new Node(Integer.valueOf(val));
    }

 4 后继结点的获取

给出一个结点,返回其后继结点(中序遍历时的下一个结点),这里的结点有左结点、右结点、父结点,代码如下:

public static Node getSuccessorNode(Node node) {
        if (node == null) {
            return node;
        }
        //右子树不为空,说明后继结点为右子树的最左子结点
        if (node.right != null) {
            return getLeftMost(node.right);
        } else { //无右子树,需要一直往上找
            Node parent = node.parent;
            //当前结点是其父结点的右子结点,继续循环,直到找到当前结点是父结点的左子结点
            while (parent != null && parent.left != node) {//
                node = parent;
                parent = node.parent;
            }
            return parent;//何时返回空?给的node为最右边的子结点
        }
    }

    //获取最左的结点
    public static Node getLeftMost(Node node) {
        if (node == null) {
            return node;
        }
        while (node.left != null) {
            node = node.left;
        }
        return node;
    }

前驱结点同理。

 

5 二叉树的递归套路

1) 假设以X节点为头,假设可以向X左树和X右树要任何信息;

2) 在上一步的假设下,讨论以X为头节点的树,得到答案的可能性(最重要):

  常见分类是:和X无关的答案、和X有关的答案;

3) 列出所有可能性后,确定到底需要向左树和右树要什么样的信息;

4) 把左树信息和右树信息求全集,就是任何一棵子树都需要返回的信息S;

5)递归函数都返回S,每一棵子树都这么要求;

6)写代码,在代码中考虑如何把左树的信息和右树信息整合出整棵树的信息。

 

例题1:判断二叉树的平衡性

 //主函数
    public static boolean isBalanced2(Node head) {
        return process2(head).isBalaced;
    }
    //左、右要求一样,Info信息返回的结构体
    public static class Info {
        public boolean isBalaced;
        public int height;
        public Info(boolean b, int h) {
            isBalaced = b;
            height = h;
        }
    }
    public static Info process2(Node X) {
        if (X == null) {
            return new Info(true, 0);
        }
        Info leftInfo = process2(X.left);
        Info rightInfo = process2(X.right);
        int height = Math.max(leftInfo. height, rightInfo . height) + 1;
        boolean isBalanced = true;
        if (!leftInfo.isBalaced || !rightInfo.isBalaced|| Math.abs(leftInfo.height - rightInfo.height) > 1) {
            isBalanced = false;
        }
        return new Info(isBalanced, height);
    }

例二:获取二叉树的最大距离

给定一棵二叉树的头节点head,任何两个节点之间都存在距离,返回整棵二叉树的最大距离

//主函数
    public static int maxDistance2(Node head) {
        return process(head).maxDistance;
    }
        //需要你返回的信息体
    public static class Info {
        public int maxDistance;
        public int height;
        public Info(int dis, int h) {
            maxDistance = dis;
            height = h;
        }
    }

    /**
     * 得到答案的可能性:①最大距离包含头结点,则最大距离=左子结点的高度+右子结点的高度+1;
     *                ②不包含头结点,则最大距离=Max(左子结点的最大距离, 右子结点的最大距离)。
     *         总结:最大距离=Max(左子结点的最大距离, 右子结点的最大距离, 左子结点的高度+右子结点的高度+1)
     */
    public static Info process(Node X) {
        if (X == null) {
            return new Info(0, 0);
        }
        Info leftInfo = process(X.left);
        Info rightInfo = process(X.right);
        //获取当前结点的高度
        int height = Math.max(leftInfo.height, rightInfo.height) + 1;
        int maxDistance = Math.max(
                Math.max(leftInfo.maxDistance, rightInfo.maxDistance),
                leftInfo.height + rightInfo.height + 1);
        return new Info(maxDistance, height);
    }

例三:给定一棵二叉树的头节点head,返回这颗二叉树中最大的二叉搜索子树的头节点

    public static int maxSubBSTSize2(Node head) {
        if (head == null) {
            return 0;
        }
        return process(head).maxSubBSTSize;
    }

    /**
     * 任何子树
     * isAllBST 是否是二叉搜索树
     * maxSubBSTSize 树的size
     * min、max 树的最小值和最大值
     */
    public static class Info {
        public boolean isAllBST;
        public int maxSubBSTSize;
        public int min;
        public int max;

        public Info(boolean is, int size, int mi, int ma) {
            isAllBST = is;
            maxSubBSTSize = size;
            min = mi;
            max = ma;
        }
    }

    public static Info process(Node X) {
        if (X == null) {
            return null;
        }
        Info leftInfo = process(X.left);
        Info rightInfo = process(X.right);
        int min = X.value;
        int max = X.value;
        //如何获取Info里面的四个信息,先尝试获取max和min
        if (leftInfo != null) {
            min = Math.min(min, leftInfo.min);
            max = Math.max(max, leftInfo.max);
        }
        if (rightInfo != null) {
            min = Math.min(min, rightInfo.min);
            max = Math.max(max, rightInfo.max);
        }

        //再来获取子树的最大搜索树的size
        //这部分可以理解为:先考虑和当前结点无关的情况,即最大搜索树出现在子树当中
        int maxSubBSTSize = 0;
        if (leftInfo != null) {
            maxSubBSTSize = leftInfo.maxSubBSTSize;
        }
        if (rightInfo != null) {
            maxSubBSTSize = Math.max(maxSubBSTSize, rightInfo.maxSubBSTSize);
        }

        boolean isAllBST = false;
        //处理当前结点
        if (
            //左树整体需要是搜索二叉树、右树整体需要是搜索二叉树
            (leftInfo == null ? true : leftInfo.isAllBST)
            &&(rightInfo == null ? true : rightInfo.isAllBST)
            //左树最大值<X、右树最小值>X
            &&(leftInfo == null ? true : leftInfo.max < X.value)
            &&(rightInfo == null ? true : rightInfo.min > X.value)
        ) {
            maxSubBSTSize =
                    (leftInfo == null ? 0 : leftInfo.maxSubBSTSize)
                    +
                    (rightInfo == null ? 0 : rightInfo.maxSubBSTSize)
                    +
                    1;
            isAllBST = true;
        }
        //不管三七二十一,先把这个返回值先写上去
        return new Info(isAllBST, maxSubBSTSize, min, max);
    }

 例四:给定一棵二叉树的头节点head,和另外两个节点a和b。返回a和b的最低公共祖先

普通方法:先用一个Map把所有结点都放进去,key为当前结点,value为当前结点的父结点(Map<Node, Node.parent>),完事之后创建一个Set,从a结点开始往上遍历(cur = parentMap.get(cur)),遍历一个存放一个进Set,完事之后又从b结点开始往上遍历,每次遍历查看是否已经存在在Set中,是的话则返回,否则继续

利用二叉树的递归套路亦可解决

 

 

请把一段纸条竖着放在桌子上,然后从纸条的下边向.上方对折1次,压出折痕后展开。此时折痕是凹下去的,即折痕突起的方向指向纸条的背面。如果从纸条的下边向上方连续对折2次,压出折痕后展开,此时有三条折痕,从上到下依次是下折痕、下 折痕和.上折痕。
给定一个输入参数N,代表纸条都从下边向上方连续对折N次。请从上到下打印所有折痕的方向。
例如:N=1时,打印: down N=2时,打印: down down up

 

posted @ 2021-01-02 16:55  Mistolte  阅读(140)  评论(0编辑  收藏  举报