树的常用术语
- 节点
- 根节点
- 父节点
- 子节点
- 叶子节点
没有子节点的节点
- 节点的权
节点值
- 节点的度
该节点的子节点个数
- 路径
从root节点找到该节点的路径
- 层
- 子树
- 树的高度
最大层数
- 森林
多颗子树构成森林
- 深度
从最低层(根节点)到该节点的层数,根的深度为0
高度
从最高层(叶子节点)到该节点的层数,所有树叶的高度为0
二叉树
二叉树类别
每个节点最多只能有两个子节点,二叉树的子节点分为左节点和右节点。
满二叉树
如果该二叉树的所有叶子节点都在最后一层,并且结点总数=2^n-1, n为层数,则我们称为满二叉树。
完全二叉树
如果该二叉树的所有叶子节点都在最后一层或者倒数第二层,而且最后一层的叶子节点在左边连续,倒数第二层的叶子节点在右边连续,我们称为完全二叉树。
把 F 删了就不是完全二叉树了
二叉搜索树
左子节点值小于自身,右子节点值大于自身
1.插入方法put实现思想
- 1.如果当前树中没有任何一个结点,则直接把新结点当做根结点使用
- 2.如果当前树不为空,则从根结点开始∶
- 2.1如果新结点的key小于当前结点的key,则继续找当前结点的左子结点;
- 2.2如果新结点的key大于当前结点的key,则继续找当前结点的右子结点;
- 2.3如果新结点的key等于当前结点的key,则树中已经存在这样的结点,替换该结点的value值即可。
2.查询方法get实现思想:从根节点开始∶
- 1.如果要查询的key小于当前结点的key,则继续找当前结点的左子结点;
- ⒉.如果要查询的key大于当前结点的key,则继续找当前结点的右子结点;
- 3.如果要查询的key等于当前结点的key ,则树中返回当前结点的value。
3.删除方法delete实现思想:
- 1.找到被删除结点;
- 2.找到被删除结点右子树中的最小结点minNode
- 3.删除右子树中的最小结点
- 4.让被删除结点的左子树称为最小结点minNode的左子树,让被删除结点的右子树称为最小结点minNode的右子树(可以理解minNode换成了被删除节点的位置)
- 5.让被删除结点的父节点指向最小结点minNore
package tree;
/**
* @author pangjian
* @ClassName BinaryTree
* @Description 二叉树
* @date 2021/6/29 11:39
*/
public class BinaryTree<Key extends Comparable<Key>, Value> {
// 记录根节点
private Node root;
// 记录树中的元素的个数
private int N;
/**
* @Description:节点类
* @date 2021/6/29 11:45
*/
private class Node {
// 存储键
public Key key;
// 存储值
private Value value;
// 记录左子节点
public Node left;
// 记录右子节点
public Node right;
public Node(Key key, Value value, Node left, Node right) {
this.key = key;
this.value = value;
this.left = left;
this.right = right;
}
}
/**
* @Description:获取树中元素的的个数
* @return int
* @date 2021/6/29 11:45
*/
public int size(){
return N;
}
/**
* @Description: 向树中添加元素key-value
* @Param key:
* @Param value:
* @return void
* @date 2021/6/29 11:58
*/
public void put(Key key,Value value){
root = put(root,key,value);
}
/**
* @Description: 向指定树x中添加key-value,并返回添加元素后新的树
* @Param x:
* @Param key:
* @Param value:
* @return tree.BinaryTree<Key,Value>.Node
* @date 2021/6/29 11:59
*/
public Node put(Node x,Key key,Value value){
// 如果x子树为空
if(x==null){
N++;
return new Node(key,value,null,null);
}
// 如果x子树不为空
int cmp = key.compareTo(x.key);
// 如果key大于x节点的键,则继续找x节点的右子树,如果为空就把创建新节点赋值给当前节点的右节点
if (cmp>0){
x.right = put(x.right,key,value);
// 如果key小于x节点的键,则继续找x节点的左子树
}else if(cmp<0){
x.left = put(x.left,key,value);
// 如果key等于x节点的键,则替换x节点的值即可
}else {
x.value = value;
}
return x;
}
/**
* @Description: 查询树中指定key对应的value
* @Param key:
* @return Value
* @date 2021/6/29 12:27
*/
public Value get(Key key){
return get(root,key);
}
/**
* @Description: 从指定树x中,查找key对应的值
* @Param x:
* @Param key:
* @return Value
* @date 2021/6/29 12:27
*/
public Value get(Node x,Key key){
// x树为空
if(x==null){
return null;
}
// x树不为空
int cmp = key.compareTo(x.key);
// 如果key大于x节点的键,则继续找x节点的右子树
if (cmp>0){
return get(x.right,key);
// 如果key小于x节点的键,则继续找x节点的左子树
}else if(cmp<0){
return get(x.left,key);
// 如果key等于x节点的键,就找到了键为key的节点,返回该值
}else {
return x.value;
}
}
/**
* @Description: 删除树中key对应的value
* @Param key:
* @return void
* @date 2021/6/29 13:41
*/
public void delete(Key key){
delete(root,key);
}
/**
* @Description: 删除指定树x中的key对应的value,并返回删除后的新树
* @Param x:
* @Param key:
* @return tree.BinaryTree<Key,Value>.Node
* @date 2021/6/29 13:41
*/
public Node delete(Node x,Key key){
// x树为null
if(x==null){
return null;
}
// x树不为空
int cmp = key.compareTo(x.key);
// 如果key大于x节点的键,则继续找x节点的右子树
if (cmp>0){
x.right = delete(x.right,key);
// 如果key小于x节点的键,则继续找x节点的左子树
}else if(cmp<0){
x.left = delete(x.left,key);
// 如果key等于x节点的键,完成真正的删除节点动作,要删除的节点就是x
}else {
// 让元素个数减一
N--;
// 找到右子树中最小的节点
if(x.right==null){
return x.left;
}
if(x.left==null){
return x.right;
}
Node minNode = x.right;
while (minNode.left != null){
minNode = minNode.right;
}
// 删除右子树中最小的节点
Node n = x.right;
while (n.left!=null){
if(n.left.left==null){
n.left=null;
}else {
n = n.left;
}
}
// 让x节点的左子树变成minNode的左子树,让x节点的右子树变成minNode的右子树
minNode.left = x.left;
minNode.right = x.right;
// 让x节点的父节点指向minNode
x = minNode;
}
return x;
}
/**
* @Description:在整颗树中找到最小的键
* @return Key
* @date 2021/6/29 14:42
*/
public Key min(){
return min(root).key;
}
/**
* @Description: 在指定树x中找出最小键所在的节点
* @Param x:
* @return tree.BinaryTree<Key,Value>.Node
* @date 2021/6/29 14:42
*/
private Node min(Node x){
if(x.left!=null){
return min(x.left);
}else {
return x;
}
}
/**
* @Description:在整颗树中找到最大的键
* @return Key
* @date 2021/6/29 14:58
*/
public Key max(){
return max(root).key;
}
/**
* @Description:在指定的树x中,找到最大的键所在的节点
* @Param x:
* @return tree.BinaryTree<Key,Value>.Node
* @date 2021/6/29 14:58
*/
public Node max(Node x){
if(x.right!=null){
return max(x.right);
}else {
return x;
}
}
}
测试
package tree;
/**
* @author pangjian
* @ClassName BinaryTreeTest
* @Description TODO
* @date 2021/6/29 14:05
*/
public class BinaryTreeTest {
public static void main(String[] args) {
BinaryTree<Integer,String> tree = new BinaryTree<>();
tree.put(1,"张一");
tree.put(2,"李二");
tree.put(3,"黄3");
tree.put(4,"王四");
tree.put(5,"潘五");
System.out.println("插入完毕后元素个数:"+tree.size());
System.out.println("键2对应的元素是:"+ tree.get(2));
tree.delete(3);
System.out.println("删除后的元素个数:"+ tree.size());
System.out.println("删除后键3对应的元素个数:" + tree.get(3));
}
}
可以根据debug去理解
二叉树遍历
- 前序遍历:先输出父节点,再遍历左子树和右子树
- 中序遍历:先遍历左子树,再输出父节点,再遍历右子树
- 后序遍历:先遍历左子树,再遍历右子树,最后输出父节点
小结:看输出父节点的顺序,就确定是前序,中序还是后序
平衡树(AVL)
左右子树高度之差不超过1
2-3查找树
—棵2-3查找树要么为空,要么满足满足下面两个要求∶
- 2-结点︰
含有一个键(及其对应的值)和两条链,左链接指向2-3树中的键都小于该结点,右链接指向的2-3树中的键都大于该结点。·
- 3-结点∶
含有两个键(及其对应的值)和三条链,左链接指向的2-3树中的键都小于该结点,中链接指向的2-3树中的键都位于该结点的两个键之间,右链接指向的2-3树中的键都大于该结点。
(中链接)H 位于 E和J之间,(右链接)L 在 E和J大,(左链接)A和C 都比 E和J小
2-3查找树插入
向2-结点中插入新键
向一棵只含有一个3-结点的树中插入新键
向一个父结点为2-结点的3-结点中插入新键
向一个父结点为3-结点的3-结点中插入新键
分解根结点
2-3查找树性质
通过对2-3树插入操作的分析,我们发现在插入的时候,2-3树需要做一些局部的变换来保持2-3树的平衡。一棵完全平衡的2-3树具有以下性质:
- 任意空链接到根结点的路径长度都是相等的。
- 4-结点变换为3-结点时,树的高度不会发生变化,只有当根结点是临时的4-结点,分解根结点时,树高+1。
- 2-3树与普通二叉查找树最大的区别在于,普通的二叉查找树是自顶向下生长,而2-3树是自底向上生长。
红黑树
- 每个结点要么是红色,要么是黑色
- 根结点是黑色的
- 叶子节点都是黑色
- 如果一个结点是红色,那么他的孩子都是黑色的
红黑树的平衡化
左旋
右旋
旋转和颜色变换规则:所有插入的点默认为红色
变颜色的情况:当前插入结点(6)的父亲节点是红色,且它叔叔节点也是红色:
- 把父节点设为黑色
- 把叔叔节点也设为黑色
- 把父亲的父亲(爷爷)设为红色
- 把指计定义到祖父结点设为当前要操作的节点
左旋:当前父结点是红色,叔叔是黑色的时候,且当前的结点是右子树。左旋以父结点作为左旋。
刚刚变颜色后操作节点变成了12,而满足左旋规则后,操作节点则变成了12的父节点5,在5基础上进行左旋
右旋:当前父结点是红色,叔叔是黑色的时候,且当前的结点是左子树。右旋
- 把父结点变为黑色
- 把爷爷结点变为红色
- 以爷爷结点右旋转
package Hash;
/**
* @author pangjian
* @ClassName RedBlackTree
* @Description 红黑树部分代码
* @date 2021/6/30 16:00
*/
public class RedBlackTree {
private final int R = 0; // 红色
private final int B = 0; // 黑色
private Node root; // 红黑树的根节点
class Node{
int data;
int color = R; // 默认全部为红色
Node left;
Node right;
Node parent;
public Node(int data) {
this.data = data;
}
}
/**
* @Description:往root插入数据,先插入,然后判断变换规则去判断是变颜色还是旋转
* @Param root:
* @Param data:
* @return void
* @date 2021/6/30 16:04
*/
public void insert(Node root,int data){
if(root.data < data){
if(root.right == null){
root.right = new Node(data);
}else {
insert(root.right,data); //以root的右节点作为根继续后面的过程
}
}else {
if(root.left == null){
root.left = new Node(data);
}else {
insert(root.left,data);
}
}
}
/**
* @Description:以哪一个点去进行左旋
* @Param node:
* @return void
* @date 2021/6/30 16:09
*/
public void leftRotate(Node node){
// node就是根节点
if(node.parent == null){
Node right = root.right; // 就是黄色B节点
root.right = right.left; // A的右子树指向B的左子树
right.left.parent = root; // B的左子树父节点指向A节点
root.parent = right; // A的父亲变成B
right.parent = null; // 把B变成根节点
}else {
}
}
}