二叉树(二):算法题型总结

在讲解各二叉树题型之前,先创建一个类,用来表示二叉树中的节点,代码如下:

class Node {
    int value;
    Node left;
    Node right;

    public Node() {
    }

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

1、给定两个二叉树的节点node1、node2,找到它们的最低公共祖先节点

思路:

方法一: ①创建一个哈希表HashMap,遍历记录二叉树所有节点的父节点

                ②创建一个哈希表HashSet,记录node1往上所有的祖先节点

                ③node2往上依次遍历所有的祖先节点,HashSet记录的node1祖先节点第一次在node2                      遍历过程出现,那么这个节点就是它两的最低公共祖先节点

方法二:(该方法代码量极少,但难理解,需自己啃代码领会辅助理解)最低公共祖先只可能是下面两种情况 1)node1是node1、node2的最低公共祖先或者node2是node1、node2的最低公共祖先 2)node1与node2不互为最低公共祖先,node1和node2往上汇聚才找到的

方法一的代码实现如下:

  //找到两个节点node1、node2的最低公共祖先
    //方法一
    public Node findLowestAncestor1(Node head,Node node1,Node node2){
        HashMap<Node,Node> fatherMap=new HashMap<>();
        fatherMap.put(head,head);
        process3(head,fatherMap);
        HashSet<Node> node1AncestorSet=new HashSet<>();
        Node cur=node1;
        while (cur!=fatherMap.get(cur)){//从下往上回溯,只有头节点head的父节点是自己
            node1AncestorSet.add(cur);
            cur=fatherMap.get(cur);
        }
        node1AncestorSet.add(head);
        Node temp=node2;
        while (temp!=fatherMap.get(temp)){
            if (node1AncestorSet.contains(temp)){
                return temp;
            }
            temp=fatherMap.get(temp);
        }
        return head;//只有当第二个while中没有找到公共祖先,才可能运行这行代码,head一定是node1、node2的公共祖先
    }

    public void process3(Node head,HashMap<Node,Node> fatherMap){//process3这个方法作用是记录所有节点的父节点是哪个(head除外)
        if (head==null){
            return;
        }
        fatherMap.put(head.left,head);
        fatherMap.put(head.right,head);
        process3(head.left,fatherMap);
        process3(head.right,fatherMap);
    }

方法二的代码实现如下:

 //方法二
    public Node findLowestAncestor2(Node head,Node node1,Node node2){
        Node cur=head;
        if (cur==null||cur==node1||cur==node2){
            return cur;
        }
        Node left=findLowestAncestor2(cur.left,node1,node2);
        Node right=findLowestAncestor2(cur.right, node1, node2);
        if (left!=null&&right!=null){
            return cur;
        }
        return left!=null?left:right;
    }

2、获取二叉树的最大宽度。

思路:宽度优先遍历+用哈希表记录当前节点所在层,统计每层节点的个数再取最大的即可

算法代码如下:

//获取二叉树的最大宽度
    public int getMaxWidth(Node head) {
        if (head == null) {
            return 0;
        }
        Queue<Node> queue = new LinkedList<>();//这是多态的用法,这样写也是对的:LinkedList<Node> queue=new LinkedList<>();
        HashMap<Node, Integer> hashMap = new HashMap<>();
        queue.add(head);
        hashMap.put(head, 1);
        int curLevel = 1;//当前在哪一层
        int curNodeLevel;//表示当前节点属于哪一层
        int curLevelNodes = 0;//当前层节点的个数
        int max = Integer.MIN_VALUE;//统计节点数最多那层节点的个数,max=0,-1等等都行
        Node cur;//表示当前节点
        while (!queue.isEmpty()) {
            cur = queue.poll();
            curNodeLevel = hashMap.get(cur);
            if (curNodeLevel == curLevel) {
                curLevelNodes++;
            } else {
                max = Math.max(max, curLevelNodes);
                curLevel++;
                curLevelNodes = 1;
            }
            if (cur.left != null) {
                hashMap.put(cur.left, curNodeLevel + 1);
                queue.add(cur.left);
            }
            if (cur.right != null) {
                hashMap.put(cur.right, curNodeLevel + 1);
                queue.add(cur.right);
            }
        }
        max = Math.max(max, curLevelNodes);//如果不加这一行,那么最后一层的节点个数就没有作比较
        return max;
    }

3、判断二叉树是否为搜索二叉树

        搜索二叉树的特点是对于每一棵子树来说,它(子树的头节点)的左节点都比它小,它的右节点都比它大。那么不难发现,如果对二叉搜索树进行中序遍历排列,那么会得到一个从小到大的序列。

思路:用中序遍历,只要保证每一棵子树左头右节点的值是按升序的方式即可

代码实现如下:

