- insert(key):将某个key加入到该结构,做到不重复加入。
- delete(key):将原本在结构中的某个key移除。
- getRandom(): 等概率随机返回结构中的任何一个key。
【要求】 Insert、delete和getRandom方法的时间复杂度都是O(1)
3.2 解题思路
具体操作:
- 针对insert我们可以实现两个map同步操作,一个插入(key, index),另外一个插入(index, key),然后使用size计数即可,保持同步。
- 针对getRandom,虽然Hash表返回的是近似等概率的,但是不是严格等概率的,所有我们利用随机数从(index, key)中得到一个key。
- 针对delete操作,我们确实可以直接在(key, index)进行操作,但是这样我们在使用getRandom函数之后它会产生空洞了,所以一种思路就是我们可以借助最后一行(key,index)进行赋值给需要删除的key,这样就可以消除空洞。
3.3 代码实现
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<unordered_map>
#include<cstdlib>
using namespace std;
class RandomPool {
public:
unordered_map<string, int> keyIndexMap;
unordered_map<int, string> indexKeyMap;
int size;
RandomPool() : size(0) {} // default constructor
void insertKey(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(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
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);
}
}
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");
cout << "===================Insert key===================" << endl;
cout << "keyIndexMap: " << endl;
for (auto& it : randomPool.keyIndexMap) {
cout << it.first << ": " << it.second << endl;
}
cout << "indexKeyMap: " << endl;
for (auto it = randomPool.indexKeyMap.begin(); it != randomPool.indexKeyMap.end(); ++it) {
cout << it->first << ": " << it->second << endl;
}
cout << "===================Random key===================" << endl;
string randomKey1 = randomPool.getRandomKey();
string randomKey2 = randomPool.getRandomKey();
string randomKey3 = randomPool.getRandomKey();
cout << "key1: " << randomKey1 << "\n" << "key2: " << randomKey2 << "\n" << "key3: " << randomKey3 << endl;
cout << "===================Delete key===================" << endl;
randomPool.deleteKey("A"); // delete "A"
cout << "keyIndexMap: " << endl;
for (auto& it : randomPool.keyIndexMap) {
cout << it.first << ": " << it.second << endl;
}
cout << "indexKeyMap: " << endl;
for (auto it = randomPool.indexKeyMap.begin(); it != randomPool.indexKeyMap.end(); ++it) {
cout << it->first << ": " << it->second << endl;
}
cout << "==================After delete random key========" << endl;
string randomKey11 = randomPool.getRandomKey();
string randomKey22 = randomPool.getRandomKey();
string randomKey33 = randomPool.getRandomKey();
cout << "key1: " << randomKey11 << "\n" << "key2: " << randomKey22 << "\n" << "key3: " << randomKey33 << endl;
return 0;
}
四、布隆过滤器
4.1 问题介绍
布隆过滤器(Bloom Filter)实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。
优点:
- 可以高效地进行查询,可以用来告诉你“某样东西一定不存在或者可能存在”
- 可以高效的进行插入(但是不能删除)
- 相比于传统的List、Set、Map等数据结构,它占用空间更少,因为其本身并不存储任何数据(重点)
- 适用于海量数据的查找(亿量级)
缺点:
- 不能提供删除操作
- 存在失误概率:将白名单中的内容误认为是黑名单中的(反之则不可能失误)。
例如:网络爬虫程序,为了不去爬相同的URL页面,需要记录下所有爬过的URL(极大数据量),在爬一个新的网页时,判断是否已经爬过。
4.2 解题思路
- 定义一个m个空间大小的哈希表(0~m-1)。
- 将每个URL分别通过K个不同的哈希函数,并除以m取模,将得到的所有值都在哈希表上表示出来。(例如得到1,2,4,6……那么哈希表的1,2,4,6……空间上都涂黑。如果已经涂黑了则不进行操作)。
- 当查验一个URL是否在表上时,先将该URL通过K个哈希函数,并除以m取模,如果得到的所有值都在哈希表上被涂黑了,那么这个URL在表上。(例如得到1,2,4,6……但哈希表6号空间未被涂黑,说明此URL不在表上)。
重点在于如何确定m和K的值。
- (其中n为样本量,P为失误率)。
- (其中n为样本量,m为空间)。
- (其中P为实际失误率 )。
4.3 代码实现
#include <iostream>
#include <unordered_map>
#include <string>
#include <sys/time.h>
#include <utility>
#include <iomanip>
#define MAP_ITEMS 100000
using namespace std;
int main()
{
unordered_map<string, bool> unordermp;
timeval startTime, endTime;
//1.插入MAP_ITEMS个元素到map中
gettimeofday(&startTime, NULL);
std::string key = "https://blog.csdn.net/qq_41453285";
for(int i = 0; i < MAP_ITEMS; ++i){
string sub_key = to_string(i);
unordermp.insert(std::make_pair(key + sub_key, 1));
}
gettimeofday(&endTime, NULL);
long insert_time = (endTime.tv_sec - startTime.tv_sec)*1000 + (endTime.tv_usec-startTime.tv_usec)/1000;
//2.在map中查找一个元素
gettimeofday(&startTime, NULL);
if( unordermp.find(key + "10000") == unordermp.end())
std::cout << "not found!" << std::endl;
gettimeofday(&endTime, NULL);
long find_time = endTime.tv_usec - startTime.tv_usec;
//3.估算当前key的平均大小
double key_size = key.size() + to_string(MAP_ITEMS).size()/2;
//4.打印相关信息
std::cout << "Number of members " << "key size " << "insert time(ms) " << "find time(us) " << std::endl;
std::cout << left << setw(19) << MAP_ITEMS;
std::cout << left << setw(10) << key_size;
std::cout << left << setw(17) << insert_time;
std::cout << left << setw(15) << find_time << std::endl;
}
五、一致性哈希原理
5.1 问题介绍
六、岛问题
6.1 问题介绍
6.2 解题思路
**1. 采用感染算法:**首先从第一行开始遍历二维数组,如果发现“1”,那么递归查找和它相连接的所有“1”,并将“1”改成“2”。当下次遍历到同一个岛时,程序会发现该岛已经被标记为“2”,从而避免重复计算。
时间复杂度:,遍历时每个位置都会被调用1次,同时每个位置最多可能被它上下左右的4个位置递归调用4次。
**2. 采用并查集:**若数据量很大,需要并行运算求解,就不太好做了,难点在于多CPU计算结果的合并.
合并时要考虑将边界有重合的岛合并,但问题在于我们不知道多个重合边是否属于同一个岛。
- 考虑如下情况:有一个大C字形岛屿,被一刀切成三块了,那么在合并的时候遇到两条边,但我们不知道到底有几个岛(要合并几次)。
- 应用并查集结构,将一个岛屿构造成一个并查集,遇到相同边的时候合并进同一个并查集中,这样避免了重复合并的问题.
6.3 代码实现
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<unordered_map>
#include<cstdlib>
using namespace std;
int m[100][100];
//1.感染算法实现
void infect(int i, int j, int N, int M)//可变参数只有位置i,j
{
//base case:当前位置是无效位置或者不是1,返回
if (i < 0 || i >= N || j < 0 || j >= M || m[i][j] != 1)
return;
m[i][j] = 2;
//递归感染和它连成一片的1
infect(i + 1, j, N, M);
infect(i - 1, j, N, M);
infect(i, j + 1, N, M);
infect(i, j - 1, N, M);
}
int countIslands(int N, int M)
{
if (m == NULL || m[0] == NULL)
return 0;
int res = 0;
for (int i = 0; i < N; i++)
{
for (int j = 0; j < M; j++)
{
if (m[i][j] == 1)
{
res++;
infect(i, j, N, M);
}
}
}
return res;
}
int main()
{
int N, M;
cin >> N >> M;
for (int i = 0; i < N; i++)
{
for (int j = 0; j < M; j++)
{
**自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。**
**深知大多数Linux运维工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!**
**因此收集整理了一份《2024年Linux运维全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。**
![img](https://img-blog.csdnimg.cn/img_convert/69f3cabe3f9df432d0b77469b0367465.png)
![img](https://img-blog.csdnimg.cn/img_convert/ec827ff073b349f1d607bb4596a05ff2.png)
![img](https://img-blog.csdnimg.cn/img_convert/371e91df40675565d5059e9f7e5e3c74.png)
![img](https://img-blog.csdnimg.cn/img_convert/3f0e0d4956ac2321a8cc4f923a3fb01e.png)
![img](https://img-blog.csdnimg.cn/img_convert/bbec8f6af9c56f729df0c98be5adaa2f.png)
**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Linux运维知识点,真正体系化!**
**由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新**
**如果你觉得这些内容对你有帮助,可以添加VX:vip1024b (备注Linux运维获取)**
![img](https://img-blog.csdnimg.cn/img_convert/3c63c3e4baa6f92528a2b8b5ee2d2f42.jpeg)
W-1712917406893)]
[外链图片转存中...(img-D7SCbmma-1712917406893)]
[外链图片转存中...(img-FeKKDFco-1712917406894)]
**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Linux运维知识点,真正体系化!**
**由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新**
**如果你觉得这些内容对你有帮助,可以添加VX:vip1024b (备注Linux运维获取)**
[外链图片转存中...(img-HKbkcHQZ-1712917406894)]