Java - Set&Map

目录

一.搜索树

二.Map:

三.关于Map.Entry的说明:,>

四.Set:

五. 哈希表:



一.搜索树

        我们如果想学习一下Set&Map的话,不妨先学习一下其本质——搜索树!

二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:
1.若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
2.若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
3.它的左右子树也分别为二叉搜索树

模拟实现:

1.插入节点

        ①. 先 root  == null 直接让root 等于该节点

        ②. 然后找到叶子节点

        ③. 但是插入的时候,得有parent记录cur 的前一个父亲节点 然后看parent与插入节点的关系。

public boolean insert(int key){
        TreeNode cur = new TreeNode(key);
        if(root == null){
            root = cur;
        }
        TreeNode parent = null;
        cur = root;
        while(cur != null){
            if(cur.val > key){
                parent = cur;
                cur = cur.left;
            }else if(cur.val < key){
                parent = cur;
                cur = cur.right;
            }else{
                return false;
            }
        }
        TreeNode tar = new TreeNode(key);
        //此时cur == null了
        if(parent.val < key){
            parent.right = tar;
        }else{
            parent.left = tar;
        }
        return true;
    }

2.搜索节点

        利用搜索树的性质进行搜索!

public TreeNode search(int val){
        TreeNode cur = root;
        while(cur != null){
            if(cur.val < val){
                cur = cur.right;
            }else if(cur.val > val){
                cur = cur.left;
            }else{
                return cur;
            }
        }
        return null;
    }

3.  删节点      ?

public void remove(int key) {
        TreeNode parent = null;
        TreeNode cur = root;
        while (cur != null) {
            if(cur.val == key) {
                removeNode(parent,cur);
                return;
            }else if(cur.val < key) {
                parent = cur;
                cur = cur.right;
            }else {
                parent = cur;
                cur = cur.left;
            }
        }
    }

    private void removeNode(TreeNode parent,TreeNode cur){
        if(cur.left == null){
            if(cur == root){
                root = root.right;
            }else if(parent.left == cur){
                parent.left = cur.right;
            }else{
                parent.right = cur.right;
            }
        } else if (cur.right == null) {
            if(cur == root){
                root = cur.left;
            }else if(parent.left == cur) {
                parent.left = cur.left;
            }else{
                parent.right = cur.left;
            }
        }else {
            TreeNode target = cur.right;
            TreeNode targetParent = cur;
            while(target.left != null){
                targetParent = target;
                target = target.left;
            }
            //这里已经找到了右树的最左节点
            cur.val = target.val;
            //删节点

            //找最小值的时候,left没有数据
            if(targetParent.right == target){
                targetParent.right = target.right;
            }else{
                targetParent.left = target.right;
            }
        }
    }

我们要知道的是:TreeMap和TreeSet本质都是一颗搜索树,但这棵搜索树比较特殊 是红黑树!


二.Map:

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

        1.1        map的模型

             map存储的是Key-Value键值对

                                           

注意TreeMap实现了SortedMap接口:TreeMap可以比较的

意味着 存入的数据key是可比较的(否则 就会报错 !),而且存入的时候是比较之后再存入的!

 所以,如果是put的时候key是一个自定义类:就需要实现comparable接口,重写其方法 亦或者传一个比较器!

        1.2 map方法:

          

要注意的是 entrySet()方法:

可以理解为 :将Map内的数据key与value组织变成一个整体(此结构叫Map.Entry<K,V>),起来放入一个Set的大麻袋里(无规则放入) 

//此方法为了遍历集合! 且map没有实现Iterable接口

1. Map是一个接口,不能直接实例化对象,如果要实例化对象只能实例化其实现类TreeMap或者HashMap
2. Map中存放键值对的Key是唯一的,value是可以重复的
3. Map中的Key可以全部分离出来,存储到Set中来进行访问(因为Key不能重复)。
4. Map中的value可以全部分离出来,存储在Collection的任何一个子集合中(value可能有重复)。
5. Map中键值对的Key不能直接修改,value可以修改,如果要修改key,只能先将该key删除掉,然后再来进行重新插入

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


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

