Skip to content

Files

Latest commit

531bb7f · Mar 5, 2020

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
Feb 13, 2020
Feb 13, 2020
Feb 13, 2020
Feb 13, 2020
Mar 5, 2020

Java底层实现Map映射

基于链表和二分搜索树


[TOC]

1、什么是映射

  无论是集合还是映射,都是来源于数学当中,在数学领域映射指两个元素的集之间元素相互“对应”的关系。映射也分很多种,满射,单射,一一映射等三种情况,但是在计算机领域映射只是单指一一映射。我觉得着这种关系更适合叫python中的字典

2、映射类的实现——基于链表

  同栈和队列相同,我们都是基于一些其他的数据结构来封装我们的类。所以我们需要涉及集映射的接口。由于我们之前已经封装好了链表底层,具体的函数方法可以查看LinkedList链表这篇文章。

和映射一样我们仍需要设计映射的接口函数。 程序实现:

public interface Map<K, V> {
    void add(K key, V value);
    V remove(K key);
    boolean contains(K key);
    V get(K key);
    void set(K key, V newValue);
    int getSize();
    boolean isEmpty();
}

2.1、增加元素

这里我们为了更好的获得节点信息,我们引入getNode函数。之所以这样设计是因为,在映射中添加或者删除元素,都需要先去判断键值是否存在。所以引入getNode函数保证了我们类的整洁性。

private Node getNode(K key) {
    Node cur = dummyNode.next;
    while (cur.next != 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);   //获取key所对应的节点
    if (node == null) {  //不存在节点则新建节点信息
        dummyNode.next = new Node(key, value, dummyNode.next);
        size++;
    } else  //存在则进行更改操作
        node.value = value;
}

2.2、删除元素

删除元素的过程大致分为二个步骤:找到元素然后删除元素。这里我们需要注意的是,我们并不能使用getNode这个函数,因为getNode定义到了待删除元素上,实际上我们知道,删除元素我们需要定位到待删除元素的前一个位置,所以我们这里需要做特殊处理。

程序实现:

@Override
public V remove(K key) {
    Node pre = dummyNode;
    while (pre.next != null) { //找打待删除节点的前一个节点
        if (pre.next.key.equals(key))
            break;
        pre = pre.next; 
    }
    if (pre.next != null) {
        Node delNode = pre.next;
        pre.next = delNode.next;
        delNode.next = null;
        size--;
        return delNode.value;
    }
    return null;
}

2.3、改变元素

程序实现:

这里有的小伙伴可能要问了,我们的增加函数不也能实现改变操作吗?直接add(key, newValue)不久可以吗? 实际上不能这么操作。我们需要让用户明白,如果真的执行改变操作,前提是必须元素内包含元素。如果不包含此元素,则会报错。实际上,set才是真正意义上的改变操作。

@Override
public void set(K key, V newValue) {
    Node node = getNode(key);
    if (node == null)
        throw new IllegalArgumentException(key + "doesn't exist");
    node.value = newValue;
}

2.4、查询操作

查询操作就比较简单了,直接使用我们之前写好的getNode函数即可。

程序实现:

@Override
public boolean contains(K key) {
    return getNode(key) != null;
}

@Override
public V get(K key) {
    Node node = getNode(key);
    return node == null ? null : node.value;
}

3、映射类的实现——基于BST二分搜索树

这里的BSTMap的设计同BST设计很相似,具体的可以参考我BST二分搜索树这篇文章。BST节点存储的是一个元素,BSTMap存储的多存储一个value值,就只是多存储了一个value值。

private class Node{  //内部类
    public K key;
    public V value;
    public  Node left, right;

    public Node(K key, V value){
        this.key = key;
        this.value = value;
        left = null;
        right = null;
    }
}

大家以增加元素为例。看下面的对比。

BST增加元素程序实现:

public void add(E e) {
    root =  add(root, e);
}

private Node add(Node node, E e) {
    // 终止条件
    if (node == null){
        size++;
        return new Node(e);
    }
    // 开始递归
    if (e.compareTo(node.e) < 0)
        node.left = add(node.left, e);
    else if (e.compareTo(node.e) > 0)
        node.right = add(node.right, e);

    return node;
}

BSTMap增加元素程序实现:

@Override
public void add(K key, V value) {
    add(root, 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
        node.key = key;
    return node;
}

上面的 e 对应着下面的 key 。就是加一个value。是不是超级简单。对于其他来说照着BST实现即可。我就不做多做陈述啦。具体的可以参考我BST二分搜索树这篇文章。

4、时间复杂度分析

增加元素 删除元素 查询元素
链表 O(N) O(N) O(N)
二分搜索树 O(log(N)) O(log(N)) O(log(N))

  这里链表复杂度这么大的原因就在于getNode函数。我们需要先遍历一遍是否含有此元素,在进行操作。时间浪费在了遍历的操作上。

  因此我们可以在表中看出来,二分搜索树的性能远高于链表。

最后

更多精彩内容,大家可以转到我的主页:曲怪曲怪的主页

或者关注我的微信公众号:TeaUrn

或者扫描下方二维码进行关注。里面有惊喜等你哦。

源码地址:可在公众号内回复 数据结构与算法源码 即可获得。