需要映射数据结构一般是为了根据键来快速寻找值,键充当索引
删除元素时,键充当索引,可以不考虑键对应的值是什么,只要删除指定键对应的数据,相应的值也删除了
查找是否存在某个数据,也可以直接根据键来查找,类似于查字典,只要知道某个单词是否存在,不去管单词的释义是什么;类似查车牌号是否注册,不去管车辆的具体信息
使用get拿到具体信息
set给定键和新的值的信息,更新键对应的值,而不是传入新的键和值,不然就和add的作用重合了
public class LinkedListMap<K,V> implements Map<K,V>{
private class Node{
public K key;
public V value;
public Node next;
public Node(K key,V value,Node next){
this.key=key;
this.value=value;
this.next=next;
}
public Node(K key,V value){
this(key,value,null);
}
public Node(){
this(null,null,null);
}
public Node(K key){
this(key,null,null);
}
@Override
public String toString() {
return key.toString()+value.toString();
}
}
private Node dummyHead;
private int size;
public LinkedListMap(){
dummyHead=new Node();
size=0;
}
//辅助函数,遍历一遍
private Node getNode(K key){
//遍历整个链表的内容
Node cur=dummyHead.next;
while(cur!=null){
if(cur.key.equals(key))
return cur;
cur=cur.next;
}
return null;
}
@Override
public void add(K key, V value) {
//键值唯一
Node node=getNode(key);
if(node==null){
//添加到链表头
Node newNode=new Node(key,value);
node.next=dummyHead.next;
dummyHead.next=node;
size++;
}
else//更新value,或抛出异常,或忽略
node.value=value;
}
@Override
public void set(K key, V newValue) {
Node node=getNode(key);
if(node==null)
throw new IllegalArgumentException(key+"d e");
node.value=newValue;
}
@Override
public V remove(K key) {
Node prev=dummyHead;
while(prev.next!=null){
if(prev.next.key.equals(key))
break;
prev=prev.next;
}
//可能没有遍历到
if(prev.next!=null){
Node delNode=prev.next;
prev.next=delNode.next;
delNode.next=null;
size--;
return delNode.value;
}
return null;
}
@Override
public boolean contains(K key) {
return getNode(key)!=null;
}
@Override
public V get(K key) {
Node node=getNode(key);
return node==null? null:getNode(key).value;
}
@Override
public int getSize() {
return size;
}
@Override
public boolean isEmpty() {
return size==0;
}
}
基于二分搜索树实现的映射
BSTMap中的K相当于BST中的元素,需要具有可比较性
public class BstMap<K extends Comparable<K>,V> implements Map<K,V>{
private class Node{
public Node left;
public Node right;
public K key;
public V value;
public Node(K key,V value){
this.key=key;
this.value=value;
this.left=null;
this.right=null;
}
}
private Node root;
private int size;
public BstMap(){
root=null;
size=0;
}
//向二分搜索树中添加新的元素(key value)
@Override
public void add(K key, V value) {
root=add(root,key,value);
}
//向以node为根的二分搜索树中插入元素(key value),递归算法
//返回插入新节点后二分搜索树的根
private Node add(Node node,K key,V value){
if(node==null){
size++;
return new Node(key,value);}
if(key.compareTo(node.key)<0)
node.left=add(node.left,key,value);
else if(key.compareTo(node.key)>0)
node.right=add(node.right,key,value);
else//在bst中当添加的值已经存在时,直接忽略
//而在映射中当键值存在时,用户的value与已有的value不同,需要进行修改,与链表映射类似
node.value=value;
return node;
}
//辅助函数,遍历一遍
//返回以node为根节点的二分搜索树中,key所在的节点
private Node getNode(Node node,K key){
if(node==null)
return null;
if(key.compareTo(node.key)<0)
return getNode(node.left,key);
else if(key.compareTo(node.key)>0)
return getNode(node.right,key);
else
return node;
}
@Override
public V remove(K key) {
Node res=getNode(root,key);
if(res==null)
return null;
root=remove(root,key);
return res.value;
}
//删除以node为根的二分搜索树中键为key的节点,返回删除节点后新的二分搜索树的根,添加和删除操作都需要遍历树,需要进行大小比较
private Node remove(Node node,K key) {
if (node == null)
return node;
if (key.compareTo(node.key) == 0) {
if (node.left == null) {
Node right = node.right;
node.right = null;
size--;
return right;
}
if (node.right == null) {
Node left = node.left;
node.left = null;
size--;
return left;
}
Node newRoot = minimun(node.right);
newRoot.right = removeMin(node.right);
node.right = node.left = null;
return newRoot;
} else if (key.compareTo(node.key) < 0) {
node.left = remove(node.left, key);
return node;
} else {
node.right = remove(node.right, key);
return node;
}
}
public K minimun(){
return minimun(root).key;
}
//返回以node为根节点的二分搜索树中的最小值所在节点
private Node minimun(Node node){
if(node.left==null)
return node;
return minimun(node.left);
}
public K removeMin(){
K res=minimun();
root=removeMin(root);
return res;
}
//返回以node为根节点的二分搜索树删除最小值所在节点后的新的二分搜索树的根
private Node removeMin(Node node){
if(node.left==null) {
Node right = node.right;
node.right=null;
size--;
return right;
}
node.left=removeMin(node.left);
return node;
}
@Override
public boolean contains(K key) {
Node res=getNode(root,key);
return res!=null;
}
@Override
public V get(K key) {
Node node=getNode(root,key);
return node==null ?null:node.value;
}
@Override
public void set(K key, V value) {
Node node=getNode(root,key);
if(node==null)
throw new IllegalArgumentException("WRONG");
node.value=value;
}
@Override
public int getSize() {
return size;
}
@Override
public boolean isEmpty() {
return size==0;
}
}
基于链表实现的映射的性能远差于基于二分搜索树实现的映射性能
映射的时间复杂度分析
有序映射和无序映射
集合与映射的关系
(1)映射中的键充当了在集合中元素相对应的位置,只不过每一个键还跟了一个value
(2)实现集合和映射时,都可以既使用bst又使用链表来实现
(3)对于映射来说,它本身就是一个集合,只不过是一个键的集合,而且每一个键还携带了一个value,只不过bst和链表都只能存储一个变量,在实现map时重写了方法,实质的逻辑和在set中没有太大区别,因此可以基于集合的实现来实现映射或根据映射的实现来实现集合
(4)当有一个集合的底层实现时,定义集合中的元素是键值对,并且定义在比较时是以key进行比较的,而不去管value的值;映射的底层实现比集合复杂,可以根据映射的实现包装出集合,对于不管什么key,对应的value都是空的,只考虑key时,映射就是一个key的集合,get和set方法也就没有意义了(key不能重复,满足set中元素不能重复的要求)
在实现映射的标准库操作中,使用put同一add和set的操作,map.put(n,1) map.put(n,map.get(n)+1)
注意判断交集的方法