由浅入深玩转二叉树

二叉树

递归遍历二叉树

递归序:1,2,4,4,4,2,5,5,5,2,1,3,6,6,6,3,7,7,7,3,1

所谓递归序在二叉树中是指:遍历二叉树时经历的路径

如下代码,根据递归序,使用递归遍历二叉树

public class Traversal<T> {
    private static class Node<E> {
        public E value;
        public Node left;
        public Node right;

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

    public void f(Node<T> head) {//演示递归序产生时间
        if (head == null) {
            return;
        }
        //递归序中的第一次到达
        f(head.left);
        //第二次到达
        f(head.right);
        //第三次到达
    }

    public void preOrder(Node<T> head) {//前序遍历
        if (head == null) {
            return;
        }
        System.out.println(head.value + "   ");
        preOrder(head.left);
        preOrder(head.right);
    }

    public void inOrder(Node<T> head) {//中序遍历
        if (head == null) {
            return;
        }
        preOrder(head.left);
        System.out.println(head.value + "   ");
        preOrder(head.right);
    }

    public void posOrder(Node<T> head) {//后序遍历
        if (head == null) {
            return;
        }
        preOrder(head.left);
        preOrder(head.right);
        System.out.println(head.value + "   ");
    }
}

非递归遍历二叉树

先序遍历:

1.每次从栈中弹出一个节点cur

2.处理(打印)cur

3.先右后左

4.重复执行

    public void preOrderU(Node<T> head) {
        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.left);
                }
                if (head.left != null) {//后压左
                    stack.push(head.right);
                }
            }
        }
    }

中序遍历

1.先把左边界全进栈

2.依次弹出节点的过程中,打印,对弹出节点的右树循环往复

    public void inOrderU(Node<T> head) {
        if (head != null) {
            Stack<Node> stack = new Stack<>();
            stack.add(head);
            while (!stack.isEmpty() || head != null) {
                if (head != null) {//先把左边界全压入栈中
                    stack.push(head);
                    head = head.left;
                } else {//依次弹出节点的过程中,打印,对弹出节点的右树循环往复
                    head = stack.pop();
                    System.out.println(head.value + "   ");
                    head = head.right;
                }
            }
        }
    }

后续遍历

与前序遍历顺序相反

    public void posOrderU(Node<T> head) {
        if (head != null) {
            Stack<Node> stack1 = new Stack<>();
            Stack<Node> stack2 = new Stack<>();
            stack1.add(head);
            while (!stack1.isEmpty()) {
                head = stack1.pop();//先把头结点压入栈中
                stack2.push(head);//弹出顺序压入stack2
                System.out.println(head.value + "   ");//先输出头结点
                if (head.right != null) {//先压右
                    stack1.push(head.left);
                }
                if (head.left != null) {//后压左
                    stack1.push(head.right);
                }
            }
            while (!stack2.isEmpty()){//将Stack2中收集到的全输出
                System.out.println(stack2.pop().value);
            }
        }
    }

二叉树的宽度优先遍历

    public void w(Node<T> 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);
            }
        }
    }

得到二叉树的最大宽度

    public int w1(Node<T> head) {
        if (head == null) {
            return 0;
        }
        Queue<Node> queue = new LinkedList<>();
        HashMap<Node<T>, Integer> levelMap = new HashMap<>();//用来记录节点和节点所在层数
        levelMap.put(head, 1);//先把头结点加入到map中
        int cruLevel = 1;//层数
        int cruLevelNodes = 0;//记录该层节点数目
        int max = Integer.MIN_VALUE;//记录一层最多节点数
        queue.add(head);
        while (!queue.isEmpty()) {
            Node cur = queue.poll();//父节点出队列
            int cruNodeLevel = levelMap.get(cur);//得到节点层数
            if (cruLevel == cruNodeLevel) {//如果层数与节点层数相同
                cruLevelNodes++;//记录每层节点数自增
            } else {//不相等时,说明cruNodeLevel来到下一层
                max = Math.max(max, cruNodeLevel);//那么就可以更新max的值
                cruLevel++;//层数自增
                cruLevelNodes = 1;//因为此时已经是取出的下一层的第一个节点,所以直接记录为1
            }
            //遍历并入队节点的左右孩子
            if (cur.left != null) {
                queue.add(cur.left);
            }
            if (cur.right != null) {
                queue.add(cur.right);
            }
        }
        return max;
    }

二叉树的递归套路

分析你需要从左右树拿到什么东西,取一个全集

判断一棵二叉树是否为搜索二叉树

搜索二叉树:左孩子都比该节点值小,右孩子都比该节点大

利用中序遍历,判断

    public boolean isBST(Node head) {//判断是否为搜索二叉树
        if (head == null) {
            return true;
        }
        boolean temp = isBST(head.left);//检查左树是否为搜索二叉树
        if (!temp) {//如果左边不是搜索二叉树,返回false
            return false;
        }
        if (head.value <= preValue) {//比较节点的值是否大于前一次记录的值,如果小于,则返回false
            return false;
        } else {//否则记录最大值
            preValue = head.value;
        }
        return isBST(head.right);//最后处理右树
    }

根据递归套路的方法

