文章目录
二叉树的结构
节点的构成,通过节点的连接构成二叉树
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;
}