第七节:Java集合框架之map和set

Posted 快乐江湖

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了第七节:Java集合框架之map和set相关的知识,希望对你有一定的参考价值。

文章目录

一:二叉排序树(二叉搜索树)基本概念及实现

(1)定义

二叉排序树(Binary Sort Tree)::又称之为二叉搜索树,它具有下面的性质

  • 若其左子树不空,则左子树上所有结点的值均小于根结点的值
  • 若其右子树不空,则右子树上所有结点的值均大于根结点的值
  • 其左、右子树也分别是二叉排序树

由以上性质可知,二叉排序树的中序遍历是一个递增序列

(2)二叉排序树操作

A:查找

二叉排序树查找:若树非空,让目标值与根节点的值进行比较。查找成功返回结点指针,失败则返回NULL

  • 如果相等,那么查找成功
  • 如果小于,则在左子树上继续查找
  • 如果大于,则在右子树上继续查找

B:插入

二叉排序树插入:其本质就是将关键字放到树中的合适位置,和查找思想一致,而且插入的地方总在叶子结点处

  • 注意:二叉排序树内不能存在两个相同的数据

C:删除

二叉排序树删除:二叉排序树的删除操作需要仔细分析,因为插入操作能保证每次插入后仍然是一颗二叉排序树,但是删除操作可能导致整个树的特性发生变化。二叉树排序树删除某结点时需要考虑三种情况

  • 待删除结点为叶子结点
  • 待删除结点的左子树或右子树为空
  • 待删除结点 的左子树和右子树都存在

当然叶子结点可以归结为左子树为空或右子树为空那一种情况,因此共有左为空,右为空和左右都不为空这么三种情况

①:如果左子树为空

处理办法:如果待删除结点左子树为空,那么让父亲的左子树或者右子树指向我的右子树


  • 需要注意,如果删除的是根结点,那么就让根结点的右孩子结点直接作为根结点

②:如果右子树为空

处理办法:如果待删除结点右子树为空,那么让父亲的左子树或者右子树指向我的左子树

③:如果左右子树都不为空

处理办法:从要删除的结点位置开始,寻找左子树的最右结点(也就是左子树的最大结点)或右子树的最左结点(也就是右子树的最小节点)替代要删除的结点。替代后,这个问题就转化为了删除左为空或右为空的结点了

如下,以寻找右子树的最左结点为例,这里删除根结点5。首先寻找5的右子树的最左结点,是6,submin标记,同时记录6的父亲结点7,submin_pre标记然后将submin处的6直接赋值给要删除的结点5,这样结点5等于就删除了,接着只需要将submin删除即可。在这种情况下找到的submin一定满足左子树为空,所以符合上面的那种情况,删除后让其父亲结点的左子树或右子树连接到它的右子树11即可

但是要注意一个特殊情况:submin本身就是要删除结点cur右子树的最小结点,因此submin_pre在赋值时,一定要赋值为cur

(3)二叉排序树实现

package myBinarySearchTree;

