Java(二叉树)

二叉树的结构

节点的构成,通过节点的连接构成二叉树

class Node {
    public int val;
    public Node left;
    public Node right;

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

遍历二叉树

遍历二叉树(递归)

先序遍历

先序遍历即为根节点先处理,再左结点,再右节点。

// 先序遍历
public static void preorderTraversal(Node head) {
    // 退出递归的条件
    if (head == null) {
        return;
    }
    System.out.print(head.val + " ");
    preorderTraversal(head.left);
    preorderTraversal(head.right);
}
中序遍历

中序遍历即为左结点先处理,再根节点,再右节点。

// 中序遍历
public static void middleOrderTraversal(Node head) {
    // 退出递归的条件
    if (head == null) {
        return;
    }
    middleOrderTraversal(head.left);
    System.out.print(head.val + " ");
    middleOrderTraversal(head.right);
}
后序遍历

后序遍历即为左结点先处理,再右节点,再根节点。

// 后序遍历
public static void postorderTraversal(Node head) {
    // 退出递归的条件
    if (head == null) {
        return;
    }
    postorderTraversal(head.left);
    postorderTraversal(head.right);
    System.out.print(head.val + " ");
}

遍历二叉树(非递归)

先序遍历

思路

1、准备一个栈

2、将头节点压入栈中

3、栈不为空进行出栈的操作,打印处理

4、有右子节点进行入栈,有左子节点进行入栈

5、重复3、4操作 直到栈空为止

// 先序遍历 (非递归)
public static void preorderTraversalNoRec(Node head) {
    if (head == null) {
        return;
    }
    // 准备一个栈
    Stack<Node> stack = new Stack<>();
    // 将头节点入栈
    stack.push(head);
    // 进行循环入栈
    while (!stack.isEmpty()) {
        // 取出栈顶元素
        head = stack.pop();
        // 进行处理
        System.out.print(head.val + " ");
        // 先入右节点
        if (head.right != null) {
            stack.push(head.right);
        }
        // 左结点
        if (head.left != null) {
            stack.push(head.left);
        }
    }
}
中序遍历

思路

1、准备一个栈

2、将子树的左子节点依次压入栈中(一路左到空)

3、依次弹出元素,处理弹出的元素

4、将弹出元素的右子节点的左子节点依次压入栈中(一路左到空)

5、循环处理直到栈为空 head走到null

// 中序遍历 (非递归)
public static void middleOrderTraversalNoRec(Node head) {
    if (head == null) {
        return;
    }
    // 准备一个栈
    Stack<Node> stack = new Stack<>();
    // 将左子节点全部压入栈中
    while (!stack.isEmpty() || head != null) {
        if (head != null) {
            // 压入栈中
            stack.push(head);
            // 向左走
            head = head.left;
        } else {
            // 左边走完了 进行出栈和打印
            head = stack.pop();
            System.out.print(head.val + " ");
            // 将右子节点赋值给head 重复上面的操作
            head = head.right;
        }
    }
}
后序遍历

思路

1、准备两个栈 s1 s2

2、将头节点压入s1

3、弹出s1中节点到s2中

4、将左子节点和右子节点依次压入s1中

5、重复3、4操作直到s1为空

6、依次出栈s2就是后序遍历

// 后序遍历 (非递归)
public static void postorderTraversalNoRec(Node head) {
    if (head == null) {
        return;
    }
    // 准备两个栈 s1 s2
    Stack<Node> s1 = new Stack<>();
    Stack<Node> s2 = new Stack<>();
    // 将头节点压入s1中
    s1.push(head);
    // 进行循环判断s1是否为空
    while (!s1.isEmpty()) {
        // 出s1栈 入s2栈
        head = s1.pop();
        s2.push(head);
        // 依次压入s1 左子节点 和 右子节点
        if (head.left != null) {
            s1.push(head.left);
        }
        if (head.right != null) {
            s1.push(head.right);
        }
    }
    // 依次出s2的栈
    while (!s2.isEmpty()) {
        System.out.print(s2.pop().val + " ");
    }
}

Morris遍历

在这里插入图片描述

public static void morris(Node head) {
    if (head == null) {
        return;
    }
    Node cur = head;
    Node mostRight;
    while (cur != null) {
        // 获取当前节点的左节点
        mostRight = cur.left;
        // 有左子节点
        if (mostRight != null) {
            // 找到左子树的最右的节点  mostRight.right != cur 防止修改后的值
            while (mostRight.right != null && mostRight.right != cur) {
                mostRight = mostRight.right;
            }
            if (mostRight.right == null) {
                // 指向cur
                mostRight.right = cur;
                // cur向左移动
                cur = cur.left;
                continue;
            } else {
                // 指向的是cur当前对象  说明左边遍历完毕
                mostRight.right = null;
                cur = cur.right;
            }
        }
        // 没有左子节点向右移动
        cur = cur.right;
    }
}
先序遍历

思路

在morris中只出现一次的直接处理,出现两次第一次才进行处理,第二次不进行处理

public static void preMorris(Node head) {
    if (head == null) {
        return;
    }
    Node cur = head;
    Node mostRight;
    while (cur != null) {
        // 获取当前节点的左节点
        mostRight = cur.left;
        // 有左子节点
        if (mostRight != null) {
            // 找到左子树的最右的节点  mostRight.right != cur 防止修改后的值
            while (mostRight.right != null && mostRight.right != cur) {
                mostRight = mostRight.right;
            }
            // 第一次来到
            if (mostRight.right == null) {
                System.out.println(cur.val);
                // 指向cur
                mostRight.right = cur;
                // cur向左移动
                cur = cur.left;
                continue;
            } else {
                // 指向的是cur当前对象  说明左边遍历完毕
                mostRight.right = null;
                cur = cur.right;
            }
        } else {
            // mostRight != null 没有左结点的会直接打印(只会打印一次)
            System.out.println(cur.val);
        }
        // 没有左子节点向右移动
        cur = cur.right;
    }
}
中序遍历

思路

在morris中只出现一次的直接 ,出现两次第二次才进行处理,第一次不进行处理

public static void middleMorris(Node head) {
    if (head == null) {
        return;
    }
    Node cur = head;
    Node mostRight;
    while (cur != null) {
        // 获取当前节点的左节点
        mostRight = cur.left;
        // 有左子节点
        if (mostRight != null) {
            // 找到左子树的最右的节点  mostRight.right != cur 防止修改后的值
            while (mostRight.right != null && mostRight.right != cur) {
                mostRight = mostRight.right;
            }
            // 第一次来到
            if (mostRight.right == null) {
                // 指向cur
                mostRight.right = cur;
                // cur向左移动
                cur = cur.left;
                continue;
            } else {
                // 指向的是cur当前对象  说明左边遍历完毕
                mostRight.right = null;
            }
        }
        // 处理
        System.out.println(cur.val);
        // 没有左子节点向右移动
        cur = cur.right;
    }
}
后序遍历

思路

在morris中只出现一次的不处理 ,出现两次第二次才进行处理,将这个节点的左子节点的右边界进行逆序处理,最后将这个树的右边界进行逆序处理

public static void postMorris(Node head) {
    if (head == null) {
        return;
    }
    Node cur = head;
    Node mostRight;
    while (cur != null) {
        // 获取当前节点的左节点
        mostRight = cur.left;
        // 有左子节点
        if (mostRight != null) {
            // 找到左子树的最右的节点  mostRight.right != cur 防止修改后的值
            while (mostRight.right != null && mostRight.right != cur) {
                mostRight = mostRight.right;
            }
            // 第一次来到
            if (mostRight.right == null) {
                // 指向cur
                mostRight.right = cur;
                // cur向左移动
                cur = cur.left;
                continue;
            } else {
                // 指向的是cur当前对象  说明左边遍历完毕
                mostRight.right = null;
                // 这里进行处理左子树的右边界
                printEdge(cur.left);
            }
        }
        // 没有左子节点向右移动
        cur = cur.right;
    }
    printEdge(head);
}


// 逆序打印树的边界
public static void printEdge(Node node) {
    Node edge = reserveEdge(node);
    Node cur = edge;
    // 打印edge
    while (cur != null) {
        System.out.println(cur.val);
        cur = cur.right;
    }
    // 最后翻转回来
    reserveEdge(edge);
}


// 翻转树的边
public static Node reserveEdge(Node node) {
    Node next;
    Node pre = null;
    while (node != null) {
        next = node.right;
        node.right = pre;
        pre = node;
        node = next;
    }
    return pre;
}

宽度优先遍历

深度优先遍历就是先序遍历

思路:

1、准备一个队列

2、先将头节点存入到队列中

3、将头节点取出,进行处理

4、依次将左右节点存入到队列中

5、重复3、4的操作直到队列为空

// Width first traversal
public static void widthFirstTraversal(Node head) {
    if (head == null) {
        return;
    }
    // 准备一个队列
    Queue<Node> queue = new LinkedList<>();
    // 将头节点存入到queue中
    queue.add(head);
    while (!queue.isEmpty()) {
        // 取出一个元素
        head = queue.poll();
        System.out.print(head.val + " ");
        if (head.left != null) {
            queue.add(head.left);
        }
        if (head.right != null) {
            queue.add(head.right);
        }
    }
}

算法题目

给定二叉树的两个节点,找到他们的最低公共祖先节点

即给定同一颗树的两个节点,先上找找到的第一个共同的父节点就是这个节点将其进行返回即可

思路

1、记录每一个节点的父节点,然后将一个节点的向上走到根节点的所有的节点记录下来
2、如果将另一个节点向上走,一旦遇到第一个上一个点记录下来的节点,那他们的公共的节点就是这个节点

// 1、记录每一个节点的父节点,然后将一个节点的向上走到根节点的所有的节点记录下来
// 2、如果将另一个节点向上走,一旦遇到第一个上一个点记录下来的节点,
// 那他们的公共的节点就是这个节点
public static Node findCommonNode(Node head, Node node1, Node node2) {
    // 记录每一个节点的父节点
    HashMap<Node, Node> map = new HashMap<>();
    // 将head节点存入map
    map.put(head, head);
    process(head, map);
    // 用set存入node1节点的所有的父节点
    HashSet<Node> set = new HashSet<>();
    // 只有根节点的父节点是自己
    while (map.get(node1) != node1) {
        set.add(node1);
        node1 = map.get(node1);
    }
    set.add(head);
    // 进行第二个节点的向上进行遍历
    while (map.get(node2) != node2) {
        if (set.contains(map.get(node2))) {
            // 存在
            return map.get(node2);
        }
        node2 = map.get(node2);
    }
    return head;
}

public static void process(Node head, HashMap<Node, Node> map) {
    if (head == null) {
        return;
    }
    map.put(head.left, head);
    map.put(head.right, head);
    process(head.left, map);
    process(head.right, map);
}

思路:

使用树形DP的方式进行解决问题
先左子树索要公共的祖先节点,再先右进行索要公共的祖先节点
可能有以下的情况:
1、左子树和右子树都没有祖先节点,那直接返回null
2、当一方有的时候,返回存在的那一个
3、当左右节点都存在的时候,返回自己

// 1、左子树和右子树都没有祖先节点,那直接返回null
// 2、当一方有的时候,返回存在的那一个
// 3、当左右节点都存在的时候,返回自己
public static Node findCommonNode(Node head, Node node1, Node node2) {
    if (head == null || head == node1 || head == node2) {
        return head;
    }
    Node leftNode = findCommonNode(head.left, node1, node2);
    Node rightNode = findCommonNode(head.right, node1, node2);
    if (leftNode != null && rightNode != null) {
        return head;
    }
    // 当一方有的时候,返回存在的那一个
    return leftNode != null ? leftNode : rightNode;
}

求完全二叉树节点的数量

不能使用O(n)的时间复杂度

思路

1、因为满二叉树可以直接计算出节点的数量,想办法将完全二叉树转换成满二叉树和其他
2、计算出完全二叉树的最大的深度,判断右子树的最大深度,如果等于完全二叉树的深度说明左子树为完全二叉树
3、如果不等于,说明右边为高度减1的满二叉树

/**
 * @author zyq
 * @version 1.0
 * 计算完全二叉树的节点的个数
 */
public class CalcAnyTreeDemo {

