HashMap以及二叉搜索树(Map和Set)
概要
根据图表就可以知道,Set和Map是两个接口,这个两个接口是不通,Set是collection接口下的,而Map是单独存在的一个接口。而set下分为TreeSet和HashSet数据结构,而Map下也分为HashMap和TreeMap。
所以在此就不得不说两种结构。Set和Map
Map:
使用的存储结构:
什么是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可以为空。但是HashMap的key和value都可以为空。
3.Map中的Key可以全部分离出来,存储到Set中来进行访问(因为Key不能重复)。
4.Map中的value可以全部分离出来,存储在Collection的任何一个子集合中(value可能有重复)。
5.Map中键值对的Key不能直接修改,value可以修改,如果要修改key,只能先将该key删除掉,然后再来进行重新插入
Set:
使用的存储结构:
返回值 | 方法 | 解释 |
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
中,可以达到去重的效果
|
HashMap与TreeSet
TreeSet
- 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
- 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
- 它的左右子树也分别为二叉搜索树
一颗二叉树搜索树如何构建?:(这里有个错误,9和8的位置得换一下)
发现没有,一个搜索树其实就是一颗排序树,会将里面的数据进行排序。那么对于这颗搜索树,如何体现搜索二字呢?
如何查找:
假如我查一个数字:2,他会怎么走呢?(这里有个错误,9和8的位置得换一下)
搜索树如何所搜
如何插入:
插入一个数字:11,他会怎么走?(这里有个错误,9和8的位置得换一下)
二叉树搜索插入
如何删除(巨麻烦)(总体来说七种可能):
而每种可能都可能存在以下情况,也就是说我们需要写七种情况的代码。
cur为我要删除的节点
情况1:(左树为空)
情况2:(右树为空)
情况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;
}
}
}
}
HashMap
我还是这句话:不同的数据结构是为了应对不同的数据情况。
哈希值与哈希冲突:
![](https://i-blog.csdnimg.cn/blog_migrate/275b71eb4839c4d5f2c9ce7680518c5b.png)
但是呢,相信大家都发现一个问题,那就是,诺是我在输入一个数字14进这个表, 而4这个位置已经有元素了。
解决方案:
此时就出现了二种解决方案。
1.将14放入与之相邻的空位置,并且在之后扩容后,将数字放到本该属于他的位置。(线性探测(分两次)闭散列)
第一次查找位置,发现有元素了,然后再一次查找位置,找一个空位置坐下。
2.我们在哈希表中添加一个链表,诺有相同的哈希值的元素是我们就将这个值放入链表中。(具体名字叫:哈希桶(开散列))
但是这样的话就存在一个问题就是所搜效率的问题。比如10万个数据,10万个哈希值相同,然后我们在这个链表里不停翻,去抄我们要的元素。这样的效率很慢啊。所以这个桶也可以用另一个搜索速度更快的结构代替。比如:
1. 每个桶的背后是另一个哈希表
哈希函数设计的越精妙,产生哈希冲突的可能性就越低,但是无法避免哈希冲突
负载因子:
在设计上去衡量一个哈希表的是否越来越接近冲突,设计师:提出了负载因子调节的概念:(这个是网上找的图片)
如此我们便因此去设定一个状态点:负载因子,诺是超过,我们就进行扩容。这样始终将负载因子保持在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本中
小结
Map底层结构 | TreeMap | HashMap |
底层结构 | 红黑树 | 哈希桶 |
插入/删除/查找时间复杂度 | O(1) | |
是否有序 | 关于Key有序 | 无序 |
线程安全 | 不安全 | 不安全 |
插入/删除/查找区别 | 需要进行元素比较 | 通过哈希函数计算哈希地址 |
比较与覆写 |
key
必须能够比较,否则会抛出 ClassCastException异常
|
自定义类型需要覆写
equals
和 hashCode方法
|
应用场景 | 需要Key有序场景下 |
Key是否有序不关心,需要更高的时间性能
|
Set底层结构 | TreeSet | HashSet |
底层结构 | 红黑树 | 哈希桶 |
插入
/
删除
/
查找时间复杂度
| O(1) | |
是否有序 | 关于Key有序 | 不一定有序 |
线程安全 | 不安全 | 不安全 |
插入/删除/查找区别 | 按照红黑树的特性来进行插入和删除 |
1.
先计算
key
哈希地址
2.
然后进行 插入和删除
|
比较与覆写 |
key
必须能够比较,否则会抛出
ClassCastException
异常
|
自定义类型需要覆写
equals
和 hashCode方法
|
应用场景 | 需要Key有序场景下 | Key是否有序不关心,需要更高的时间性能 |