Map61B定义Map需要实现的方法
import java.util.Set;
public interface Map61B<K, V> extends Iterable<K> {
/** Removes all of the mappings from this map. */
void clear();
/* Returns true if this map contains a mapping for the specified key. */
boolean containsKey(K key);
/* Returns the value to which the specified key is mapped, or null if this
* map contains no mapping for the key.
*/
V get(K key);
/* Returns the number of key-value mappings in this map. */
int size();
/* Associates the specified value with the specified key in this map. */
void put(K key, V value);
/* Returns a Set view of the keys contained in this map. Not required for Lab 7.
* If you don't implement this, throw an UnsupportedOperationException. */
Set<K> keySet();
/* Removes the mapping for the specified key from this map if present. */
V remove(K key);
/* Removes the entry for the specified key only if it is currently mapped to
* the specified value. */
V remove(K key, V value);
}
比较 ULLMap 和 BSTMap 实现的优劣
在实现映射(Map)数据结构时,选择合适的底层数据结构会显著影响操作的性能和复杂性。本文将比较两种不同的映射实现:ULLMap
,其底层使用未排序的链表;BSTMap
,其底层使用二叉搜索树(BST)。这两种实现都用于处理键值对,但在效率和复杂性方面提供了不同的权衡。
ULLMap:基于未排序链表的 Map
ULLMap
实现使用未排序的链表来存储键值对。该结构具有以下特点:
关键操作:
- 获取(get):由于需要遍历链表查找键,其时间复杂度为线性时间 (O(n))。
- 插入(put):如果键已经存在,更新值的时间复杂度为线性时间 (O(n)),否则在链表头插入的时间复杂度为常数时间 (O(1))。
- 包含键(containsKey):由于需要遍历链表以检查键是否存在,其时间复杂度为线性时间 (O(n))。
- 删除(remove):遍历链表找到与给定键匹配的条目并将其删除,时间复杂度为线性时间 (O(n))。
- 获取大小(size):返回存储的键值对数量,时间复杂度为常数时间 (O(1))。
- 清空(clear):将链表头指针设为 null,清空链表,时间复杂度为常数时间 (O(1))。
优点:
- 实现简单,适用于键值对数量较少的情况。
- 内存开销较小,因为不需要额外的结构来维持排序。
- 插入新键值对时效率高。
缺点:
- 对于大规模数据,操作效率较低。
- 查找、插入和删除的时间复杂度较高。
UULMap实现:
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
/** A data structure that uses a linked list to store pairs of keys and values.
* Any key must appear at most once in the dictionary, but values may appear multiple
* times. Key operations are get(key), put(key, value), and contains(key) methods. The value
* associated to a key is the value in the last call to put with that key. */
public class ULLMap<K, V> implements Map61B<K, V> {
int size = 0;
/** Returns the value corresponding to KEY or null if no such value exists. */
public V get(K key) {
if (list == null) {
return null;
}
Entry lookup = list.get(key);
if (lookup == null) {
return null;
}
return lookup.val;
}
@Override
public int size() {
return size;
}
/** Removes all of the mappings from this map. */
@Override
public void clear() {
size = 0;
list = null;
}
/** Inserts the key-value pair of KEY and VALUE into this dictionary,
* replacing the previous value associated to KEY, if any. */
public void put(K key, V val) {
if (list != null) {
Entry lookup = list.get(key);
if (lookup == null) {
list = new Entry(key, val, list);
} else {
lookup.val = val;
}
} else {
list = new Entry(key, val, list);
size = size + 1;
}
}
/** Returns true if and only if this dictionary contains KEY as the
* key of some key-value pair. */
public boolean containsKey(K key) {
if (list == null) {
return false;
}
return list.get(key) != null;
}
@Override
public Iterator<K> iterator() {
return new ULLMapIter();
}
/** Keys and values are stored in a linked list of Entry objects.
* This variable stores the first pair in this linked list. */
private Entry list;
/** Represents one node in the linked list that stores the key-value pairs
* in the dictionary. */
private class Entry {
/** Stores KEY as the key in this key-value pair, VAL as the value, and
* NEXT as the next node in the linked list. */
Entry(K k, V v, Entry n) {
key = k;
val = v;
next = n;
}
/** Returns the Entry in this linked list of key-value pairs whose key
* is equal to KEY, or null if no such Entry exists. */
Entry get(K k) {
if (k != null && k.equals(key)) {
return this;
}
if (next == null) {
return null;
}
return next.get(key);
}
/** Stores the key of the key-value pair of this node in the list. */
K key;
/** Stores the value of the key-value pair of this node in the list. */
V val;
/** Stores the next Entry in the linked list. */
Entry next;
}
/** An iterator that iterates over the keys of the dictionary. */
private class ULLMapIter implements Iterator<K> {
/** Create a new ULLMapIter by setting cur to the first node in the
* linked list that stores the key-value pairs. */
public ULLMapIter() {
cur = list;
}
@Override
public boolean hasNext() {
return cur != null;
}
@Override
public K next() {
K ret = cur.key;
cur = cur.next;
return ret;
}
/** Stores the current key-value pair. */
private Entry cur;
}
@Override
public V remove(K key) {
if (list == null) {
return null;
}
if (list.key.equals(key)) {
V val = list.val;
list = list.next;
size -= 1;
return val;
}
Entry prev = list;
Entry curr = list.next;
while (curr != null) {
if (curr.key.equals(key)) {
V val = curr.val;
prev.next = curr.next;
size -= 1;
return val;
}
prev = curr;
curr = curr.next;
}
return null;
}
@Override
public V remove(K key, V value) {
if (list == null) {
return null;
}
if (list.key.equals(key) && list.val.equals(value)) {
V val = list.val;
list = list.next;
size -= 1;
return val;
}
Entry prev = list;
Entry curr = list.next;
while (curr != null) {
if (curr.key.equals(key) && curr.val.equals(value)) {
V val = curr.val;
prev.next = curr.next;
size -= 1;
return val;
}
prev = curr;
curr = curr.next;
}
return null;
}
@Override
public Set<K> keySet() {
Set<K> keys = new HashSet<>();
Entry curr = list;
while (curr != null) {
keys.add(curr.key);
curr = curr.next;
}
return keys;
}
}
BSTMap:基于二叉搜索树的 Map
BSTMap
实现使用二叉搜索树来存储键值对。该结构具有以下特点:
关键操作:
- 获取(get):使用二叉搜索树的性质,根据键值进行有序查找,平均时间复杂度为对数时间 (O(\log n)),最坏情况下(树不平衡)为线性时间 (O(n))。
- 插入(put):根据键值进行有序插入或更新,平均时间复杂度为对数时间 (O(\log n)),最坏情况下为线性时间 (O(n))。
- 包含键(containsKey):根据键值在二叉搜索树中进行有序查找,平均时间复杂度为对数时间 (O(\log n))。
- 删除(remove):在二叉搜索树中找到与给定键匹配的节点并删除,平均时间复杂度为对数时间 (O(\log n)),最坏情况下为线性时间 (O(n))。
- 获取大小(size):返回二叉搜索树中的节点数量,时间复杂度为常数时间 (O(1))。
- 清空(clear):将树的根节点设为 null,清空二叉搜索树,时间复杂度为常数时间 (O(1))。
优点:
- 对于大规模数据,操作效率较高。
- 在理想情况下,查找、插入和删除的时间复杂度较低。
缺点:
- 需要维护树的平衡,可能引入额外的复杂度(如使用自平衡树结构)。
- 实现复杂度较高,相较于链表,内存开销略大。
BSTMap实现:
import java.util.*;
public class BSTMap<K extends Comparable<K>,V> implements Map61B<K,V> {
int size = 0;
private BST tree;
private class BST {
private Node root;
private class Node {
private K key; // sorted by key
private V val; // associated data
private Node left, right; // left and right subtrees
private int size; // number of nodes in subtree
public Node(K key, V val, int size) {
this.key = key;
this.val = val;
this.size = size;
}
}
public BST() {
}
/**
* Returns the value associated with the given key.
*
* @param key the key
* @return the value associated with the given key if the key is in the symbol table
* and {@code null} if the key is not in the symbol table
* @throws IllegalArgumentException if {@code key} is {@code null}
*/
public V get(K key) {
return get(root, key);
}
private V get(Node x, K key) {
if (key == null) throw new IllegalArgumentException("calls get() with a null key");
if (x == null) return null;
int cmp = key.compareTo(x.key);
if (cmp < 0) return get(x.left, key);
else if (cmp > 0) return get(x.right, key);
else return x.val;
}
public void put(K key, V val) {
if (key == null) throw new IllegalArgumentException("calls put() with a null key");
// if val not contain null
// if (val == null) {
// delete(key);
// return;
// }
root = put(root, key, val);
}
private Node put(Node x, K key, V val) {
if (x == null) return new Node(key, val, 1);
int cmp = key.compareTo(x.key);
if (cmp < 0) x.left = put(x.left, key, val);
else if (cmp > 0) x.right = put(x.right, key, val);
else x.val = val;
x.size = 1 + size(x.left) + size(x.right);
return x;
}
/**
* Returns the number of key-value pairs in this symbol table.
* @return the number of key-value pairs in this symbol table
*/
public int size() {
return size(root);
}
/** return number of key-value pairs in BST rooted at x**/
private int size(Node x) {
if (x == null) return 0;
else return x.size;
}
/**
* Removes the specified key and its associated value from this symbol table
* (if the key is in this symbol table).
*
* @param key the key
* @throws IllegalArgumentException if {@code key} is {@code null}
*/
public void delete(K key) {
if (key == null) throw new IllegalArgumentException("calls delete() with a null key");
root = delete(root, key);
}
private Node delete(Node x, K key) {
if (x == null) return null;
int cmp = key.compareTo(x.key);
if (cmp < 0) x.left = delete(x.left, key);
else if (cmp > 0) x.right = delete(x.right, key);
else {
if (x.right == null) return x.left;
if (x.left == null) return x.right;
Node t = x;
x = min(t.right);
x.right = deleteMin(t.right);
x.left = t.left;
}
x.size = size(x.left) + size(x.right) + 1;
return x;
}
/**
* Removes the smallest key and associated value from the symbol table.
*
* @throws NoSuchElementException if the symbol table is empty
*/
public void deleteMin() {
if (isEmpty()) throw new NoSuchElementException("Symbol table underflow");
root = deleteMin(root);
}
private Node deleteMin(Node x) {
if (x.left == null) return x.right;
x.left = deleteMin(x.left);
x.size = size(x.left) + size(x.right) + 1;
return x;
}
/**
* Returns true if this symbol table is empty.
* @return {@code true} if this symbol table is empty; {@code false} otherwise
*/
public boolean isEmpty() {
return size() == 0;
}
/**
* Returns the smallest key in the symbol table.
*
* @return the smallest key in the symbol table
* @throws NoSuchElementException if the symbol table is empty
*/
public K min() {
if (isEmpty()) throw new NoSuchElementException("calls min() with empty symbol table");
return min(root).key;
}
private Node min(Node x) {
if (x.left == null) return x;
else return min(x.left);
}
}
@Override
public Iterator<K> iterator() {
return new BSTIterator();
}
/** Removes all of the mappings from this map. */
@Override
public void clear() {
size = 0;
tree = null;
}
/** Returns true if this map contains a mapping for the specified key. */
@Override
public boolean containsKey(K key) {
if (tree == null) return false;
return get(key) != null;
}
/** Returns the value corresponding to KEY or null if no such value exists. */
@Override
public V get(K key) {
if (tree == null) {
return null;
}
return tree.get(key);
}
/** Returns the number of key-value mappings in this map. */
@Override
public int size() {
return size;
}
/** Associates the specified value with the specified key in this map. */
@Override
public void put(K key, V value) {
if (tree != null) {
if (containsKey(key)) {
tree.put(key, value);
} else {
tree.put(key, value);
size = size + 1;
}
}
else {
tree = new BST();
tree.put(key, value);
size = size + 1;
}
}
/** Removes the mapping for the specified key from this map if present.*/
@Override
public V remove(K key) {
if (containsKey(key)) {
V v = get(key);
tree.delete(key);
size = size - 1;
return v;
}
return null;
}
/* Removes the entry for the specified key only if it is currently mapped to
* the specified value.*/
@Override
public V remove(K key, V value) {
if (containsKey(key)) {
V v = get(key);
if (v.equals(value)) {
tree.delete(key);
size = size - 1;
return v;
}
}
return null;
}
@Override
public Set<K> keySet() {
Set set = new HashSet<K>();
for (K node: this) {
set.add(node);
}
return set;
}
void printInOrder() {
if (tree == null) throw new IllegalArgumentException("calls printInOrder() with a null Map");
System.out.println("{");
printInOrder(tree.root);
System.out.println("}");
}
/**Print out BSTMap in increasing order of Key*/
void printInOrder(BST.Node node) {
if (node == null) return;
printInOrder(node.left);
System.out.println("key"+node.key+":"+"value"+node.val);
printInOrder(node.right);
}
private class BSTIterator implements Iterator<K> {
private Stack<BST.Node> stack = new Stack<>();
public BSTIterator() {
pushLeft(tree.root);
}
private void pushLeft(BST.Node x) {
while (x != null) {
stack.push(x);
x = x.left;
}
}
@Override
public boolean hasNext() {
return !stack.isEmpty();
}
@Override
public K next() {
if (!hasNext()) throw new NoSuchElementException();
BST.Node node = stack.pop();
K key = node.key;
if (node.right != null) {
pushLeft(node.right);
}
return key;
}
}
}
方法优劣性对比
get 方法对比:
-
ULLMap:
- 时间复杂度:(O(n))
- 优点:实现简单
- 缺点:效率低
-
BSTMap:
- 时间复杂度:平均 (O(\log n)),最坏 (O(n))
- 优点:在平衡树情况下效率高
- 缺点:实现复杂
put 方法对比:
-
ULLMap:
- 时间复杂度:键存在时 (O(n)),键不存在时 (O(1))
- 优点:插入新键值对时效率高
- 缺点:更新现有键值对时效率低
-
BSTMap:
- 时间复杂度:平均 (O(\log n)),最坏 (O(n))
- 优点:在平衡树情况下效率高
- 缺点:实现复杂
containsKey 方法对比:
-
ULLMap:
- 时间复杂度:(O(n))
- 优点:实现简单
- 缺点:效率低
-
BSTMap:
- 时间复杂度:平均 (O(\log n)),最坏 (O(n))
- 优点:在平衡树情况下效率高
- 缺点:实现复杂
remove 方法对比:
-
ULLMap:
- 时间复杂度:(O(n))
- 优点:实现简单
- 缺点:效率低
-
BSTMap:
- 时间复杂度:平均 (O(\log n)),最坏 (O(n))
- 优点:在平衡树情况下效率高
- 缺点:实现复杂
size 和 clear 方法对比:
-
ULLMap:
- 时间复杂度:(O(1))
- 优点:实现简单,效率高
- 缺点:无
-
BSTMap:
- 时间复杂度:(O(1))
- 优点:实现简单,效率高
- 缺点:无
总结
总体而言,ULLMap
实现简单,适用于小规模数据集,操作效率在键值对数量较少时表现尚可。然而,随着数据规模增大,其操作性能会显著下降。而 BSTMap
尽管实现复杂,但在大规模数据集下操作效率更高,尤其在平衡树情况下,其查找、插入和删除操作的性能明显优于 ULLMap
。因此,在需要处理较大数据集或对操作效率要求较高的情况下,BSTMap
是更合适的选择。
通过对这两种不同实现的对比,我们可以更好地理解不同数据结构在不同场景下的适用性,从而在实际应用中做出更为合适的选择。