C++设计RandomPool结构

1. 题目要求

设计一种结构,在该结构中有如下三个功能:

  1. insert(key):将某个key加入到该结构,做到不重复加入。
  2. delete(key):将原本在结构中的某个key移除。
  3. getRandom(): 等概率随机返回结构中的任何一个key。

【要求】 Insert、delete和getRandom方法的时间复杂度都是 O ( 1 ) O(1) O(1)

2. 思路

用两个hash表

为了得到key -> index以及index -> key的对应关系,使用两个哈希表来建立两者之间的映射。
在这里插入图片描述

  • map1中存放的 key value 分别是 相应的值 和对应的插入顺序
  • map2中正好相反, key value 分别是对应的插入顺序和相应的值

具体操作:

  1. 针对insert我们可以实现二个map同步操作,一个插入(key, index),另外一个插入(index, key),然后使用size计数即可,保持同步。
  2. 针对getRandom,虽然Hash表返回的是近似等概论的,但是不是严格等概率的,所有我们利用随机数从(index, key)中得到一个key。
  3. 针对delete操作,我们确实可以直接在(key, index)进行操作,但是这样我们在使用getRandom函数之后它会产生空洞了,所以一种思路就是我们可以借助最后一行(key,index)进行赋值给需要删除的key,这样就可以消除空洞。

3. 准备

为了实现C++版本的代码,我们需要对一些常见的map, hash_map,unordered_map常见的函数使用熟悉哈。可以参考下面的几篇博客作为先导。

  1. map 学习(上)——C++中 map 的使用
  2. map 学习(下)——C++ 中的 hash_map, unordered_map
  3. c++ unordered_map 判断某个键是否存在
  4. C++11 中的 emplace
  5. cppreference.com
  6. 十一、从头到尾解析Hash表算法
  7. 哈希表(散列表)原理详解

好了上面对hash有了基本的了解,我们就可以愉快的写代码了。

4. 代码

#include <iostream>
#include <unordered_map>
#include <cstdlib>

class RandomPool {
 public:
     std::unordered_map<std::string, int> keyIndexMap;
     std::unordered_map<int, std::string> indexKeyMap;
     int size;
     RandomPool(): size(0) {}   // default constructor

     void insertKey(std::string key) {
        if (keyIndexMap.find(key) == keyIndexMap.end()) {   // if don't have key
            keyIndexMap.emplace(key, size);  // we can also insert({key, size}) instead
            indexKeyMap.emplace(size, key);
            size++;
        }
     }

     void deleteKey(std::string key) {
        if (keyIndexMap.find(key) != keyIndexMap.end()) {  // if we have key
            int deleteIndex = keyIndexMap.at(key);  // find we we want to delete the index
            int lastIndex = --size;  // last index
            std::string lastKey = indexKeyMap.at(lastIndex);    // find the last key
            keyIndexMap.erase(key);
            keyIndexMap.erase(lastKey);
            indexKeyMap.erase(deleteIndex);
            indexKeyMap.erase(lastIndex);
            keyIndexMap.emplace(lastKey, deleteIndex);
            indexKeyMap.emplace(deleteIndex, lastKey);
        }
     }

     std::string getRandomKey() {
        int random = rand() % size;     // get [0, size-1]
        return indexKeyMap.at(random);  // we can also use indexKeyMap[random] instead
     }
};
int main()
{
    RandomPool randomPool;
    randomPool.insertKey("A");
    randomPool.insertKey("B");
    randomPool.insertKey("C");
    std::cout << "===================Insert key===================" << std::endl;
    std::cout << "keyIndexMap: " << std::endl;
    for (auto& it: randomPool.keyIndexMap) {
        std::cout << it.first << ": " << it.second << std::endl;
    }
    std::cout << "indexKeyMap: " << std::endl;
    for (auto it = randomPool.indexKeyMap.begin(); it != randomPool.indexKeyMap.end(); ++it) {
        std::cout << it->first << ": " << it->second << std::endl;
    }
    std::cout << "===================Random key===================" << std::endl;
    std::string randomKey1 = randomPool.getRandomKey();
    std::string randomKey2 = randomPool.getRandomKey();
    std::string randomKey3 = randomPool.getRandomKey();
    std::cout << "key1: " << randomKey1 << "\n" << "key2: "
              << randomKey2 << "\n" <<  "key3: " << randomKey3 << std::endl;

    std::cout << "===================Delete key===================" << std::endl;
    randomPool.deleteKey("A");  // delete "A"
    std::cout << "keyIndexMap: " << std::endl;
    for (auto& it: randomPool.keyIndexMap) {
        std::cout << it.first << ": " << it.second << std::endl;
    }
    std::cout << "indexKeyMap: " << std::endl;
    for (auto it = randomPool.indexKeyMap.begin(); it != randomPool.indexKeyMap.end(); ++it) {
        std::cout << it->first << ": " << it->second << std::endl;
    }
    std::cout << "==================After delete random key========" << std::endl;
    std::string randomKey11 = randomPool.getRandomKey();
    std::string randomKey22 = randomPool.getRandomKey();
    std::string randomKey33 = randomPool.getRandomKey();
    std::cout << "key1: " << randomKey11 << "\n" << "key2: "
              << randomKey22 << "\n" <<  "key3: " << randomKey33 << std::endl;
    return 0;
}

