持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第六天,点击查看活动详情。
最近在看左神的数据结构与算法,考虑到视频讲的内容和给出的资料的PDF有些出入,不方便去复习,打算出一个左神的数据结构与算法的笔记系列供大家复习,同时也可以加深自己对于这些知识的掌握,该系列以视频的集数为分隔,今天是第六篇:P8|图
一、二叉树相关概念
1、判断一棵树是否是搜索二叉树
1️⃣ 搜索二叉树
对于每一棵树来说,它的左树的结点都比它小,右树的结点都比它大
2️⃣ 判断方法
方法一
中序打印:在打印的时候,先打印左节点,然后头结点,最后右结点- 而搜索二叉树的性质是:右树>头>左树
- 因此判断方法就是:中序遍历,判断遍历的过程是否是升序的
方法二(此方法和下面的判断平衡二叉树的方法一致)
- 一棵树是搜索二叉树的四个条件:
- 它的左子树是搜索二叉树
- 它的右子树是搜索二叉树
- 左子树的最大值小于当前结点的值
- 右子树的最小值大于当前结点的值
- 因此在进行递归调用的时候,我们需要返回值有:
- 当前树的最小值
- 当前树的最大值
- 该树是否是搜索二叉树
3️⃣ 实现代码
public static boolean checkBST(Node head){
if(head == null){
return true;
}
boolean isLeftBst = checkBST(head.left);
if(!isLeftBst){
return false;
}
if(head.value <= preValue){
return false;
}else{
preValue = head.value;
}
return checkBst(head.right);
}
2、判断一棵树是否是完全二叉树
满二叉树的概念在讲堆排序的时候就讲到了,此处再提一下:
完全二叉树(Completed Binary Tree):所有的节点要么没有子节点、要么只有左孩子、或者有左孩子和右孩子,总之就是不能只有右孩子
1️⃣ 判断方法
- 做
层序遍历,层序遍历在[[数据结构与算法 左程云 P7]]中有讲到,我们在做层序遍历的时候添加两个条件:- 任意一个接地那有右节点但是没有左节点的,返回false,这个很直观
- 第二个条件是:当第一次出现了左右孩子不双全,即只有左节点的情况,那么后面遍历过程中所有的结点都应该是叶节点(没有孩子的结点)
2️⃣ 代码实现
public static boolean isCBT(Node head) {
if (head == null) {
return true;
}
LinkedList<Node> queue = new LinkedList<>();
boolean leaf = false;
Node l = null;
Node r = null;
queue.add(head);
while (!queue.isEmpty()) {
head = queue.poll();
l = head.left;
r = head.right;
if ((leaf && (l != null || r != null)) || (l == null && r != null)) {
return false;
}
if (l != null) {
queue.add(l);
}
if (r != null) {
queue.add(r);
} else {
leaf = true;
}
}
return true;
}
3、判断一棵树是否是满二叉树
- 获取二叉树的最大深度
depth,就有多少层 - 获取二叉树的总结点个数
count - 如果满足
2^depth - 1 == count,那么就是满二叉树
4、判断一棵树是否是平衡二叉树
1️⃣ 概念
平衡二叉树:对于任何一个子树来说,它的左树和右树的高度差不能超过一
2️⃣分析
- 现要保证一棵子树是平衡二叉树,那么对于它来说,这个子树是平衡二叉树的条件是:
- 它的左子树是平衡二叉树
- 它的右子树是平衡二叉树
- | 左子树的高度 - 右子树的高度 | ≤ 1
- 因此在进行递归调用的时候,我们需要的条件有:
- 该子树是否是平衡二叉树
- 该子树的高度
3️⃣ 代码实现
public static boolean isBalanced(Node head) {
return process(head).isBalanced;
}
// 封装返回值的对象
public static class ReturnType {
public boolean isBalanced;
public int height;
public ReturnType(boolean isB, int hei) {
isBalanced = isB;
height = hei;
}
}
public static ReturnType process(Node x) {
if (x == null) {
return new ReturnType(true, 0);
}
ReturnType leftData = process(x.left);
ReturnType rightData = process(x.right);
// 当前树的高度 = 左子树和右子树最大高度 + 1️
int height = Math.max(leftData.height, rightData.height);
// 是平衡树的三个条件:
// ① 左子树是平衡树
// ② 右子树是平衡树
// ③ 两颗子树的高度差小于2
boolean isBalanced = leftData.isBalanced && rightData.isBalanced
&& Math.abs(leftData.height - rightData.height) < 2;
return new ReturnType(isBalanced, height);
}️
二、公共祖先结点
题目:给定两个二叉树的结点
node1和node2,找到他们的最低公共祖先结点。
思路一
- 还是使用树形DP,把所有父子关系存入HashMap,即HashMap.put(子,父)
- 其实有了父与子的关系,即能通过子节点访问到父节点,要找最低公共祖先结点类似于两个非成环链表的公共结点了
- 不过这里的方法是:把
node1到根节点的所有结点放在HashSet中,然后node2往头结点走,每次遍历都去set中查看是否有该结点,第一个有的结点就是最低公共祖先结点
思路二
- 这个逻辑有点不好理解,我们先看代码
public static Node lowestAncestor(Node head, Node o1, Node o2) {
if (head == null || head == o1 || head == o2) {
return head;
}
Node left = lowestAncestor(head.left, o1, o2);
Node right = lowestAncestor(head.right, o1, o2);
if (left != null && right != null) {
return head;
}
return left != null ? left : right;
}
- 对于两个结点存在公共祖先的情况我们可以分为两种:
- 第一种——
node1是node2的祖先/node2是node1的祖先 - 第二种——
node1和node2有公共祖先
- 第一种——
- 两种情况图解示例分析如下:
三、找到一个结点的后继结点
题目:现在有一种新的二叉树结点类型如下:
public class Node{
public int value;
public Node left;
public Node right;
public Node parent;
public Node(int val){
value = val;
};
}
该结构比普通二叉树的节点结构多了一个指向父节点的
parent指针 假设有一个Node类型的结点组成的二叉树,树中每个结点的parent指针都正确的指向自己的父节点,根节点的parent指向null只给一个二叉树中的某个结点node,请返回node的后继结点后继结点:在中序遍历的序列中,node的下一个结点
实现思路
先要找一个随机结点
x的后继结点,存在的情况有以下几种:
1、该结点有右树
- 它的后继结点是右树上的最左结点
2、该结点没有右树
- 依次向上判断,当前结点是否是父节点的左子树,一直遍历到是为止,如果遍历结束都没有这种,那它的后继节点就是空
代码实现
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;
}
}
public static Node getLeftMost(Node node) {
if (node == null) {
return node;
}
while (node.left != null) {
node = node.left;
}
return node;
}
四、二叉树的序列化与反序列化
实现思路
- 使用
先序遍历/中序遍历/后序遍历 value之间使用!分隔null使用#代替- 下面用先序遍历举个例子:
实现代码
// 序列化
public static String serialByPre(Node head) {
if (head == null) {
return "#!";
}
String res = head.value + "!";
res += serialByPre(head.left);
res += serialByPre(head.right);
return res;
}
// 反序列化
public static Node reconByPreString(String preStr) {
String[] values = preStr.split("!");
Queue<String> queue = new LinkedList<String>();
for (int i = 0; i != values.length; i++) {
queue.offer(values[i]);
}
return reconPreOrder(queue);
}
public static Node reconPreOrder(Queue<String> queue) {
String value = queue.poll();
if (value.equals("#")) {
return null;
}
Node head = new Node(Integer.valueOf(value));
head.left = reconPreOrder(queue);
head.right = reconPreOrder(queue);
return head;
}