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
源碼地址:可在公衆號內回覆 數據結構與算法源碼 即可獲得。