高级数据结构-树

树的常用术语

在这里插入图片描述

  • 节点
  • 根节点
  • 父节点
  • 子节点
  • 叶子节点

没有子节点的节点

  • 节点的权

节点值

  • 节点的度

该节点的子节点个数

  • 路径

从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 {

        }
        
    }



}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值