简介
设计RandomPool结构,该结构有三个功能
- insert(key):将某个key加入到该结构中
- delete(key):将某个key从该结构中删除
- getRandom():等概率随机返回该结构中的一个元素
insert,delete,getRandom方法的时间复杂度均为O(1),RandomPool结构中没有重复元素
解题思路
- 问题:如何做到等概率获取元素?如何让时间复杂度到达O(1)
- 策略:给每个元素设置一个编号,只要完成等概率获取编号,就可以解决等概率获取元素的问题;采用Hash表来存储元素,增删改查的时间复杂度都为O(1)。
- 实践:
- 使用两个HashMap,第一个HashMap实现从编号找到元素的功能,另外一个HashMap实现从元素找到编号的功能,设置一个变量来设置编号和表示容器中元素的个数。
- 编号需要保证连续性,否则无法做到等概率,插入、删除元素后,都应该保证编号的连续性。
- 插入操作很容易做到连续性,让编号自增即可。删除操作时,让最后一个元素去替代被删除元素,然后删除最后一个元素,可以保证编号的连续性。
一点点感想:为什么要用两个HashMap,有不有其他方式让删除后做到等概率,这样一个数据结构在工程中如何使用?
Java代码实现
package com.quan.test;
import java.util.HashMap;
/**
* 插入、删除、等概率获取一个数的时间复杂度都为O(1)
* Pool中没有重复元素
*
* !!! 等概率获取Pool中的一个数
*/
public class RandomPool<K> {
// Test
public static void main(String[] args) {
RandomPool<String> randomPool = new RandomPool<>();
randomPool.insertKey("a");
randomPool.insertKey("b");
randomPool.insertKey("c");
for (int i = 0; i < 10; i++) {
String randomKey = randomPool.getRandomKey();
System.out.println(randomKey);
}
System.out.println("---------------------");
randomPool.deleteKey("b");
for (int i = 0; i < 10; i++) {
String randomKey = randomPool.getRandomKey();
System.out.println(randomKey);
}
}
// 从编号找到元素
private HashMap<Integer, K> indexKeyMap;
// 从元素可以找到编号,删除元素时,需要使用这个HashMap
private HashMap<K, Integer> keyIndexMap;
// Pool中元素的个数,用来设置编号
private Integer size;
public RandomPool() {
keyIndexMap = new HashMap<>();
indexKeyMap = new HashMap<>();
size = 0;
}
// 向Pool中插入一个元素
public boolean insertKey(K key) {
// Pool中不包含该元素
if (!keyIndexMap.containsKey(key)) {
// 将元素插入到Pool中
keyIndexMap.put(key, size);
indexKeyMap.put(size++, key);
return true;
}
return false;
}
// 删除Pool中一个元素
public boolean deleteKey(K key) {
/**
* !!!删除元素后,必须保持indexKeyMap中,index的连续性,这样才可以保证等概率
* 1. 查出key对应的index值,用indexKeyMap中的最后一个条目填补空缺
* 2. 删除indexKeyMap中最后一个条目
* 3. 删除keyIndexMap中的对应条目
*/
// Pool中包含该元素
if (keyIndexMap.containsKey(key)) {
// 查出key对应的index
Integer index = keyIndexMap.get(key);
// indexKeyMap中的最后一个元素
K lastIndexKey = indexKeyMap.get(size - 1);
// 用indexKeyMap中的最后一个元素去填充删除位置的元素
indexKeyMap.put(index, lastIndexKey);
keyIndexMap.put(lastIndexKey, index);
indexKeyMap.remove(--size);
keyIndexMap.remove(key);
return true;
}
return false;
}
// 等概率随机获取Pool中一个数
public K getRandomKey() {
// Pool 为空
if (size == 0) {
return null;
}
int index = (int) (Math.random() * size);
return indexKeyMap.get(index);
}
}