    public static void main(String[] args) {
        Node node1 = new Node(1);
        Node node2 = new Node(2);
        Node node3 = new Node(3);
        Node node4 = new Node(4);
        Node node5 = new Node(5);
        node1.left = node2;
        node1.right = node3;
        node2.left = node4;
        node2.right = node5;
        System.out.println(calcTreeNum(node1));
    }

    public static class Node {
        public int val;
        public Node left;
        public Node right;
        public Node(int val) {
            this.val = val;
        }
    }

    // 计算完全二叉树的数量
    public static int calcTreeNum(Node head) {
        if (head == null) {
            return 0;
        }
        return process(head, 1, getNodeByLevel(head, 1));
    }

    /**
     * 获取head为头节点的完全二叉树的节点的数量
     * @param head node节点
     * @param level 当前所在的层
     * @ param h 当前树的最高层
     * @return 节点的数量
     */
    public static int process(Node head, int level, final int h) {
        // base case
        if (level == h) {
            return 1;
        }
        int res;
        if (getNodeByLevel(head.right, level + 1) == h) {
            // 说明左子树是一个满二叉树
            res = (1 << (h - level)) + process(head.right, level + 1, h);
        } else {
            // 说明右边是一个第一层的满二叉树
            res = (1 << (h - level - 1)) + process(head.left, level + 1, h);
        }
        return res;
    }

