HashMap以及二叉搜索树(Map和Set)

概要

 根据图表就可以知道,Set和Map是两个接口,这个两个接口是不通,Set是collection接口下的,而Map是单独存在的一个接口。而set下分为TreeSet和HashSet数据结构,而Map下也分为HashMap和TreeMap。

所以在此就不得不说两种结构。Set和Map

Map:

使用的存储结构:

Map 中存储的就是 key-value 的键值对。

什么是key-value结构呢?

简单而言就是,一把钥匙开与之对应的保险箱。其实就是一个一一对应的关系,及一个键值对存储一个信息。        

 如此我们就可以通过找到钥匙,就能快速找到钥匙与之对应的数据。

做统计,这种数据结构贼好用,据我所知大数据就会使用大量这种数据结构

        

返回值方法解释
V
get (Object key)
返回 key 对应的 value
V
getOrDefault (Object key, V defaultValue)
返回 key 对应的 value key 不存在,返回默认值
V
put (K key, V value)
设置 key 对应的 value
V
remove (Object key)
删除 key 对应的映射关系
Set<K>
keySet ()
返回所有 key 的不重复集合
Collection<V>
values ()
返回所有 value 的可重复集合
Set<Map.Entry<K, V>>
entrySet ()
返回所有的 key-value 映射关系
boolean
containsKey (Object key)
判断是否包含 key
boolean
containsValue (Object value)
 
判断是否包含 value

1.Map中存放键值对的Key是唯一的,value是可以重复的

2.TreeMap中插入键值对时,key不能为空,否则就会抛NullPointerException异常value可以为空。但是HashMapkeyvalue都可以为空。

3.Map中的Key可以全部分离出来,存储到Set来进行访问(因为Key不能重复)

4.Map中的value可以全部分离出来,存储在Collection的任何一个子集合中(value可能有重复)

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

Set:

使用的存储结构:

Set 中只存储了 Key
简单而言就是,key就是我们要找的值,
返回值方法解释
boolean
add (E e)
添加元素,但重复元素不会被添加成功
void
clear ()
清空集合
boolean
contains (Object o)
判断 o 是否在集合中
Iterator<E>
iterator ()
返回迭代器
boolean
remove (Object o)
删除集合中的 o
int
size()
返回 set 中元素的个数
boolean
isEmpty()
检测 set 是否为空,空返回 true ,否则返回 false
Object[]
toArray()
set 中的元素转换为数组返回
boolean
containsAll(Collection<?> c)
集合 c 中的元素是否在 set 中全部存在,是返回 true ,否则返回
false
boolean
addAll(Collection<? extends
E> c)
将集合 c 中的元素添加到 set 中,可以达到去重的效果
1. Set 最大的功能就是对集合中的元素进行去重
2.  Set 中只存储了 key 并且要求 key一定要唯一
3.  实现 Set 接口的常用类有 TreeSet HashSet ,还有一个 LinkedHashSetLinkedHashSet是在HashSet的基础 上维护了一个双向链表来记录元素的插入次序
4. Set 中的 Key 不能修改,如果要修改,先将原来的删除掉,然后再重新插入
5.TreeSet 中不能插入 nullkey HashSet 可以。

HashMap与TreeSet

TreeSet

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

一颗二叉树搜索树如何构建?:(这里有个错误,9和8的位置得换一下)

 发现没有,一个搜索树其实就是一颗排序树,会将里面的数据进行排序。那么对于这颗搜索树,如何体现搜索二字呢?

如何查找:

假如我查一个数字:2,他会怎么走呢?(这里有个错误,9和8的位置得换一下)

搜索树如何所搜

如何插入:

插入一个数字:11,他会怎么走?(这里有个错误,9和8的位置得换一下)

二叉树搜索插入

如何删除(巨麻烦)(总体来说七种可能):

而每种可能都可能存在以下情况,也就是说我们需要写七种情况的代码。

cur为我要删除的节点

情况1:(左树为空)

cur.left == null
如何删除:(用右边替换)
cur root ,则 root = cur.right
cur parent.left ,则 parent.left = cur.right
cur parent.right ,则 parent.right = cur.right
        

情况2:(右树为空)

cur.right == null
如何删除:(用左边替换)
cur root ,则 root = cur.left
cur parent.left ,则 parent.left = cur.left
cur parent.right ,则 parent.right = cur.left

