Map&Set 1

1、搜索树

1.1 概念

二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:

1、若它的左子树不为空,则左子树上所有节点的值都小于根节点的值。

2、若它的右子树不为空,则右子树上所有节点的值都大于根节点的值。

3、它的左右子树也分别是二叉搜索树。

下图是一棵二叉搜索树,它的中序遍历就是升序的。

后面我们将通过代码来实现二叉搜索树,下面是创建二叉搜索树节点的代码:

 static class TreeNode {
        public int val;
        public TreeNode left;
        public TreeNode right;

        public TreeNode(int val) {
            this.val = val;
        }
    }

    public TreeNode root = null;

 

1.2 代码实现—查找

因为二叉搜索树在根的左边的节点都比根小,在根右边的节点比根大,我们可以设置一个节点cur==root,如果当前节点的值大于要找的值,那么就让cur往左边走,如果当前节点的值小于要找的值,那么让cur往右边走,如果要找的值等于当前cur的值返回true。

1.3 代码实现—插入

由概念:我们可知二叉搜索树是一棵有序的树。而如果我们要在二叉搜索树上插入节点,那么插入的一定是叶子节点,所以我们可以和上面的查找方法一样,定义一个cur下标,如果cur的值比要插入的值小,就让cur往右边走;如果cur的值比要插入的值大,就让cur往左边走,与查找不同的是当走到相同的值时,则需要抛出异常(因为二叉搜索树是不允许出现节点值一样的情况的)。这里还需要注意,我们需要设置一个parent节点记录parent的值,不然当cur遍历完二叉搜索树时,我们无法找到要插入位置当前父亲的值。最后,我们刚才讨论的是树中有节点的情况,如果当前树是空树,我们只需要新建一个节点插入即可。(如果不讨论空树的情况会导致空指针异常)。

 

 

  //插入的位置一定是叶子
    //不能插入相同的数据的
    public void insert(int val) {
        TreeNode node = new TreeNode(val);
        //注意判断根空不空,防止空指针异常
        if(root == null){
            root = node;
            return;
        }
        TreeNode cur = root;
        TreeNode parent = null;
        while (cur != null) {
            if (cur.val < val) {
                parent = cur;
                cur = cur.right;
            } else if (cur.val > val) {
                parent = cur;
                cur = cur.left;
            }else{
                throw new valError("输入的val在二叉搜索树中已经存在");
            }
        }
        if(parent.val < val){
            parent.right = node;
        }else{
            parent.left = node;
        }
    }

1.4 代码实现—删除(难点)

引入:

二叉搜索树的删除是一项难点,如图,如果要删除值为8的节点,只需要让9连到7的右边就可以了。

替罪羊法 

但如果要删除的是左边值为1的节点呢?

删除了1的节点后,3的左边应该连接值为0的节点还是值为2的节点呢?解决的方法是使用替罪羊法:首先,找到左子树最右边的节点或者右子树最左边的节点(这里我们采用找到右子树最左边的节点进行讲解),然后我们用找到的这个节点覆盖掉原来要删除的值,然后将右子树最左边的节点进行删除。注意:这里删除的节点其实是我们找到的节点,而我们想要删除的节点只是被替换了。

如果要删除的节点是下图的值为90的节点 

通过上面对替罪羊法的讲解,我们知道我们要先找到右子树的最左边的节点,即找到了95这个节点 ,然后让95覆盖掉当前要删除的90这个节点,再删除右子树最左边值为95的节点。怎么删除呢?只需要把95的右子树连接到120的左边即可。

代码实现及其讲解:首先,我们要找到要删除的节点,找到节点后再通过方法进行删除(查找的方法可以类比插入,因为它也需要记录被删除节点的父亲节点)。


    public void remove(int val){
        TreeNode cur = root;
        TreeNode parent = null;
        while(cur != null){
            if(cur.val < val) {
                parent = cur;
                cur = cur.right;
            } else if (cur.val > val) {
                parent = cur;
                cur = cur.left;
            }else{//说明找到了要删除的节点
                removeNode(parent,cur);
                return;
            }
        }
    }

下面是删除的方法,需要分情况讨论:

情况1:cur.left == null

情况1.1:cur是root

情况如图所示:

此时只需要让root = root.right即可。

情况1.2:cur不是root,cur是parent.left 

情况如图所示,直接让cur.right = parent.left即可。

情况1.3:cur不是root,cur是parent.right

如下图,只需要让cur.right = parent.right即可 

情况2:cur.right == null

情况2.1:cur是root

直接让root = cur.left即可

情况2.2:cur不是root,cur是parent.left