public class MyBinarySearchTree 
    //节点定义
    static class TreeNode
        public int val;
        public TreeNode left;
        public TreeNode right;

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

    public TreeNode root;

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

    //插入
    public Boolean insert(int key)
        TreeNode node = new TreeNode(key);
        //如果当前BST为空
        if(root == null)
            this.root = node;
            return true;
        
        TreeNode cur = root;
        TreeNode pre = null;
        while(cur != null)
            if(key > cur.val)
                pre = cur;
                cur = cur.right;
            else if(key < cur.val)
                pre = cur;
                cur = cur.left;
            else
                return false;//数据相同不能插入
            
        
        if(key > pre.val)
            pre.right = node;
        else
            pre.left = node;
        
        return true;
    

    //删除
    public void remove(int key)
        TreeNode cur = root;
        TreeNode pre = null;
        while(cur != null)
            if(key > cur.val)
                pre = cur;
                cur = cur.right;
            else if(key < cur.val)
                pre = cur;
                cur = cur.left;
            else
                //找到待删除结点
                removeNode(cur, pre);
                return;;
            
        
    
    //删除结点方法
    private void removeNode(TreeNode cur, TreeNode pre) 
        //情况1:如果待删除的结点左子树为空
        if(cur.left == null) 
            if(cur == root)this.root = this.root.right;//特殊情况:如果待删除结点本身就是根节点,那么直接让该结点的右结点作为根节点
            else
                //正常情况下就让其父节点的左子树或者右子树指向待删除结点的右子树即可
                if(pre.left == cur)pre.left = cur.right;
                if(pre.right == cur)pre.right = cur.right;
            
        //情况2:如果待删除的结点右子树为空
        else if(cur.right == null)
            if(cur == root)this.root = this.root.left;//特殊情况:如果待删除结点本身就是根节点,那么直接让该结点的左结点作为根节点
            else
                //正常情况下就让其父节点的左子树或者右子树指向待删除结点的左子树即可
                if(pre.left == cur)pre.left = cur.left;
                if(pre.right == cur)pre.right = cur.left;
            
        
        //情况3:如果左右子树都不为空
        /*从待删除结点开始寻找其右子树的最左结点(用submin标记该结点,并用submin_pre标记该结点的父节点)
         然后把submin结点复制到cur处,接着再删除submin结点即可
         在这种情况下找到的submin一定满足左子树为空,所以删除submin时就等价于情况1
         */
        else
            TreeNode submin_pre = cur;
            TreeNode submin = cur.right;
            while(submin.left != null)
                submin_pre = submin;
                submin = submin.left;
            
            cur.val = submin.val;
            if(submin_pre.left == submin)
                submin_pre.left = submin.right;
            else
                submin_pre.right = submin.right;
            
        
    


二:搜索