进一步的,我们为了不使用std::string作为我们的key,我们可以使用类模板。

#include <iostream>
#include <unordered_map>
#include <cstdlib>

template <typename T> class RandomPool {
 public:
     std::unordered_map<T, int> keyIndexMap;
     std::unordered_map<int, T> indexKeyMap;
     int size;
     RandomPool(): size(0) {}   // default constructor

     void insertKey(T key) {
        if (keyIndexMap.find(key) == keyIndexMap.end()) {   // if don't have key
            keyIndexMap.emplace(key, size);  // we can also insert({key, size}) instead
            indexKeyMap.emplace(size, key);
            size++;
        }
     }

     void deleteKey(T key) {
        if (keyIndexMap.find(key) != keyIndexMap.end()) {  // if we have key
            int deleteIndex = keyIndexMap.at(key);  // find we we want to delete the index
            int lastIndex = --size;  // last index
            T lastKey = indexKeyMap.at(lastIndex);    // find the last key
            keyIndexMap.erase(key);
            keyIndexMap.erase(lastKey);
            indexKeyMap.erase(deleteIndex);
            indexKeyMap.erase(lastIndex);
            keyIndexMap.emplace(lastKey, deleteIndex);
            indexKeyMap.emplace(deleteIndex, lastKey);
        }
     }

     T getRandomKey() {
        int random = rand() % size;     // get [0, size-1]
        return indexKeyMap.at(random);  // we can also use indexKeyMap[random] instead
     }
};
int main()
{
    RandomPool<std::string> randomPool;     // template
    randomPool.insertKey("A");
    randomPool.insertKey("B");
    randomPool.insertKey("C");
    std::cout << "===================Insert key===================" << std::endl;
    std::cout << "keyIndexMap: " << std::endl;
    for (auto& it: randomPool.keyIndexMap) {
        std::cout << it.first << ": " << it.second << std::endl;
    }
    std::cout << "indexKeyMap: " << std::endl;
    for (auto it = randomPool.indexKeyMap.begin(); it != randomPool.indexKeyMap.end(); ++it) {
        std::cout << it->first << ": " << it->second << std::endl;
    }
    std::cout << "===================Random key===================" << std::endl;
    std::string randomKey1 = randomPool.getRandomKey();
    std::string randomKey2 = randomPool.getRandomKey();
    std::string randomKey3 = randomPool.getRandomKey();
    std::cout << "key1: " << randomKey1 << "\n" << "key2: "
              << randomKey2 << "\n" <<  "key3: " << randomKey3 << std::endl;

    std::cout << "===================Delete key===================" << std::endl;
    randomPool.deleteKey("A");  // delete "A"
    std::cout << "keyIndexMap: " << std::endl;
    for (auto& it: randomPool.keyIndexMap) {
        std::cout << it.first << ": " << it.second << std::endl;
    }
    std::cout << "indexKeyMap: " << std::endl;
    for (auto it = randomPool.indexKeyMap.begin(); it != randomPool.indexKeyMap.end(); ++it) {
        std::cout << it->first << ": " << it->second << std::endl;
    }
    std::cout << "==================After delete random key========" << std::endl;
    std::string randomKey11 = randomPool.getRandomKey();
    std::string randomKey22 = randomPool.getRandomKey();
    std::string randomKey33 = randomPool.getRandomKey();
    std::cout << "key1: " << randomKey11 << "\n" << "key2: "
              << randomKey22 << "\n" <<  "key3: " << randomKey33 << std::endl;
    return 0;
}

其实改的不是很多了。
在这里插入图片描述

5. 参考文献

  1. 设计RandomPool结构
  2. 设计RandomPool结构
  3. 哈希表】实现结构RandomPool,用O(1)时间完成插入、删除和随机返回的功能

上面的代码insert和delete函数还可以进行优化,比如insert函数假如已经存在某个key了,我们直接return。对于delete函数如果不存在某个key,我们也可以直接return,或者抛出异常。另一方面为了下面的显示我们都是将成员变量设置为public了,但实际中最好是private属性,然后通过自定义get函数获取这些成员变量,我这里为了后面打印和方面,直接全部设置为public了。大家可以进一步优化代码结构。

6. 扩展

再看左神的视频时候,也看到了布隆过滤器和一致性哈希,感觉也是一些有意思的技术,这里也就不详细介绍了,只是列举一些比较好的参考文献。也算是以后面试的时候可以找到吧。

  1. 布隆过滤器(Bloom Filter)的原理和实现
  2. 一致性哈希算法的理解与实践
  3. 白话解析:一致性哈希算法
  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值