让parent.left = cur.left。

情况2.3:cur不是root,cur是parent.right

parent.right  = cur.left 

情况3:cur.left != null && cur.right != null

使用上面所讲的替罪羊法解决。这里不做过多赘述。

代码实现如下: 

   private void removeNode(TreeNode parent,TreeNode cur){
        if(cur.left == null){//被删节点的左边为空
            if(cur == root){
                root =cur.right;
            } else if (cur == parent.left) {
                parent.left = cur.right;
            }else{
                parent.right = cur.right;
            }
        } else if (cur.right == null) {//被删节点的右边为空
            if(cur == root){
                root =cur.left;
            } else if (cur == parent.left) {
                parent.left = cur.left;
            }else{
                parent.right = cur.left;
            }
        }else{//被删节点的左右两边都不为空
            //可以去找左子树的最右边的元素,也可以找右子树最左边的元素,
            // 对cur进行替换,
            // 然后删除掉右子树最左边或者左子树最右边的元素
            TreeNode target = cur.right;
            TreeNode targetParent = cur;
            while(target.left != null){
                targetParent = target;
                target = target.left;
            }
            cur.val = target.val;
            //右子树最左边说明左边没有元素了
            if(target == targetParent.left){
                targetParent.left = target.right;
            }else{//没进入循环的情况
                targetParent.right = target.right;
            }
        }
    }

1.5 性能分析

插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能。
对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二叉搜索树的深度的函数,即结点越深,则比较次数越多。 但对于同一个关键码集合,如果各关键码插入的次序不同,可能得到不同结构的二叉搜索树:
最优情况下,二叉搜索树为完全二叉树,其平均比较次数为:\log 2N
最差情况下,二叉搜索树退化为单支树,其平均比较次数为:N/2

 2、搜索

2.1 概念及场景

Map set 是一种专门用来进行搜索的容器或者数据结构,其搜索的效率与其具体的实例化子类有关 。以前常见的搜索方式有:
1. 直接遍历,时间复杂度为 O(N) ,元素如果比较多效率会非常慢。
2. 二分查找,时间复杂度为O(log2N), 但搜索前必须要求序列是有序的。

上述排序比较适合静态类型的查找,即一般不会对区间进行插入和删除操作了,而现实中的查找比如:

1. 根据姓名查询考试成绩
2. 通讯录,即根据姓名查询联系方式
3. 不重复集合,即需要先搜索关键字是否已经在集合中
可能在查找时进行一些插入和删除的操作,即动态查找,那上述两种方式就不太适合了,本节介绍的 Map Set 是一种适合动态查找的集合容器。

2.2 模型

一般把搜索的数据称为关键字( Key ),和关键字对应的称为值( Value ),将其称之为 Key-value 的键值对,所以模型会有两种:
1. key 模型 ,比如:1、 有一个英文词典,快速查找一个单词是否在词典中。 2、 快速查找某个名字在不在通讯录中。
2. Key-Value 模型 ,比如: 统计文件中每个单词出现的次数,统计结果是每个单词都有与其对应的次数: < 单词,单词出现的次数 >。
Map 中存储的就是 key-value 的键值对, Set 中只存储了 Key。

3、Map的使用

Map的官方文档:Map 的官方文档

 3.1 关于Map的说明

Map 是一个接口类,该类没有继承自 Collection ,该类中存储的是 结构的键值对,并且 K 一定是唯一的,不 能重复

3.2 关于Map.Entry<K, V>的说明

Map.Entry<K, V> 是Map内部实现的用来存放键值对映射关系的内部类,该内部类中主要提供了
的获取, value 的设置以及Key的比较方式。

注意:Map.Entry<K,V>并没有提供设置Key的方法 

3.3 Map 的常用方法说明

注意:

1. Map 是一个接口,不能直接实例化对象 ,如果 要实例化对象只能实例化其实现类 TreeMap 或者 HashMap
2. Map 中存放键值对的 Key 是唯一的, value 是可以重复的。
3. TreeMap 中插入键值对时, key 不能为空,否则就会抛 NullPointerException 异常 value可以为空。但 HashMap key value 都可以为空。
4. Map 中的 Key 可以全部分离出来,存储到 Set 来进行访问 ( 因为 Key 不能重复 )
5. Map 中的 value 可以全部分离出来,存储在 Collection 的任何一个子集合中 (value 可能有重复 )

6. Map中键值对的Key不能直接修改,value可以修改,如果要修改key,只能先将该key删除掉,然后再来进行重新插入。

7. TreeMap和HashMap的区别:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值