本节所要介绍的mapset是一种专门用来进行搜索的容器或者数据结构。一般我们把搜索的数据称之为关键字(key,把关键字的对应称之为值(value,合称为 key-value键值对,主要会有以下两种模型

  • key模型:例如快速查找某个名字是否存在于通讯录之中
  • key-value模型:例如统计一篇文章中每个单词出现的次数,也即<单词,单词出现的次数>

三:Map使用

(1)Map介绍

Map:Map是一个接口类,它没有继承Collection,所存储的是<K.V>键值对,并且K一定是唯一的,不可以重复

  • 由于Map是一个接口所以不可以直接实例化对象,如果要实例化,则只能实例化其实现类TreeMap或者HashMap
  • Mapkey必须唯一但value可以重复
  • Mapkey不能直接修改但value可以修改(如果要修改key必须先删除再插入
  • Map中的key可以全部分离出来存储到set中去;Map中的value可以全部分离出来存储到Collection的任何一个子集合中去

(2)TreeMap和HashMap区别

比较项目TreeMapHashMap
底层结构红黑树哈希桶
操作时间复杂度 O ( l o g 2 N ) O(log_2N) O(log2N) O ( 1 ) O(1) O(1)
是否有效关于key有序无序
是否线程安全不安全不安全
操作区别需要进行元素比较通过哈希函数计算哈希地址
比较与重写key必须要求可以比较自定义类型需要重写equalshashCode方法
应用场景要求key有序场景下关注时间性能

(3)Map常用方法说明

Map常用方法:如下

  • 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

这里对于Map.Entry<K, V>要重点说明一下: Map.Entry<K, V>Map的一个内部接口,它将key-value打包为了一个新的类型。这主要会在遍历中使用,调用Map的entrySet()方法将会返回这个Map.Entry<K, V>类型。在这以前,我们遍历一个Map集合时,需要获取key的值,然后再获取value的值,这是很繁琐和费时的。而现在通过entrySet()的方法返回一个Map.Entry<K, V>后,就可以通过它所提供的getKey()方法和getValue()方法获取keyvalue

  • 相关代码见下面示例

(4)使用示例

A:TreeMap使用示例

TreeMap使用示例:如下例,注意

  • TreeMap是按照key有序的,所以在传入的自定义类型时一定要实现其比较功能
  • TreeMapkey不可以设置为null
  • 如果put了两次相同的key,那么第二次就会覆盖第一次
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

public class TestTreeMapDemo 
    public static void main(String[] args) 
        Map<String ,Integer> map = new TreeMap<>();

        map.put("hello", 11);
        map.put("world", 22);
        map.put("we", 44);
        map.put("are", 55);
        map.put("friends", 33);
        System.out.println("hello:" + map.get("hello"));
        System.out.println("hello world:" + map.get("hello world"));
        System.out.println("hello:" + map.getOrDefault("hello world", -1)); //如果没有则返回-1
        System.out.println("AllKey:" + map.keySet());  //返回key集合
        System.out.println("AllValues:" + map.values());  //返回value集合
        System.out.println("key-friends是否存在:" + map.containsKey("friends"));
        System.out.println("value-99是否存在:" + map.containsValue(99));
        
        //Map.Entry示例
        System.out.println("Map.Entry示例-----------------------------------------------");
        Set<Map.Entry<String, Integer>> entrySet = map.entrySet();
        for(Map.Entry<String, Integer> entry : entrySet)
            System.out.println("key:" + entry.getKey() + "-" + "value:" + entry.getValue());
        
    


B:HashMap使用示例

TreeMap使用示例:如下例,注意

  • 使用HashMap存取元素时你会发现它和你的存放次序是不一致的,这是因为在存放元素时会利用该元素做hash运算,相同的元素的hash结果是一样的,所以它和存放次序无关,只和元素本身有关
  • HashMapkey可以设置为null

四:Set使用

(1)Set使用

SetpSet是继承自Collection的接口类且Set中只存储key(要求key唯一)

  • Set底层采用Map实现,其使用KeyObject的一个默认对象作为键值对插入到Map

  • Set最大的功能就是对集合中的元素进行去重

  • 实现Set接口的常用类有TreeSetHashSetLinkedHashSet,其中LinkedHashSet是在HashSet基础上维护了一个双向链表来记录元素的插入次序

  • Set中的key不可以修改(如果要修改需要先删除再插入)

  • Set不能插入null

(2)TreeSet和HashSet区别

比较项目TreeMapHashMap
底层结构红黑树哈希桶
操作时间复杂度 O ( l o g 2 N ) O(log_2N) O(log2N) O ( 1 ) O(1) O(1)
是否有效关于key有序不一定有序
是否线程安全不安全不安全
操作区别按照红黑树特性操作通过哈希函数计算哈希地址
比较与重写key必须要求可以比较自定义类型需要重写equalshashCode方法
应用场景要求key有序场景下关注时间性能

(3)Set常用方法说明

Map常用方法:如下

  • Boolean add(E, e):添加元素
  • void clear():清空集合
  • Boolean contains(Object o):判断o是否在集合中
  • Iterator<E> iterator():返回迭代器
  • int size():返回set中元素的个数
  • Boolean isEmpty():检测set是否为空
  • Object[] toArray():将set中的元素转换为数组返回
  • Boolean containsAll(Collection<?> c):判断集合c中的元素是否在set中是否全部存在
  • Boolean addAll(Collection<? extends E>c):将集合c中的元素添加到set中(可以达到去重效果)

(4)HashSet使用示例

import java.util.*;

public class TestTreeSetDemo 
    public static void main(String[] args) 
        Set<String> set = new HashSet<>();
        set.add("apple");
        set.add("orange");
        set.add("peach");
        set.add("banana");

        System.out.println("打印:" + set);
        System.out.println("返回set大小:" + set.size());
        System.out.println("判断\\"melon\\"是否存在:" + set.contains("melon"));
        set.remove("peach");
        System.out.println("删除\\"peach\\":" + set);

        //迭代器
        Iterator<String> it = set.iterator();
        while(it.hasNext())
            System.out.print(it.next() + "-");
        
        System.out.println();
    

以上是关于第七节:Java集合框架之map和set的主要内容,如果未能解决你的问题,请参考以下文章

第七节1:Java集合框架之二叉排序树和哈希表

第七节1:Java集合框架之二叉排序树和哈希表

第三节: Dart 中常用集合 List/Map

Java集合框架之Set,Map

第七节:框架搭建之页面静态化的剖析

第七节:python列表元组字典集合