    // 获取node最左节点的深度
    public static int getNodeByLevel(Node node, int level) {
        while (node != null) {
            node = node.left;
            level++;
        }
        return level - 1;
    }
}

树形DP(解决二叉树的套路)

思路

每一个节点向左右子子树获取固定的值,然后拿这些数据给自己进行组装自己的数据进行传下去。

叉树节点间的最大距离

从二叉树的节点a出发,可以向上或者向下走,但沿途的节点只能经过一次,到达节点b时路径上的节点个数叫作a到b的距离,那么二叉树任何两个节点之间都有距离,求整棵树的最大距离。

思路

1、使用树形DP的方式解决这个问题

2、分成包括本节点和没有本节点的方式进行获取数据

3、分成下面的思路:

3.1、当包括本节点的时候,最大的距离就是左子树的最大高度 + 右子树的最大高度 + 1

3.2、当不包括本节点的时候,最大的距离出自左子树的最大距离和右子树的最大距离的最大值

4、将上面的数据进行集成就是,获取左右子树的最大距离(MaxDistance)和 高度(Height)

/**
 * @author zyq
 * @version 1.0
 * 节点与节点的最大距离
 */
public class NodeAndNodeMaxDistance {

    public static void main(String[] args) {
        // getNodeAndNodeMaxDistance();
    }