情况3:(都不为空)

cur.left != null && cur.right != null

需要使用 替换法 进行删除,即在它的右子树中寻找中序下的第一个结点 ( 关键码最小 ) ,用它的值填补到被 删除节点中,再来处理该结点的删除问题

删除树中元素

自我实现:
public class Binarysearch {
    static class TreeNode{
        public int val;
        public TreeNode left;
        public TreeNode right;
        public TreeNode(int val){
            this.val=val;
        }
    }
    public TreeNode root =null;

    
    //查找
    public TreeNode find(int val){
        TreeNode node =root;
        if (root==null){
            return null;
        }
        while (node!=null){
            if(node.val==val){
                return node;
            }else if (node.val<val){
                node=node.left; //左
            } else  {
                node=node.right;//右
            }
        }
        return null;
    }

    
    //插入
    public void insert(int val){
        if (root==null){
            root=new TreeNode(val);
            return;
        }
        TreeNode cur=root;
        TreeNode parent=null;
        while (cur!=null){
            if (cur.val<val){
                parent=cur;
                cur=cur.left;
            }else if (cur.val>val){
                parent=cur;
                cur=cur.right;
            }else {
                return;
            }
        }
        TreeNode node=new TreeNode(val);
        if (parent.val<val){
            parent.left=node;
        }else {
            parent.right=node;
        }

    }
    
    //中序遍历
    public void inorder(TreeNode root){
        if (root==null){
            return;
        }
        inorder(root.left);
        System.out.println(root.val+" ");
        inorder(root.right);
    }
    
    //删除树
    public void delTree(int val ){
        TreeNode cur=root;
        TreeNode parent=null;
        while (cur!=null){
            if (cur.val==val){
                remove( parent ,cur);
                return;
            } else if (cur.val<val) {
                parent=cur;
                cur=cur.right;
            }else {
                parent=cur;
                cur=cur.right;
            }
        }

    }
//删除的核心代码
    private void remove(TreeNode parent, TreeNode cur) {
        if (cur.left==null){
            if (cur==root){
                root=cur.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=targetParent.left;
            }
            cur.val= target.val;
            if(targetParent.left==target){
                targetParent.left=target.right;
            }else {
                targetParent.right=target.right;
            }

        }
    }
}
TreeMap TreeSet 是  java 中利用搜索树实现的 Map Set ;实际上用的是红黑树。
什么是红黑树,这里不做解释,在后续文章中会出现。因为其自身的特点,就可以知道,他们会对元素进行排序。(这个特点可以利用)

HashMap

  我还是这句话:不同的数据结构是为了应对不同的数据情况。

哈希值与哈希冲突:
顺序结构以及平衡树 中,元素关键码与其存储位置之间没有对应的关系,因此在 查找一个元素时,必须要经过关键 码的多次比较 顺序查找时间复杂度为 O(N) ,平衡树中为树的高度, O( log_{2}N),搜索的效率取决于搜索过程中元素的比较次数。所以就应该有一种直接找到元素的。 通过某种函 使元素的存储位置与它的关键码之间能够建立一一映射的关系,那么在查找时通过该函数可以很快 找到该元素
哈希函数:hash(key) = key % capacity(长度)

 但是呢,相信大家都发现一个问题,那就是,诺是我在输入一个数字14进这个表, 而4这个位置已经有元素了。

解决方案:

此时就出现了二种解决方案。

1.将14放入与之相邻的空位置,并且在之后扩容后,将数字放到本该属于他的位置。(线性探测(分两次)闭散列

第一次查找位置,发现有元素了,然后再一次查找位置,找一个空位置坐下。

2.我们在哈希表中添加一个链表,诺有相同的哈希值的元素是我们就将这个值放入链表中。(具体名字叫:哈希桶(开散列))

但是这样的话就存在一个问题就是所搜效率的问题。比如10万个数据,10万个哈希值相同,然后我们在这个链表里不停翻,去抄我们要的元素。这样的效率很慢啊。所以这个桶也可以用另一个搜索速度更快的结构代替。比如:

                                        1. 每个桶的背后是另一个哈希表

                                        2. 每个桶的背后是一棵搜索树

 哈希函数设计的越精妙,产生哈希冲突的可能性就越低,但是无法避免哈希冲突

负载因子:

在设计上去衡量一个哈希表的是否越来越接近冲突,设计师:提出了负载因子调节的概念:(这个是网上找的图片)

 如此我们便因此去设定一个状态点:负载因子,诺是超过,我们就进行扩容。这样始终将负载因子保持在1以下。

所以就此我们可以实现以下代码。(还是用链表了,主要是简单明了)
自我实现
public class HashMap {
    static class Node{
        public int key;
        public int val;
        public Node next;
        public Node(int key,int val){
            this.key=key;
            this.val=val;
        }
    }
    public Node[] array;
    //元素个数
    public int usedSize;
    //元素节点
    public HashBuck(){
        array=new Node[8];
    }
//设计的临界值
    public static final double LOAD_FACTOR=0.75;

    public void put(int key,int val){
        int index=key%array.length;
        Node cur=array[index];
        while (cur!=null){
            if (cur!=null){
                cur.val=val;
                return;
            }
            cur=cur.next;
        }
        Node node=new Node(key, val);
        node.next=array[index];
        array[index]=node;
        usedSize++;
//实际负载因子超过设置的临界值就扩容
        if (calculateLoadFactor()>=LOAD_FACTOR){

            //扩容
            reszie();
        }

    }
    //扩容
    private void reszie() {
        Node []newArray=new Node[2* array.length];
        for (int i = 0; i < array.length; i++) {
            Node cur=array[i];
            //重新规划
            while (cur!=null){
                Node curNext=cur.next;
                int index=cur.key% newArray.length;

                cur.next=newArray[index];
                newArray[index]=cur;
                cur=curNext;
            }
        }
        array=newArray;
    }
//实际的负载因子
    private double calculateLoadFactor(){

        return usedSize*1.0/ array.length;
    }
    //查找元素
    public int get(int key){
        int index=key% array.length;
        Node cur=array[index];
        while (cur!=null){
            if (cur.key==key){
                return cur.val;
            }
            cur=cur.next;
        }
        return -1;
    }
}

技术细节

在java本中

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

小结

TreeMap HashMap 的区别
Map底层结构TreeMapHashMap
底层结构红黑树哈希桶
插入/删除/查找时间复杂度O(log_{2}N)O(1)
是否有序关于Key有序无序
线程安全不安全不安全
插入/删除/查找区别需要进行元素比较通过哈希函数计算哈希地址
比较与覆写
key 必须能够比较,否则会抛出 ClassCastException异常
自定义类型需要覆写 equals 和 hashCode方法
应用场景需要Key有序场景下
Key是否有序不关心,需要更高的时间性能
TreeSet HashSet 的区别
Set底层结构TreeSetHashSet
底层结构红黑树哈希桶
插入 / 删除 / 查找时间复杂度
O(log_{2}N)O(1)
是否有序关于Key有序不一定有序
线程安全不安全不安全
插入/删除/查找区别按照红黑树的特性来进行插入和删除
1. 先计算 key 哈希地址
2. 然后进行 插入和删除
比较与覆写
key 必须能够比较,否则会抛出
ClassCastException 异常
自定义类型需要覆写 equals 和 hashCode方法
应用场景需要Key有序场景下Key是否有序不关心,需要更高的时间性能

其他文章接口

 1.String方法(重要,对于操作字符串有巨大的帮助)


文章链接


2.java常用的接口及其方法(包含拷贝,比较,排序,构造器)

文章链接


3.初阶数据结构


 3.1 顺序表:ArrayList

[文章链接]


3.2 链表:LinkedList


[文章链接]

3.3 栈:Stack

[文章链接]


 3.4 队列:Queue


[文章链接]


3.5 二叉树:Tree


[文章链接]


3.6 优先级队列:PriorityQueue(堆排序)


[文章链接]


3.7 Map和Set
HashMap和HashSet,TreeMap和TreeSet


[文章链接]


4. 排序(7种方式)


4.1 插入排序(两种)


4.2  选择排序(两种)


4.3 快速排序


4.4 堆排序
里面有堆排序的实现和逻辑


[文章链接]


 4.5  归并排序


5.多线程


[文章链接]


6.网络编程


7.HTML


8.数据库Mysql


[文章链接]
 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值