第七节: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;
二:搜索
本节所要介绍的map
和set
是一种专门用来进行搜索的容器或者数据结构。一般我们把搜索的数据称之为关键字(key
),把关键字的对应称之为值(value
),合称为 key-value
键值对,主要会有以下两种模型
- 纯
key
模型:例如快速查找某个名字是否存在于通讯录之中 key-value
模型:例如统计一篇文章中每个单词出现的次数,也即<单词,单词出现的次数>
三:Map使用
(1)Map介绍
Map:Map是一个接口类,它没有继承Collection
,所存储的是<K.V>
键值对,并且K一定是唯一的,不可以重复
- 由于
Map
是一个接口所以不可以直接实例化对象,如果要实例化,则只能实例化其实现类TreeMap
或者HashMap
Map
中key
必须唯一但value
可以重复Map
中key
不能直接修改但value
可以修改(如果要修改key
则必须先删除再插入)Map
中的key
可以全部分离出来存储到set
中去;Map
中的value
可以全部分离出来存储到Collection
的任何一个子集合中去
(2)TreeMap和HashMap区别
比较项目 | TreeMap | HashMap |
---|---|---|
底层结构 | 红黑树 | 哈希桶 |
操作时间复杂度 | O ( l o g 2 N ) O(log_2N) O(log2N) | O ( 1 ) O(1) O(1) |
是否有效 | 关于key 有序 | 无序 |
是否线程安全 | 不安全 | 不安全 |
操作区别 | 需要进行元素比较 | 通过哈希函数计算哈希地址 |
比较与重写 | key 必须要求可以比较 | 自定义类型需要重写equals 和hashCode 方法 |
应用场景 | 要求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()
方法获取key
和value
- 相关代码见下面示例
(4)使用示例
A:TreeMap使用示例
TreeMap使用示例:如下例,注意
TreeMap
是按照key
有序的,所以在传入的自定义类型时一定要实现其比较功能TreeMap
的key
值不可以设置为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
结果是一样的,所以它和存放次序无关,只和元素本身有关 HashMap
的key
值可以设置为null
四:Set使用
(1)Set使用
Setp:Set
是继承自Collection
的接口类且Set
中只存储key
(要求key
唯一)
-
Set
底层采用Map
实现,其使用Key
与Object
的一个默认对象作为键值对插入到Map
中
-
Set
最大的功能就是对集合中的元素进行去重 -
实现
Set
接口的常用类有TreeSet
、HashSet
、LinkedHashSet
,其中LinkedHashSet
是在HashSet
基础上维护了一个双向链表来记录元素的插入次序 -
Set
中的key
不可以修改(如果要修改需要先删除再插入) -
Set
中不能插入null
(2)TreeSet和HashSet区别
比较项目 | TreeMap | HashMap |
---|---|---|
底层结构 | 红黑树 | 哈希桶 |
操作时间复杂度 | O ( l o g 2 N ) O(log_2N) O(log2N) | O ( 1 ) O(1) O(1) |
是否有效 | 关于key 有序 | 不一定有序 |
是否线程安全 | 不安全 | 不安全 |
操作区别 | 按照红黑树特性操作 | 通过哈希函数计算哈希地址 |
比较与重写 | key 必须要求可以比较 | 自定义类型需要重写equals 和hashCode 方法 |
应用场景 | 要求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的主要内容,如果未能解决你的问题,请参考以下文章