从一个值对应一个值的这种关系就叫映射
首先关于映射,我们主要关心的是一一对应的关系,就比如初高中所学的函数,在生活中也比较常见
一般都是一对一对出现的,为什么会出现这种数据结构呢,以为很多时候我们要根据键(key)来查找值(Value)
首先定义Map的接口(老规矩,依旧采用泛型做为底层实现)
public interface Map<K, V> { void add(K key, V value); /** * 通过键删除这个元素,并返回值Value * * @param key * @return */ V remove(K key); boolean contains(K key); V get(K key); /** * 给定一个键与新的值修改键在map中对应的值 * @param key * @param value */ void set(K key, V value); int getSize(); boolean isEmpty(); }
Map可以基于多种底层实现,第一种基于二分搜索树实现一个Map
那第一步肯定是定义一个内部类来存放键与值
private class Node { K key; V value; Node left, right; public Node(K key, V value) { this.key = key; this.value = value; left = null; right = null; } @Override public String toString() { return key.toString() + " : " + value.toString(); } }
要注意的是在这里要对‘K’做一个限制,因为要存储在二分搜索树中,所以必须有一个比较的标准
所以类的定义这么写即可>
public class BSTMap<K extends Comparable<K>, V> implements Map<K, V>
与之前实现二分搜索树中的逻辑相似,只是做出了很小的修改,当用户添加一个新键值对的时候,如果key已经存在,用
新的value覆盖掉之前的value
@Override public void add(K key, V value) { root = add(root, key, value); } /** * 向以node为根的二分搜索树添加元素(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 /*key.compareTo(node.key) == 0*/ { //如果添加一样的键,就对它的值重新赋值 node.value = value; } return node; }
接下来实现一个辅助函数,可以在编写下面的方法中更容易实现>
/** * 返回以node为根的树中,key所在的结点 * * @param node * @param key * @return */ private Node getNode(Node node, K key) { if (node == null) { return null; } if (key.equals(node.key)) { return node; } else if (key.compareTo(node.key) < 0) { return getNode(node.left, key); } else /*key.compareTo(node.key)*/ { return getNode(node.right, key); } }
接下来就可以很容易的实现contains ,get 和 set 方法
@Override public boolean contains(K key) { return getNode(root, key) != null; } /** * 通过key获取相应的value * @param key * @return */ @Override public V get(K key) { Node node = getNode(root, key); return node == null ? null : node.value; } /** * 置存在的key对应的value为新的value * @param key * @param newValue */ @Override public void set(K key, V newValue) { Node node = getNode(root, key); if (node == null) { throw new IllegalArgumentException(key + " doesn't exist "); } else { node.value = newValue; } }
最后当然还要实现删除任意键值对的函数啦
在这个之前。同样需要几个辅助函数,这里与之前实现二分搜索树中的删除任意节点逻辑相同
/** * 返回以node为结点的树下的最小值 * * @param node * @return */ private Node minimum(Node node) { if (node.left == null) { return node; } return minimum(node.left); } /** * 删除以node为根的树下的最小结点,并返回根节点 * * @param node * @return */ private Node removeMIn(Node node) { //node的左孩子为空的时候就代表着node就是此树中最小的节点 //所以在这里只需要将他的右孩子作为新的根返回至上一层即可 if (node.left == null) { Node rightNode = node.right; node.right = null; size--; return rightNode; } //最小的节点肯定是沿着左孩子一直到底 node.left = removeMIn(node.left); return node; }
有了这两个辅助函数,实现删除任意值就很容易了>
/** * 从二分搜索树中删除key对应的节点 * * @param key * @return */ @Override public V remove(K key) { Node node = getNode(root, key); if (node != null) { root = remove(root, key); return node.value; } return null; } /** * 删除掉以node为根的二分搜索树中键为key对应的节点 * 并返回删除节点后新的二分搜索树的根 * * @param node * @param key * @return */ private Node remove(Node node, K key) { if (node == null) { return null; } if (key.compareTo(node.key) < 0) { node.left = remove(node.left, key); return node; } else if (key.compareTo(node.key) > 0) { node.right = remove(node.right, key); return node; } else { //待删除的节点的左子树为空的情况 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; } //左右子树都不为空 Node successor = minimum(node.right); successor.right = removeMIn(node.right); successor.left = node.left; node.left = node.right = null; return successor; } }
这里有一个最典型的应用就是统计词频>
@Test public void testBSTMap() { double startTime = System.nanoTime(); System.out.println("Pride and Perjudice"); //list的作用就是将这本书的所有词汇全部放置到集合中 ArrayList<String> words = new ArrayList<>(); //如果读取文件正常,返回true,接着进行下面的统计工作 if (FileOperation.readFile("src/pride-and-prejudice.txt", words)) { System.out.println("Total word : " + words.size()); BSTMap<String, Integer> map = new BSTMap<String, Integer>(); for (String word : words) { //如果map中存在这个单词,将键对应的值进行+1操作 if (map.contains(word)) { map.set(word, map.get(word) + 1); } else /*如果不存在这个单词,就将这个单词添加进去*/{ map.add(word, 1); } } System.out.println("The different words : " + map.getSize()); System.out.println(map.toString()); System.out.println("Frequency of pride : " + map.get("pride")); System.out.println("Frequency of prejudice : " + map.get("prejudice")); double endTime = System.nanoTime(); System.out.println((endTime - startTime) / 1000000000.0 + "s"); } }
统计结果如下所示,在十二万数据的情况下,二分搜索树的性能表现得还是相当优异的
二分搜索树的底层实现可以参照:二分搜索树底层实现
整个Map的代码也放在这里
import java.util.LinkedList;
import java.util.Queue;
/**
* 以二分搜索树为底层
* 实现映射
*
* @param <K>
* @param <V>
*/
public class BSTMap<K extends Comparable<K>, V> implements Map<K, V> {
private class Node {
K key;
V value;
Node left, right;
public Node(K key, V value) {
this.key = key;
this.value = value;
left = null;
right = null;
}
@Override
public String toString() {
return key.toString() + " : " + value.toString();
}
}
private Node root;
private int size;
@Override
public int getSize() {
return size;
}
@Override
public boolean isEmpty() {
return size == 0;
}
@Override
public void add(K key, V value) {
root = add(root, key, value);
}
/**
* 向以node为根的二分搜索树添加元素(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 /*key.compareTo(node.key) == 0*/ { //如果添加一样的键,就对它的值重新赋值
node.value = value;
}
return node;
}
@Override
public boolean contains(K key) {
return getNode(root, key) != null;
}
/**
* 通过key获取相应的value
*
* @param key
* @return
*/
@Override
public V get(K key) {
Node node = getNode(root, key);
return node == null ? null : node.value;
}
/**
* 置存在的key对应的value为新的value
*
* @param key
* @param newValue
*/
@Override
public void set(K key, V newValue) {
Node node = getNode(root, key);
if (node == null) {
throw new IllegalArgumentException(key + " doesn't exist ");
} else {
node.value = newValue;
}
}
/**
* 从二分搜索树中删除key对应的节点
*
* @param key
* @return
*/
@Override
public V remove(K key) {
Node node = getNode(root, key);
if (node != null) {
root = remove(root, key);
return node.value;
}
return null;
}
/**
* 删除掉以node为根的二分搜索树中键为key对应的节点
* 并返回删除节点后新的二分搜索树的根
*
* @param node
* @param key
* @return
*/
private Node remove(Node node, K key) {
if (node == null) {
return null;
}
if (key.compareTo(node.key) < 0) {
node.left = remove(node.left, key);
return node;
} else if (key.compareTo(node.key) > 0) {
node.right = remove(node.right, key);
return node;
} else {
//待删除的节点的左子树为空的情况
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;
}
//左右子树都不为空
Node successor = minimum(node.right);
successor.right = removeMIn(node.right);
successor.left = node.left;
node.left = node.right = null;
return successor;
}
}
/**
* 返回以node为结点的树下的最小值
*
* @param node
* @return
*/
private Node minimum(Node node) {
if (node.left == null) {
return node;
}
return minimum(node.left);
}
/**
* 删除以node为根的树下的最小结点,并返回根节点
*
* @param node
* @return
*/
private Node removeMIn(Node node) {
if (node.left == null) {
/*
node的左孩子为空的时候就代表着node就是此树中最小的节点
所以在这里只需要将他的右孩子作为新的根返回至上一层即可
*/
Node rightNode = node.right;
node.right = null;
size--;
return rightNode;
}
//最小的节点肯定是沿着左孩子一直到底
node.left = removeMIn(node.left);
return node;
}
/**
* 返回以node为根的树中,key所在的结点
*
* @param node
* @param key
* @return
*/
private Node getNode(Node node, K key) {
if (node == null) {
return null;
}
if (key.equals(node.key)) {
return node;
} else if (key.compareTo(node.key) < 0) {
return getNode(node.left, key);
} else /*key.compareTo(node.key)*/ {
return getNode(node.right, key);
}
}
@Override
public String toString() {
StringBuilder res = new StringBuilder();
Queue<Node> queue = new LinkedList<>();
queue.add(root);
while (!queue.isEmpty()) {
Node node = queue.poll();
res.append(node + "\n");
if (node.left != null) {
queue.add(node.left);
}
if (node.right != null) {
queue.add(node.right);
}
}
return res.toString();
}
}
在这里还有基于链表的Map的实现> 他的性能要远差于基于二分搜索树的Map
public class LinkedListMap<K, V> implements Map<K, V> {
private class Node {
public K key;
public V value;
public Node next;
public Node(K key, V value, Node next) {
this.key = key;
this.value = value;
this.next = next;
}
public Node(K key, V value) {
this(key, value, null);
}
public Node() {
this(null, null, null);
}
@Override
public String toString() {
return key.toString() + " : " + value.toString();
}
}
private Node dummyHead;
private int size;
public LinkedListMap() {
dummyHead = new Node();
size = 0;
}
@Override
public void add(K key, V value) {
Node node = getNode(key);
if (node == null) {
dummyHead.next = new Node(key, value, dummyHead.next);
size++;
} else {
node.value = value;
}
}
@Override
public V remove(K key) {
Node prev = dummyHead;
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 node = getNode(key);
return node == null ? null : node.value;
}
@Override
public void set(K key, V newValue) {
Node node = getNode(key);
if (node == null) {
throw new IllegalArgumentException(key + " doesn't exist ! ");
} else {
node.value = newValue;
}
}
@Override
public int getSize() {
return size;
}
@Override
public boolean isEmpty() {
return size == 0;
}
@Override
public String toString() {
StringBuilder res = new StringBuilder();
Node cur = dummyHead.next;
while (cur != null) {
res.append(cur + "\n");
cur = cur.next;
}
return res.toString();
}
private Node getNode(K key) {
Node cur = dummyHead.next;
while (cur != null) {
if (cur.key.equals(key)) {
return cur;
}
cur = cur.next;
}
return null;
}
}