對映(Map)
對映是什麼?
如果使用白話來說的話, 就是更具特定唯一的資訊來找到對應的實體。
比如說我們查字典, 要查詢的字能找到對應的解釋。
比如我們的有身份證id就能查到對應的人資訊。
比如我們有快遞id就能知道當前快遞在什麼位置
等等, 都是通過對映的關係來進行實現。
對映結構特點
- 以K,V鍵值對儲存的資料
- 根據K能快速的尋找到V
對映介面的設計
我們優先建立一個父類介面, 並設計兩個子類一個是連結串列來實現, 一個是二分搜尋樹來實現。
public interface Map<K, V> {
// 新增資料
void add(K key, V value);
// 刪除資料
V remove(K key);
// 判斷K是否存在
boolean contains(K key);
// 更具K獲取value
V get(K key);
// 設定新的value
void set(K key, V newValue);
int getSize();
boolean isEmpty();
}
複製程式碼
基於連結串列實現對映資料結構
學習集合的時候, 我們也使用了連結串列進行集合的設計, 但是在設計對映物件的時候可能就無法滿足了, 畢竟要K和V一對。 所以我們需要重新構建一下Node物件。並重新設計add和remove等方法。
public class LinkedListMap<K, V> implements Map<K, V> {
// 之前寫的連結串列只有一個元素是無法滿足map的結構, 所以重新定義一下Node
private class Node {
public K key;
public V value;
public Node next;
public Node() {
this(null, null, null);
}
public Node(K key, V value, Node next) {
this.key = key;
this.value = value;
this.next = next;
}
@Override
public String toString() {
return key + " : " + value;
}
}
private int size ;
private Node dummyHead ; // 虛擬頭結點
public LinkedListMap() {
dummyHead = new Node();
size = 0;
}
/**
* 更具K獲取到具體的Node物件
* @param key
* @return
*/
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 cur = getNode(key);
if (cur == null) {
dummyHead.next = new Node(key, value, dummyHead.next);
size ++ ;
} else {
// 已經存在了, 這裡的話不丟擲異常但是給個提示吧
System.out.println("新增的key="+key+"已存在, 給你更新啦!");
cur.value = value;
}
}
@Override
public void set(K key, V newValue) {
Node cur = getNode(key);
if (cur == null) {
throw new IllegalArgumentException("當前key="+key+"不存在, 請檢查是否拼寫錯誤");
}
cur.value = newValue;
}
@Override
public V remove(K key) {
Node prev = dummyHead.next;
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 cur = getNode(key);
return cur == null ? null : cur.value;
}
@Override
public int getSize() {
return size;
}
@Override
public boolean isEmpty() {
return size == 0;
}
}
複製程式碼
接下來測試一下我們基於連結串列的對映資料結構把。
public static void main(String[] args) {
String[] words = {"A", "B", "C", "D", "E", "A", "A", "A", "B", "B", "C", "C", "F", "F", "F", "K",};
Map<String, Integer> map = new LinkedListMap<>();
for (String word : words) {
if (map.contains(word)) {
Integer v = map.get(word) + 1;
map.set(word, v);
} else {
map.add(word, 1);
}
}
System.out.println("總共: " + map.getSize());
System.out.println(map.get("A"));
System.out.println(map.get("B"));
System.out.println(map.get("C"));
System.out.println(map.get("D"));
System.out.println(map.get("E"));
System.out.println(map.get("F"));
System.out.println(map.get("K"));
}
複製程式碼
基於二分搜尋樹實現對映資料結構
利用二分搜尋樹的話, 之前寫的也是不能使用的。所以我們需要重新構建一個Node。
public class BSTMap<K extends Comparable<K>, V> implements Map<K, V> {
private class Node {
public K key ;
public V value ;
public Node left ;
public Node right ;
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() {
this.root = null;
this.size = 0;
}
private Node getNode(Node node , K key) {
if (node == null)
return null;
if (node.key.compareTo(key) == 0)
return node;
else if (node.key.compareTo(key) < 0)
return getNode(node.left, key);
else
return getNode(node.right, key);
}
@Override
public void add(K key, V value) {
root = add(root, key, value);
}
private Node add(Node node, K key, V value) {
if (node == null) {
Node n = new Node(key, value);
size ++ ;
return n;
}
if (node.key.compareTo(key) > 0) {
node.right = add(node.right, key, value);
} else if (node.key.compareTo(key) < 0) {
node.left = add(node.left, key, value);
} else {
// 如果存在key則更新資料
node.value = value;
}
return node;
}
private Node minimum(Node node) {
if (node.left == null)
return node ;
return minimum(node.left);
}
private Node removeMin(Node node) {
if (node.left == null) {
Node rightNode = node.right;
node.right = null;
size --;
return rightNode;
}
node.left = removeMin(node.left);
return node;
}
private Node remove(Node node, K key) {
if (node.key.compareTo(key) == 0) {
if(node.left == null) {
Node rightNode = node.right;
node.right = null;
size --;
return rightNode;
}
// 第二種情況: 刪除只有右孩子的節點(簡單)
if (node.right == null) {
Node leftNode = node.left;
node.left = null;
size --;
return leftNode;
}
/***
* 待刪除節點左右子樹都不為空的情況
*/
// 1. 找到比待刪除節點大的最小節點, 即待刪除節點右子樹中最小的節點(找到後繼節點)
// 2. 用後繼節點頂替待刪除節點的位置
Node succeed = minimum(node.right);
// 返回刪除最小值後的一個新樹, 最小值已經被我們記錄住了, 然後設定左右子樹
succeed.right = removeMin(node.right);
succeed.left = node.left;
// 這裡之所以沒有size--, 是因為removeMin方法已經做了
node.left = node.right = null;
return succeed;
} else if (node.key.compareTo(key) > 0) {
node.right = remove(node.right, key);
return node;
} else {
node.left = remove(node.left, key);
return node;
}
}
@Override
public V remove(K key) {
Node node = getNode(root, key);
if (node != null) {
root = remove(root, key);
return node.value;
}
return null;
}
@Override
public void set(K key, V newValue) {
Node node = getNode(root, key);
if (node == null)
throw new IllegalArgumentException("key=" + key + " 不存在, 更新失敗!");
node.value = newValue;
}
@Override
public boolean contains(K key) {
return getNode(root, key) != null;
}
@Override
public V get(K key) {
Node node = getNode(root, key);
return node == null ? null : node.value;
}
@Override
public int getSize() {
return size;
}
@Override
public boolean isEmpty() {
return size == 0;
}
}
複製程式碼
測試一下
public static void main(String[] args) {
String[] words = {"A", "B", "C", "D", "E", "A", "A", "A", "B", "B", "C", "C", "F", "F", "F", "K",};
Map<String, Integer> map = new BSTMap<>();
for (String word : words) {
if (map.contains(word)) {
Integer v = map.get(word) + 1;
map.set(word, v);
} else {
map.add(word, 1);
}
}
System.out.println("總共: " + map.getSize());
System.out.println(map.get("A"));
System.out.println(map.get("B"));
System.out.println(map.get("C"));
System.out.println(map.get("D"));
System.out.println(map.get("E"));
System.out.println(map.get("F"));
System.out.println(map.get("K"));
Integer v = map.remove("A");
System.out.println("A " + v);
System.out.println(map.get("A"));
System.out.println(map.get("K"));
}
複製程式碼