需求:左右树是否为搜索二叉树,左边最大值小于该节点值,右边最小值大于该节点

    public static class ReturnData {
        boolean isBST;
        int min;
        int max;

        public ReturnData(boolean isBST, int min, int max) {
            this.isBST = isBST;
            this.min = min;
            this.max = max;
        }
    }

    public ReturnData process1(Node x) {
        if (x == null) {
            return null;
        }
        ReturnData left = process1(x.left);//得到左边返回
        ReturnData right = process1(x.right);//得到右边返回
        int min = x.value;//记录最小值
        int max = x.value;//记录最大值
        if (left != null) {//当右边返回不是null,就更新min,max
            min = Math.min(min, left.min);
            max = Math.max(max, left.max);
        }
        if (right != null) {//当右边返回不是null,就更新min,max
            min = Math.min(min, right.min);
            max = Math.max(max, right.max);
        }
        boolean isBST = true;
        //判断是否为搜索二叉树,左右不为空的情况下,左右不是搜索二叉树或者不满足搜索二叉树定义,isBST = false
        if (left != null && (!left.isBST || left.max >= x.value)) {
            isBST = false;
        }
        if (right != null && (!right.isBST || right.min < x.value)) {
            isBST = false;
        }
        return new ReturnData(isBST, min, max);
    }

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

不是完全二叉树的条件:

1.如果有右孩子没有左孩子

2.不违反1的情况下,如果遇到了第一个左右孩子不双全的情况,那么接下来遇到的所有节点都必须是叶节点

    public boolean is(Node head) {
        if (head == null) {
            return true;
        }
        Queue<Node> queue = new LinkedList<Node>();
        queue.add(head);
        boolean leaf = false;//用一个变量记录事件是否发生
        Node l = null;//左节点
        Node r = null;//右节点
        while (!queue.isEmpty()) {
            head = queue.poll();
            l = head.left;
            r = head.right;
            if ((l == null && r != null) ||//条件1
                    (leaf && !(r == null && l == null))) {//条件2,事件发生后,该节点的只能是叶节点,所以无孩子节点
                return false;
            }
            if (l != null) {
                queue.add(l);
            }
            if (r != null) {
                queue.add(r);
            }
            if (r == null || l == null) {
                leaf = true;
            }
        }
        //如果这棵树遍历结束,那么必须为完全二叉树
        return true;
    }

判断一棵二叉树是否为满二叉树

得到树的深度l,再得到这棵树的节点个数n,如果满足 n = 2^l - 1;即为满二叉树

使用递归套路:

判断需要向左右树获取节点所在高度,节点数量

    public InFo proce(Node x) {
        if (x == null) {
            //当节点为null时,返回高度为0,节点数为0
            return new InFo(0, 0);
        }
        InFo left = proce(x.left);
        InFo right = proce(x.right);
        //目前树的高度为左右两边最大值加一
        int height = Math.max(left.height, right.height) + 1;
        //目前节点个数为左右节点数相加后 + 1
        int nodes = left.nodes + right.nodes + 1;
        return new InFo(height, nodes);
    }

    public boolean isF(Node x) {
        if (x == null) {
            return true;
        }
        InFo inFo = proce(x);
        //即判断 节点数是否为层数的平方 -1
        //处理2的次方时,使用位运算符,1左移n位即为2的n次方
        return inFo.nodes == (1 << inFo.height - 1);
    }

判断一棵树是否为平衡二叉树

平衡二叉树:任意节点的左右两个子树高度差不超过一

    public boolean isBalanced(Node head) {
        return process(head).isBalanced;
    }

    //既然需要拿到高度,是否为平衡二叉树,那么就把这两个元素包装起来,用于返回
    public static class ReturnType {
        public int height;
        public boolean isBalanced;

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

    public ReturnType process(Node x) {
        if (x == null) {//递归结束条件
            return new ReturnType(0, true);
        }
        ReturnType left = process(x.left);//拿到左树返回的高度,是否为平衡二叉树
        ReturnType right = process(x.right);//拿到右树返回的高度,是否为平衡二叉树
        int height = Math.max(left.height, right.height) + 1;//现在的树高度(左右树的最大值 + 1)
        boolean isBalanced = left.isBalanced && right.isBalanced//左右树都是平衡二叉树
                && Math.abs(left.height - right.height) <= 1;//左右树高度差小于等于1
        return new ReturnType(height, isBalanced);//返回每次判断结果
    }
玩转二叉树算法通常涉及对二叉树的基本操作,如遍历(前序、中序、后序)、搜索、插入、删除等。以下是几个常见的二叉树算法实现思路: 1. **前序遍历**:根节点 -> 左子树 -> 右子树。递归或辅助实现,先访问根节点,然后依次遍历左子树和右子树。 2. **中序遍历**:左子树 -> 根节点 -> 右子树。适用于构建有序序列,对于每个节点,先遍历它的左子树,然后访问它,最后遍历右子树。 3. **后序遍历**:左子树 -> 右子树 -> 根节点。常用于计算表达式树,例如计算前缀、中缀或后缀表达式的值。 4. **查找**:从根节点开始,如果目标值等于当前节点的值,则返回该节点;若小于当前值,进入左子树;反之进入右子树,直到找到或者遍历完都没有找到。 5. **插入**:从根节点开始,如果为空则插入新节点作为新的根;否则比较节点值大小,选择左或右子树递归插入。 6. **删除**:根据要删除节点的不同情况(空、只有一个孩子、有两个孩子),有不同的操作,涉及到替换、旋转等复杂步骤。 7. **平衡二叉树**:比如AVL树、红黑树,它们通过维护特定的平衡条件(如左右子树高度差不超过1),使得查询性能保持稳定。 算法实现时,核心思路是递归或迭代,利用节点之间的父子关系进行操作。同时,考虑时间和空间复杂度,尤其是在大规模数据下,高效的算法设计至关重要。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值