因此entry之后再使用以上三种方法!

四.Set:

        1.1 set

与Map不同的是:Set继承Collection的接口类,Set只存储了Key!

不能存储相同的元素

 且key必须是可比较的,但是为啥这样呢?

观察add源码可以知道 相当于其本质就是TreeMap,但是value值变成固定的PRESENT!

TreeMap与TreeSet的当中去存储元素的时候,他们的key一定可以比较,否则会出异常

         2.2 Set 方法

1. Set是继承自Collection的一个接口类
2. Set中只存储了key,并且要求key一定要唯一
3. Set的底层是使用Map来实现的,其使用key与Object的一个默认对象作为键值对插入Map中的
4. Set最大的功能就是对集合中的元素进行去重
5. 实现Set接口的常用类有TreeSet和HashSet,还有一个LinkedHashSet,LinkedHashSet是在HashSet的基础上维护了一个双向链表来记录元素的插入次序。
6. Set中的Key不能修改,如果要修改,先将原来的删除掉,然后再重新插入
7. Set中不能插入null的key。 


五. 哈希表:

        这种数据结构可以实现搜索时达到O(1)的时间复杂度!

        5.1 概念

可以不经过任何比较,一次直接从表中得到要搜索的元素。 如果构造一种存储结构,通过某种函
(hashFunc)使元素的存储位置与它的关键码之间能够建立一一映射的关系,那么在查找时通过该函数可以很快找到该元素。

        该方式叫哈希方法/散列方法

                转换函数叫哈希函数

                        这种数据结构叫哈希表

        5.2 哈希冲突

对于两个数据元素的关键字 和 (i != j),有 != ,但有:Hash( ) == Hash( ),即:不同关键字通过相同哈希哈数计算出相同的哈希地址,该种现象称为哈希冲突或哈希碰撞。
把具有不同关键码而具有相同哈希地址的数据元素称为“同义词”。

         5.2.1 哈希冲突-避免 

我们要知道哈希冲突是避免不了的,只能降低其发生的概率

        ①哈希函数设置不合理

        1. 直接定制法--(常用)
取关键字的某个线性函数为散列地址:Hash(Key)= A*Key + B 优点:简单、均匀 缺点:需要事先知道关
键字的分布情况 使用场景:适合查找比较小且连续的情况 面试题:字符串中第一个只出现一次字符
        2. 除留余数法--(常用)
设散列表中允许的地址数为m,取一个不大于m,但最接近或者等于m的质数p作为除数,按照哈希函数:
Hash(key) = key% p(p<=m),将关键码转换成哈希地址
        3. 平方取中法--(了解)
比特就业课
假设关键字为1234,对它平方就是1522756,抽取中间的3位227作为哈希地址; 再比如关键字为4321,对
它平方就是18671041,抽取中间的3位671(或710)作为哈希地址 平方取中法比较适合:不知道关键字的分
布,而位数又不是很大的情况
        4. 折叠法--(了解)
折叠法是将关键字从左到右分割成位数相等的几部分(最后一部分位数可以短些),然后将这几部分叠加求和,
并按散列表表长,取后几位作为散列地址。
折叠法适合事先不需要知道关键字的分布,适合关键字位数比较多的情况
        5. 随机数法--(了解)
选择一个随机函数,取关键字的随机函数值为它的哈希地址,即H(key) = random(key),其中random为随机数
函数。
通常应用于关键字长度不等时采用此法
        6. 数学分析法--(了解)
