16、数据结构与算法基础:映射

摘要

映射的数据结构可以用链表、AVL 树或者红黑树中的任何一种来实现,对外实现对映射序列中元素的增、删、改和查等操作。所以通晓链表、AVL 树或者红黑树的底层实现逻辑之后,在顶层实现其他的数据存储序列都能很快的切入和掌握。

映射,英文为 Map,在有些编程语言中也叫做字典(dictionary)。

Map中存储着一对对的 key-value 键值对,每一个 key 在 Map 中是唯一的。Map 可以直接使用之前学习的链表、AVL 树和红黑树等来实现。

先定义一下 Map 的接口

public interface Map<K, V> {
   
     
  // 元素数量
	int size();
  // Map 是否是空
	boolean isEmpty();
  // 加入 key-value 键值对
	V put(K key, V value);
  // 获取为 key 的 K-V 键值对
	V get(K key);
  // 移除为 key 的 K-V键值对
	V remove(K key);
  // Map 中是否包含 key 值
	boolean containsKey(K key);
  // Map 中是否包含 value 值
	boolean containsValue(V value);
}

这里使用红黑树实现映射结构。不熟悉红黑树的可以看前几期介绍红黑树的文章,这里不再详细介绍。

在红黑树中定义的范型是 E,在 Map 中需要更改为 <K, V>。即将红黑树中的 Node<E> 都更改为 Node<K, V>

定义的接口里面,前面 6 个函数直接使用红黑树的函数来实现。最后的 containsValue(V:) 函数需要提出来单独实现。

在Map 中的每个 key 都是唯一确定的,并且在红黑树中的 K-V 键值对都是以 key 来比较调整的。所以若想看 Map 是否包含某一个 value 值,就需要去遍历红黑树来实现。

可以使用广度搜索的方式来遍历红黑树,实现广度搜索的底层逻辑就是遍历队列:

public boolean containsValue(V value) {
   
     
  if (root == null) return false;
	// 创建队列,队列的特性:先入先出(FIFO)
  Queue<Node<K, V>> queue = new LinkedList<>();
  // 先入队 root 节点
  queue.offer(root);
  // 循环条件
  while (!queue.isEmpty()) {
   
     
    // 出队节点,并比较 value 值
    Node<K, V> node = queue.poll();
    if (valEquals(value, node.value)) return true;
		
    // 分别入队左右节点
    if (node.left != null) {
   
     
      queue.offer(node.left);
    }
    if (node.right != null) {
   
     
      queue.offer(node.right);
    }
  }
  return false;
}

到这里,已经实现完 Map 的所有函数,熟悉红黑树的结构,就可以很快实现 Map 结构。

使用红黑树实现的 Map,有两个特点

1、 Key必须具备可比较性;
2、 元素的分布是有顺序的;

接下来简单提一下在 JAVA 中如何实现比较两个元素的,这里就是比较两个 key 。在 JAVA 中导入比较的标准工具库(import java.util.Comparator),然后在构造函数的地方定义可以穿入比较对象的参数:

// K 是范型
TreeMap(Comparator<K> comparator) {
   
     
  this.comparator = comparator;
}

下面就可以使用传递进来的 comparator 来实现两个 key 的比较:

// 返回的是 int 值,若 -1, e1 < e2; 0, e1 == e2; 1, e1 > e2
private int compare(K e1, K e2) {
   
     
  if (comparator != null) return comparator.compare(e1, e2);

  return ((Comparable<K>)e1).compareTo(e2);
 }

这个比较方式最大的好处就是可以在外部来定义 comparator 的比较规则。比如在外部可以这样调用:

new Comparator<Int>() {
   
     
		@Override
		public int compare(Int o1, Int o2) {
   
     
				return o2 - o1;
		}
}

上面代码就是创建一个 comparator 对象,在对象中定义比较的规则,即若 return 的值是 1,则是 o2 > o1,和上面的 k 比较恰好相反。

版权声明:本文不是「本站」原创文章,版权归原作者所有 | 原文地址: