Map、Set和搜索树的概念和使用

本文详细介绍了Java集合框架中的Map、Set接口以及搜索树的相关概念和使用。Map是一个键值对接口,其内部实现如HashMap和TreeMap各有特点,包括哈希表的哈希冲突解决策略。Set接口基于Map实现,提供了元素去重功能,如HashSet和TreeSet的差异在于底层数据结构和排序特性。此外,文章还讲解了二叉搜索树(二叉排序树)的查找、插入和删除操作。

目录

一、Map的概念和使用

1.什么是Map

2.对于Map遍历的使用

 3.Map常见方法的使用

4、哈希表

4.1、哈希表的概念

4.2、哈希冲突

 二、Set的概念和使用

1、什么是Set

2、Set常见方法的使用

 三、搜索树

3.1、搜索树的概念

3.2、查找

 3.3插入

 3.4删除


一、Map的概念和使用

1.什么是Map

Map不同于set,Map是一个接口,不能被实例化(如果需要实例化要实现TreeMap和HashMap)

Map类没有继承与Collection中,而是一个单独的键值对由<K,V>组成,并且K是唯一的不能被重复,如果添加的K重复,那么就会修改V中的值。但是map中的key不能被修改,如果需要修改请删除后重新加入

在Map中插入键值堆时,Key不能为空,value可以为空

Map中的Key和Value都可以被分离出来

2.对于Map遍历的使用

对于Map的遍历我们需要使用到Map.Entry<K,V>来进行遍历

对于整体进行遍历:(entry)

        for(Map.Entry<Integer,String> entry:map.entrySet()){
            System.out.print(entry+" ");
        }

 对于key进行遍历:(entry.getKey())

        for(Map.Entry<Integer,String> entry:map.entrySet()){
            System.out.print(entry.getKey()+" ");
        }

 对于value进行遍历:(entry.getValue())

        for(Map.Entry<Integer,String> entry:map.entrySet()){
            System.out.print(entry.getValue()+" ");
        }

 3.Map常见方法的使用

方法用法
V get(Object key)返回key中对应的value
V getOrDefault(Object key,V defaultValue)返回key中对应的value,不存在返回defaultValue

V put(K key,V value)

添加相应的键值对放入map中
V remove(Object key)删除key以及相对应的映射关系
Set<K> keySet()返回所有key的集合
Collection<V> values()返回所有value的集合
Set<Map.Entry<K,V>> entrySet()返回所有的映射关系
boolean containsKey(Object key)判断是否包含key
boolean containsValue(Object value)判断是否包含value
    public static void main(String[] args) {
        Map<Integer,String> map=new HashMap<>();
        //添加put
        map.put(1,"a");
        map.put(2,"b");
        map.put(3,"c");
        map.put(4,"d");
        map.put(5,"e");

        //get方法
        System.out.println(map.get(1));//a
        //getOrDefault方法
        System.out.println(map.getOrDefault(1, "qwe"));//a
        System.out.println(map.getOrDefault(10, "qwe"));//qwe

        //remove删除
        System.out.println(map.remove(1));//a
        System.out.println(map.remove(10));//null

        //keySet方法
        Set<Integer> set=map.keySet();

        //values方法(单独赋值)
        Collection<String> list= map.values();

        //判断包含关系
        System.out.println(map.containsKey(1));//false
        System.out.println(map.containsValue("a"));//flase
    }

注意:TreeMap和HashMap的区别。 

Map比较TreeMapHashMap
底层结构红黑树哈希桶
时间复杂度O(logN)O(1)
是否有序关于Key有序无序
线程安全不安全不安全
增删改查需要进行元素的比较通过哈希函数计算哈希地址在比较
比较与覆写Key必须可以比较自定义需要重写equals方法和hashCode方法
应用场景需要key有序需要更高的时间性能

 当哈希桶中的数组大于64,单链表大于8时,就会变成红黑树。

4、哈希表

4.1、哈希表的概念

相较于普通的顺序结构和平衡树的搜索效率,哈希表可以通过一一对应的关系,快速的查找到当前元素。

我们通过哈希函数实现:hash(key)=key%(数组长度)。

例如:我们插入一组数据(1,3,9,5,7)

这样我们就快速的将数组插入到相应的位置,查找时通过哈希函数调用出来就行了,但是当哈希函数得到的两个值相同会怎么办?

4.2、哈希冲突

当使用不同关键字通过哈希函数计算出相同的哈希地址,这种现象我们称之为哈希冲突。

对于解决哈希冲突我们可以采用下图的方法:

 对于避免哈希冲突:

 设计哈希函数,我们采用可以采用的方法有:

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种不同的符号在各位上出现的频率不一定相同,可能在某些位上分布比较均匀,每种符号出现的机会均等,在某些位上分布不均匀只有某几种符号经常出现。可根据散列表的大小,选择其中各种符号分布均匀的若干位作为散列地址。
 

 哈希因子:

散列表的载荷因子为:a=填入表中的元素个数/散列表的长度。

 当我们填入的个数大于载荷因子时,我们需要调节哈希表的大小,对数组进行扩容。

 解决哈希冲突:

解决哈希冲突的方法有两种:闭散列和开散列

闭散列:

1、线性探测

 当使用哈希函数计算时,如果发生哈希冲突,那么我们就会向后位移找到第一个空位,将数据填入。

注意:不能随便的物理删除、否则找不到其他值的搜索。

2、二次探测

 我们通过H=(H+I^2)%m和H=(H-1^2)%m来进行计算

注意:闭散列最大的缺陷是空间利用率比较低。

开散列:(哈希桶)

 我们把每个桶中的元素用通过单链表链接起来。我们就通过这种方法进行插入,在jdk1.8之前我们使用的是头差法,在jdk1.8之后我们使用的是尾插法。

实现:

public class Test04 {
    public static class Node{
        public int key;
        public int value;
        Node next;

        public Node(int key, int value) {
            this.key = key;
            this.value = value;
        }
    }

    public Node[] array;
    public int usedSize;

    public Test04() {
        this.array=new Node[10];
        this.usedSize=0;
    }
    //添加方法
    public void put(int key,int val){
        int index=key%this.array.length;
        Node cur=array[index];
        while(cur!=null){
            if(cur.key==key){
                cur.value=val;
            }
            cur=cur.next;
        }
        Node node=new Node(key,val);
        node.next=array[index];
        array[index]=node;

        if(loadFactor()>=0.75){
            resize();
        }
    }
    
    public void resize(){
        Node[] newArray=new Node[this.array.length*2];
        for(int i=0;i<this.array.length;i++){
            Node temp=array[i];
            while(temp!=null){
                Node tempNext=temp.next;
                
                int index=temp.key%newArray.length;
                temp.next=newArray[index];
                newArray[index]=temp;
                
                temp=tempNext;
            }
        }
        array=newArray;
    }
    
    public double loadFactor(){
        return this.usedSize*1.0/this.array.length;
    }
    
    //获取方法
    public int get(int key){
        int index=key%this.array.length;
        Node cur=array[index];
        
        while(cur!=null){
            if(cur.key==key){
                return cur.value;
            }
        }
        return -1;
    }
}

 二、Set的概念和使用

1、什么是Set

Set是继承Collection中的一个接口,Set中储存了key

Set的底层是使用Map来实现的,Set可以对集合中的元素进行去重操作

Set中的元素不能被修改,只能被删除,同时不能插入null

2、Set常见方法的使用

方法用法
boolean add(E e)添加元素
void clear()清空集合
boolean contains(Object o)判断o是否在集合中
Iterator<E> iterator()返回迭代器
boolean remove(Object o)删除集合中的o
int size()返回集合中的个数
boolean isEmpty()判断集合是否为空
Object[] toArray()将集合中的元素转化为数组返回
boolean containsAll(Collection<?> c)c中的元素是否在set中全部存在
boolean addAll(Collection<? extends E> c)将c中的元素全部转换到set中
    public static void main(String[] args) {
        Set<Integer> set=new HashSet<>();
        //添加
        set.add(1);
        set.add(2);
        set.add(3);
        set.add(4);
        set.add(5);

        //判断存在
        System.out.println(set.contains(1));//ture

        //迭代器
        Iterator<Integer> it=set.iterator();
        while(it.hasNext()){
            System.out.println(it.next());//1 2 3 4 5
        }

        //个数
        System.out.println(set.size());//5

        //判断是否为空
        System.out.println(set.isEmpty());//false

        //转换成数组
        Integer[] array= (Integer[])set.toArray();
        
    }

注意:TreeSet和HashSet的区别

set底层结构TreeSetHashSet
底层结构红黑树哈希桶
时间复杂度O(logN)O(1)
是否有序关于key有序不一定有序
线程不安全不安全
增删改查区别通过红黑树的特性来实现计算key计算key然后进行插入和删除
比较与覆写Key必须可以比较自定义需要重写equals方法和hashCode方法
应用场景需要key有序需要更高的时间性能

 三、搜索树

3.1、搜索树的概念

搜索树又称二叉排序树:

若左子树不为空、左子树上所有的节点的值都小于根节点

若右子树不为空、右子树上所有的节点的值都大于根节点

它的左右子树也分别为二叉搜索树

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

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

3.2、查找

我们直接遍历搜索树就行了

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

 3.3插入

我们通过遍历之后找到相对应的位置然后进行插入就行了

    public boolean insertTree(int val) {
        if(root==null){
            root=new TreeNode(val);
            return true;
        }
        
        TreeNode cur=root;
        TreeNode pre=null;
        
        while(cur!=null){
            if(cur.val<val){
                pre=cur;
                cur=cur.right;
            }else{
                pre=cur;
                cur=cur.left;
            }
        }
        if(pre.val<val){
            pre.right=new TreeNode(val);
        }else{
            pre.left=new TreeNode(val);
        }
        return true;
    }

 3.4删除

我们先对位置进行查找

1. cur.left == null

cur 是 root,则 root = cur.right

cur 不是 root,cur 是 parent.left,则 parent.left = cur.right

cur 不是 root,cur 是 parent.right,则 parent.right = cur.right

2. cur.right == null

cur 是 root,则 root = cur.left

cur 不是 root,cur 是 parent.left,则 parent.left = cur.left

cur 不是 root,cur 是 parent.right,则 parent.right = cur.left

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

我们找到cur的左边的最大值或者cur的右边的最小值

将它的值放入到cur位置上,在通过上述两种方法进行调换
 

    public void remove(int key){
        TreeNode cur=root;
        TreeNode parent=null;

        while(cur!=null){
            if(cur.val<key){
                parent=cur;
                cur=cur.right;
            }else if(cur.val==key){
                removeNode(parent,cur);
            }else{
                parent=cur;
                cur=cur.left;
            }
        }
    }
    public void removeNode(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 tempParent=cur;
            TreeNode temp=cur.right;
            while(cur.left!=null){
                tempParent=temp;
                temp=temp.left;
            }
            cur.val=temp.val;
            
            if(tempParent.left==temp){
                tempParent.left=temp.right;
            }else{
                tempParent.right=temp.right;
            }
        }
    }

评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

now just do it

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值