设有n个d位数,每一位可能有r种不同的符号,这r种不同的符号在各位上出现的频率不一定相同,可能在某
些位上分布比较均匀,每种符号出现的机会均等,在某些位上分布不均匀只有某几种符号经常出现。可根据
散列表的大小,选择其中各种符号分布均匀的若干位作为散列地址。例如:
假设要存储某家公司员工登记表,如果用手机号作为关键字,那么极有可能前7位都是 相同的,那么我们可以
选择后面的四位作为散列地址,如果这样的抽取工作还容易出现 冲突,还可以对抽取出来的数字进行反转(如
1234改成4321)、右环位移(如1234改成4123)、左环移位、前两数与后两数叠加(如1234改成12+34=46)等方
法。

        ②负载因子

所以当冲突率达到一个无法忍受的程度时,我们需要通过降低负载因子来变相的降低冲突率。
已知哈希表中已有的关键字个数是不可变的,那我们能调整的就只有哈希表中的数组的大小。

        5.2.2 哈希冲突的解决

1.闭散列:

        ①.线性探测
当一个元素进行存入的时候进行完哈希函数之后那个位置有节点,就找下一个空的位置。

采用闭散列处理哈希冲突时,不能随便物理删除哈希表中已有的元素,若直接删除元素会影响其他元素的搜索。比如删除元素4,如果直接删除掉,44查找起来可能会受影响。因此线性探测采用标记的伪删除法来删除一个元素。

        ②.二次探测

2.开散列:

         开散列法又叫链地址法(开链法),首先对关键码集合用散列函数计算散列地址,具有相同地址的关键码归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链接起来,各链表的头结点存储在哈希表中。

可以理解为 数组 + 链表  的结构

而且当数组长度超过64并且链表的长度超过8的时候,就会变成红黑树!


与java中类集的关系:

1. HashMap 和 HashSet 即 java 中利用哈希表实现的 Map 和 Set
2. java 中使用的是哈希桶方式解决冲突的
3. java 会在冲突链表长度大于一定阈值后,将链表转变为搜索树(红黑树)
4. java 中计算哈希值实际上是调用的类的 hashCode 方法,进行 key 的相等性比较是调用 key 的 equals 方法。所以如果要用自定义类作为 HashMap 的 key 或者 HashSet 的值,必须覆写 hashCode 和 equals 方法,而且要做到 equals 相等的对象,hashCode 一定是一致的。(非常重要!!!)

模拟实现HashMap

public class HashBucket {
    private static class Node {
        private int key;
        private int value;
        Node next;
        public Node(int key, int value) {
            this.key = key;
            this.value = value;
        }
    }
 
 
    private Node[]  array;
    private int size;   // 当前的数据个数
    private static final double LOAD_FACTOR = 0.75;
    private static final int DEFAULT_SIZE = 8;//默认桶的大小
 
    public void put(int key, int value) {
        // write code here
        Node node = new Node(key,value);
        int index = key/array.length;
        Node cur = array[index];
        while(cur != null){
            if(cur.key == key){
                cur.value = value;
                return;
            }
            cur = cur.next;
        }
        //重复的已经更新后value值
        node.next = array[index];
        array[index] = node;
        size++;
        if((size * 1.0f / array.length) >= 0.75f){
            resize();
        }
    }
 
    private void resize() {
        // write code here
        Node[] newArray = new Node[DEFAULT_SIZE * 2];
        for(int i = 0;i < array.length;i++){
            Node cur = array[i];
            while(cur != null){
                int newIndex = size / newArray.length;
                cur.next = newArray[newIndex];
                newArray[newIndex] = cur;
                cur = cur.next;
            }
        }

        array = newArray;
    }
 
 
    private double loadFactor() {
        return size * 1.0 / array.length;
    }
 
 
    public HashBucket() {
        // write code here
        array = new Node[DEFAULT_SIZE];
    }
 
 
    public int get(int key) {
         // write code here
        int index = key % array.length;
        Node head = array[index];
        while (head != null) {
            if(head.key == key) {
                return head.value;
            }
            head = head.next;
        }
        return -1;
    }

}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值