    // 树形DP封装的类的数据
    public static class ResultMsg {
        // 最大距离
        public Integer maxDistance;
        // 子树的高度
        public Integer height;

        public ResultMsg(Integer maxDistance, Integer height) {
            this.maxDistance = maxDistance;
            this.height = height;
        }
    }


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

    public static ResultMsg process(Node node) {
        // base case
        if (node == null) {
            return new ResultMsg(0, 0);
        }
        // 获取左右子树的数据
        ResultMsg leftData = process(node.left);
        ResultMsg rightData = process(node.right);
        // 组装自己的数据
        int leftDis = leftData.maxDistance;
        int rightDis = rightData.maxDistance;
        int height = leftData.height + rightData.height + 1;
        int maxDistance = Math.max(leftDis, Math.max(rightDis, height));
        return new ResultMsg(maxDistance, height);
    }
}


class Node {
    public Integer val;
    public Node left;
    public Node right;

    public Node(Integer val, Node left, Node right) {
        this.val = val;
        this.left = left;
        this.right = right;
    }
}

派对的最大快乐值

在这里插入图片描述
思路

1、使用树形DP的方式解决问题

2、对其进行分情况:

2.1、即判断这个员工是否被邀请,即分成两种情况

2.2、邀请这个员工,即可以获取这个员工的happy值,但是不可以邀请直接下级员工,即下级员工不来的最大快乐值的选择

2.3、不邀请这个员工,获取这个员工的happy值为0,下面的直接员工有两种情况,即邀请和不邀请,这个时候我们需要获取来或者不来中的最大值

3、封装成对应的类,需要两个属性,员工来的最大值和不来的最小值

public static int employeeMaxHappy(Employees employee) {
        if (employee == null) {
            return 0;
        }
        // 获取来和不来的最大和最小值
        Info info = process(employee);
        // 返回来和不来中的最大值
        return Math.max(info.noVal, info.yesVal);
    }

    public static Info process(Employees employees) {
        // base case
        if (employees.subordinate.isEmpty()) {
            // 来获取其的值    不来获取的值为0
            return new Info(employees.happy, 0);
        }
        // 封装自己的数据
        int yes = employees.happy;
        int no = 0;
        // 遍历所有的直接下级
        for (Employees employee : employees.subordinate) {
            // 递归出所有的直接下级的情况获取对应的数据
            Info subordinateInfo = process(employee);
            // 当自己(yes)来的时候 下级不可以来
            yes += subordinateInfo.noVal;
            // 当自己(yes)不来的时候 下级可以选择来和不来 获取最大值
            no += Math.max(subordinateInfo.noVal, subordinateInfo.yesVal);
        }
        return new Info(yes, no);
    }

    public static class Info {
        // 来的最大值
        public int yesVal;
        // 不来的最大值
        public int noVal;

        public Info(int yesVal, int noVal) {
            this.yesVal = yesVal;
            this.noVal = noVal;
        }
    }
}

class Employees {
    public int happy;
    public List<Employees> subordinate;

    public Employees(int happy, List<Employees> subordinate) {
        this.happy = happy;
        this.subordinate = subordinate;
    }
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值