目录
预备知识
树的基本概念
树是一种由节点组成的数据结构,但它比链表更加高级。在链表中,一个节点连接着另一个节点,树也是由许多的节点构成的。唯一的区别就是一个树节点可以连接多个树节点,一颗树只有一个根节点,根节点作为起源,由它展开一个树状的数据结构。
在树中,每个节点都含有自己的数值,以及与之相连的子节点,连接节点的线叫做相连线(edge)
- 根 图中A是根节点(root),同时也是B和C的父节点,即B和C为A的子节点,也称作孩子节点。
- 叶子节点 没有儿子节点的节点。如图中的H、I、J、F、G
- 兄弟节点 具有相同父亲的节点为兄弟
- 子树 一个树由许许多多的子树(sub-tree)构成,每个节点加上它所有的子节点(包括子节点的子节点们)就是一个子树,如图中,D、H、和I就是能构成sub tree,B、D、E、H、I、和J也是一个子树。
- 深度&高度 节点的高度(height),意味着此节点到尾节点之间相连线的数量。【注】最长的那条。图中,B的高度就是2,因为B到尾节点H之间的edge数量为2。节点的深度(depth),意味着此节点到根节点的edge数量【注】深度是唯一的。图中,D的深度是2,因为D到根节点A之间的edge数量是2。
树的种类
树的种类有很多,其中排序二叉树是重点,其他的树类型稍作理解
- 二叉树(Binary Tree):每个节点最多含有两个子节点,上面图示中的树就是二叉树。
- 完全二叉树(Complete Binary Tree):假设一个二叉树深度(depth)为d(d > 1),除了第d层外,其它各层的节点数量均已达到最大值,且第d层所有节点从左向右紧密排列,这样的二叉树就是完全二叉树。
满二叉树(Full Binary Tee):在满二叉树中,每个不是尾节点的节点都有两个子节点。
- 排序二叉树(Binary Search Tree):在此树中,每个节点的数值比左子树上的每个节点都大,比所有右子树上的节点都小。
- 平衡二叉树(AVL Tree):任何节点的两颗子树的高度差不大于1的二叉树。
- B树(B-Tree):B树和平衡二插树一样,只不过它是一种多叉树(一个节点的子节点数量可以超过二)。
- 红黑树(Red—Black Tree):是一种自平衡二叉寻找树。
二分查找树的实现
二分查找树(Binary Search Tree),也叫做排序二叉树。在这种树中,我们寻找一个特定的数值非常容易,因为二分查找树满足以下的特性:每个节点都比自己左子树上的节点大,并比右子树上的节点小。如果我们想要寻找一个特定的元素,只需要依赖其特性,顺着特定的路径就能找到目标。在此树中,搜索、插入和删除的复杂度等于树高,往往就是O(logN),非常合适用来存储数据。
定义树和节点
1、树类包括:
- 数值
- 左孩子、右孩子 (这样好递归吧?俺猜的)
- 初始化函数
2、再声明一个根节点
【疑问】
- static是啥子哦
- 初始化函数…全忘了
- 这个根节点的声明方式?
static class TreeNode {
public int value;
public TreeNode left;
public TreeNode right;
public TreeNode(int value) {
this.value = value;
}
}
public TreeNode root;
在树中插入一个值 insert
- 整体思路
我们用 current 来寻找要插入的位置,通过比较数值大小(与 parent 比较)不断地迭代遍历左子树或右子树以找到合适的位置。由于二分查找树中不存在两个相同的值,因此若 current 为空,则插入。🔚
public void insert(int key) {
if(root == null) { //根节点为空,直接插入
root = new TreeNode(key);
return;
}
TreeNode current = root; //当前节点指向根节点
TreeNode parent = null; //定义一个父节点
while(true) { //将递归换成了这!
parent = current; //父亲节点跟上
if(key < parent.value) { //若值比父亲节点小
current = parent.left; //向左
if(current == null) { //到底了
parent.left = new TreeNode(key); //插入
return;
}
} else if (key > parent.value){ //若值比父亲节点大
current = parent.right; //向右
if(current == null) { //到底了
parent.right = new TreeNode(key); //插入
return;
}
} else {
return; // BST does not allow nodes with the same value
}
}
}
在树中寻找一个节点 get
public TreeNode get(int key) {
TreeNode current = root; //从根节点开始
while(current != null && current.value != key) { //没找到 or 没到底
if (key < current.value) { // 比当前值小
current = current.left; //向左
} else if (key > current.value) { //比当前值大
current =current.right; //向右
}
} //结束了即一个条件满足
return current == null ? null : current; //分别返回对应的情况
}
在树中删去一个节点 delete
- 整体分析
1、要删除的是尾节点
2、要删除的节点只有一个孩子
3、要删除的节点有两个孩子
public boolean delete(int key) {
TreeNode parent = root; //从根节点开始
TreeNode current = root; //从根节点开始
boolean isLeftChild = false; //先往后看
while(current != null && current.value != key) { //找到这个节点节点
parent = current; //父亲节点跟上!
if(current.value > key) { //向左找
isLeftChild = true; //更新
current = current.left;
} else {
isLeftChild = false; //更新
current = current.right;
}
}
if(current == null) { //找不到这个节点,报错
return false;
}
// Case 1: if node to be deleted has no children
if(current.left == null && current.right == null) { //是叶子节点
if(current == root) { //如果找到的是根节点
root = null; //删除
} else if(isLeftChild) { //如果是父亲节点的左孩子
parent.left = null; //删除
} else {
parent.right = null;
}
// Case 2: if node to be deleted has only one child
} else if (current.right == null) { //右孩子为空
if(current == root) { //是根节点
root = current.left; //让其左孩子当根
} else if (isLeftChild) { //如果是父亲节点的左孩子
parent.left = current.left; //删除
} else {
parent.right = current.left; //删除
}
} else if (current.left == null) { //左孩子为空,只有右孩子
if(current == root) { //该节点是根节点
root = current.right; //让右孩子当根
} else if (isLeftChild) { //如果是父亲节点的左孩子
parent.left = current.right;
} else {
parent.right = current.right;
}
// Case 3: current.left != null && current.right != null
} else { //有两个孩子
TreeNode successor = getSuccessor(current);
if (current == root) {
root = successor; //将找到的节点作为根
} else if (isLeftChild) { //如果是父亲节点的左孩子
parent.left = successor;
} else {
parent.right = successor;
}
successor.left = current.left; //最后...看图吧
}
return true;
}
//找到右子树中最下最左的节点
private TreeNode getSuccessor(TreeNode node) { //找到当前节点 右子树的最小值
TreeNode successor = null;
TreeNode successorParent = null;
TreeNode current = node.right; //右子树
while (current != null) { //遍历找到右子树 左边
successorParent = successor;
successor = current;
current = current.left;
}
if (successor != node.right) { //此时 current 为空
successorParent.left= successor.right;
successor.right = node.right;
}
return successor;
}
树的遍历
- 总述
1、前序遍历
2、中序遍历
3、后续遍历
前序遍历 Preorder Traversal
在前序遍历中,先访问节点自己,然后访问左子树,最后再访问右子树。对于每个节点迭代此操作。根左右
public static void preOrderTraversal(TreeNode root) {
if(root == null) { //如果树为空,本次遍历结束
return;
}
System.out.println(root.value); //根
preOrderTraversal(root.left); //左
preOrderTraversal(root.right); //右
}
中序遍历 Inorder Traversal
在中序遍历中,先访问左子树上的节点,再访问自己,最后再访问右子树上的节点。左根右
public static void inOrderTraversal(BST.TreeNode root) {
if(root == null) {
return;
}
inOrderTraversal(root.left);
System.out.println(root.value);
inOrderTraversal(root.right);
}
后序遍历 Postorder Traversal
在后序遍历中,先访问左右子树,最后再访问自己。左右根
public static void postOrderTraversal(TreeNode root) {
if(root == null) {
return;
}
postOrderTraversal(root.left);
postOrderTraversal(root.right);
System.out.println(root.value);
}
力扣练习
958. 二叉树的完全性检验
给定一个二叉树,确定它是否是一个完全二叉树。
- 别人的想法【跪】
1、若按照广度搜索给二叉树编号的话,定义 root-----1,会发现:任何节点的左孩子----- index * 2,右孩子------- index * 2 + 1
2、所以本题的核心其实就是 ➡️ 判断按照(1)中规则编号最终得出来的maxindex 是否等于 二叉树节点树