Java底層實現Map映射——基於鏈表和二分搜索樹

Java底層實現Map映射

基於鏈表和二分搜索樹


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

源碼地址:可在公衆號內回覆 數據結構與算法源碼 即可獲得。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章