//判断二叉树是否是搜索二叉树
    //方法一:中序递归遍历判断(还有一种递归方法就是创建一个集合,按中序遍历把每次遍历的节点依次加入集合中,如果集合中的树是按升序排列的,就是搜索二叉树)
    int preValue = Integer.MIN_VALUE;//这个变量用来表示(左头右顺序中)前一个节点的值,之所以用Integer.MIN_VALUE进行初始化,

    // 是因为中序遍历第一次打印的是整棵树最底部最左侧的那个节点,此节点没有左树和右数,所以不管这个节点的值是多少都一定符合搜索二叉树的特点
    //(这和任意值(打印的第一个节点的值)一定比系统最小值(该节点左树节点的值,其实该节点没有左树)大是一个道理)
    //也可以这样理解:中序遍历过程中,左节点可以看成是头节点的前一个值,头节点可以看成是右节点的前一个值,当前节点和前一个节点的值preValue比较后,
    // 便将当前节点的值赋值给preValue,依次传递下去
    public boolean isSBTRecur(Node head) {
        if (head == null) {
            return true;//这个初始化很重要,这里的head并不是说就只是整棵树的头节点,其实后面递归之后每个节点都有机会是head
        }
        //判断左树是否是搜索二叉树
        boolean isLeftSbt = isSBTRecur(head.left);
        if (!isLeftSbt) {
            return false;
        }

        //整个树是不是搜索二叉树(判断条件),这个判断条件放中间就是因为必须是中序遍历,判断条件才成立,先序和后序判断条件都无意义
        if (head.value <= preValue) {//意思是如果当前节点的值小于前一节点(此处的前一节点不是固定的含义,可以是左节点,也可以是头节点)的值
            return false;
        } else {
            preValue = head.value;
        }

        //判断右树是否是搜索二叉树
        boolean isRightSbt = isSBTRecur(head.right);
        return isRightSbt;
    }

    //方法二、中序非递归遍历判断,这方法比递归好理解,简单,但代码复杂,自己去写

4、判断二叉树是否是完全二叉树

思路:用宽度优先遍历,遍历的每一个节点必须同时满足以下两个条件 ①有右无左,false ②如果遇到第一个左右孩子不全的节点,则后面的节点都是叶节点(既无左也无右)

代码实现如下:

 //判断二叉树是否是完全二叉树
    public boolean isCBT(Node head) {
        if (head == null) {
            return true;
        }
        LinkedList<Node> queue = new LinkedList<>();
        boolean leaf = false;//表示是否遇到左右孩子不双全的节点
        Node left;
        Node right;//分别表示左孩子和右孩子
        Node cur;//表示当前节点
        queue.add(head);
        while (!queue.isEmpty()) {
            cur = queue.poll();
            left = cur.left;
            right = cur.right;
            if ((leaf && (left != null || right != null)) || (left == null && right != null)) {
                //leaf表示是否遇到左右孩子不双全的节点,left!=null||right!=null表示当前节点不是叶节点,left==null&&right!=null表示有右无左
                return false;
            }
            if (left != null) {
                queue.add(left);
            }
            if (right != null) {
                queue.add(right);
            }
            if (left == null || right == null) {
                leaf = true;
            }
        }
        return true;
    }

5、判断搜索二叉树是否为平衡二叉树

代码实现如下:

//判断搜索二叉树是否是平衡二叉树(此处用的方法是二叉树问题的递归套路,此处的递归和之前的递归还是有所不同的,是否二叉树的相关问题都可以用这个套路解决)
    //即树型DP(DP是动态规划的意思)的问题都可以用这个套路完成
    public boolean isBanlanced(Node head) {
        return process1(head).isBalanced;
    }

    public class ReturnType1 {
        boolean isBalanced;
        int height;

        public ReturnType1(boolean isBalanced, int height) {
            this.isBalanced = isBalanced;
            this.height = height;
        }
    }

    //注意:左树、右树、整棵树进行的操作内容都一样才能构成递归(比如此处都是返回ReturnType类型的数据)
    public ReturnType1 process1(Node x) {
        if (x == null) {
            return new ReturnType1(true, 0);
        }//这个if语句很重要,相当于就是初始条件
        ReturnType1 leftData = process1(x.left);
        ReturnType1 rightData = process1(x.right);
        int height = Math.max(leftData.height, rightData.height) + 1;
        boolean isBanlanced = leftData.isBalanced && rightData.isBalanced && Math.abs(leftData.height - rightData.height) < 2;
        return new ReturnType1(isBanlanced, height);
    }

6、判断二叉树是否是满二叉树

思路:树型DP用递归套路解决,递归信息需要告诉我们level、n两个信息,即返回类型是含有这两个成员变量的类,左树能返回,右树能返回,本身能返回,同时满足才能构成递归

代码实现如下:

//判断二叉树是否为满二叉树,这为树型DP问题,用套路解决
    public boolean isFBT(Node head) {
        return process2(head).nodes == 1 << (process2(head).height) - 1;//1左移一位就是2^1,左移两位就是2^2,……;某个数右移一位就是这个数除以2^1,……
    }

    public class ReturnType2 {
        int height;
        int nodes;

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

    public ReturnType2 process2(Node x) {
        if (x == null) {
            return new ReturnType2(0, 0);
        }
        ReturnType2 leftData = process2(x.left);
        ReturnType2 rightData = process2(x.right);
        int height = Math.max(leftData.height, rightData.height) + 1;
        int nodes = leftData.nodes + rightData.nodes + 1;
        return new ReturnType2(height, nodes);
    }

猜你喜欢

转载自blog.csdn.net/Mike_honor/article/details/125834598