【C++】35:位图,布隆过滤器和海量数据处理(哈希扩展)

目录

一、位图

[1.1 位图相关面试题](#1.1 位图相关面试题)

[1.2 位图的设计和实现](#1.2 位图的设计和实现)

[1.2.1 位图的结构](#1.2.1 位图的结构)

[1.2.2 位图的接口](#1.2.2 位图的接口)

[1.2.3 测试](#1.2.3 测试)

[1.3 位图的代码](#1.3 位图的代码)

[1.3.1 bitset.h](#1.3.1 bitset.h)

[1.3.2 test.h](#1.3.2 test.h)

[1.4 C++中的位图bitset](#1.4 C++中的位图bitset)

[1.5 位图的优缺点](#1.5 位图的优缺点)

[1.6 位图相关考察题目](#1.6 位图相关考察题目)

二、布隆过滤器

[2.1 什么是布隆过滤器](#2.1 什么是布隆过滤器)

[2.2 布隆过滤器误判率推导](#2.2 布隆过滤器误判率推导)

[2.3 布隆过滤器代码实现](#2.3 布隆过滤器代码实现)

[2.3.1 bitset.h](#2.3.1 bitset.h)

[2.3.2 BloomFilter.h](#2.3.2 BloomFilter.h)

[2.3.3 test.cpp](#2.3.3 test.cpp)

[2.4 布隆过滤器删除问题](#2.4 布隆过滤器删除问题)

[2.5 布隆过滤器的应用](#2.5 布隆过滤器的应用)

三、海量数据处理

[3.1 10亿个整数里面求最大的前100个](#3.1 10亿个整数里面求最大的前100个)

[3.2 位图章节讲解的位图相关题目](#3.2 位图章节讲解的位图相关题目)

[3.3 给两个文件,分别有100亿个query,我们只有1G内存,如何找到两个文件交集?](#3.3 给两个文件,分别有100亿个query,我们只有1G内存,如何找到两个文件交集?)

[3.4 给一个超过100G大小的logfile,log中存着ip地址,设计算法找到出现次数最多的ip地址?查找出现次数前10的ip地址](#3.4 给一个超过100G大小的logfile,log中存着ip地址,设计算法找到出现次数最多的ip地址?查找出现次数前10的ip地址)


一、位图

1.1 位图相关面试题

给40亿个不重复的无符号整数,没排过序。给一个无符号整数,如何快速判断一个数是否在这40亿个数中。(本题为腾讯/百度等公司出过的一个面试题)

  1. 解题思路1:暴力遍历,时间复杂度O(N),太慢
  2. 解题思路2:排序+二分查找。时间复杂度消耗O(N*logN)+O(logN)

深入分析:解题思路2是否可行,我们先算算40亿个数据大概需要多少内存。

1G= 1024MB=1024*1024KB=1024*1024*1024Byte 约等于10亿多Byte

那么40亿个整数约等于16G,说明40亿个数是无法直接放到内存中的,只能放到硬盘文件中。而二分

查找只能对内存数组中的有序数据进行查找。

解题思路3:数据是否在给定的整形数据中,结果是在或者不在,刚好是两种状态,那么可以使用一个二进制比特位来代表数据是否存在的信息,如果二进制比特位为1,代表存在,为0代表不存在。那么我们设计一个用位表示数据是否存在的数据结构,这个数据结构就叫位图。

40亿个数据需要40亿个bit位来存储,就需要40亿bit/8=5亿B,差不多消耗内存500M左右

1.2 位图的设计和实现

位图本质是一个直接定址法的哈希表,每个整型值映射一个bit位,位图提供控制这个bit的相关接口。

1.2.1 位图的结构

位图的结构如下所示:

cpp 复制代码
using namespace std;
namespace zx
{
	template<size_t N>
	class bitset
	{
	public:
		bitset()
		{
			_bs.resize(N / 32 + 1, 0);
		}
	private:
		vector<int> _bs;
	};
}

我们需要40亿个bit位来标记40亿个数据在不在。实现中需要注意的是,C/C++没有对应位的类型,只能看int/char这样整形类型,我们再通过位运算去控制对应的比特位。所以我们vector中存储int来表示。

1:如何判断某一个数x在哪一个vector中的哪里呢?

答:i=×/32;j=x %32;计算出x映射的值在vector的第i个整形数据的第j位。

2:为什么vector要预留(N/32+1)个空间呢?

答:现在有N个数据,判断一个数据在不在这N个数据中,我们使用vector<int>来表示数据在不在,一个int里面有4个字节,就能表示32个数据在不在,所以就需要开N/32个位置,但是由于C++中的除法是向下取整的,例如需要判断60个数据,vector里面就需要开2个int,但是60/32=1,只预留了一个int,所以需要+1,综上,vector就需要开N/32+1个数据。

解决给40亿个不重复的无符号整数,查找一个数据的问题,我们要给位图开2^32个位,注意不能开40亿个位,因为映射是按大小映射的,我们要按数据大小范围开空间,范围是是0-2^32-1,所以需要开2^32个位。然后从文件中依次读取每个set到位图中,然后就可以快速判断一个值是否在这40亿个数中了。

1.2.2 位图的接口

位图中主要有三个接口,将x对应的位标记成1,将x对应的位标记成0,测试x在不在位图中。如下所示:

cpp 复制代码
//x映射的位标记成1
void set(size_t x)
{
	size_t i = x / 32;
	size_t j = x % 32;
	_bs[i] |= (1 << j);
}

//x映射的位标记成0
void reset(size_t x)
{
	size_t i = x / 32;
	size_t j = x % 32;
	_bs[i] &= ~(1 << j);
}

//x映射的位是1返回真
//x映射的位是0返回假
bool test(size_t x)
{
	size_t i = x / 32;
	size_t j = x % 32;
	return _bs[i] & (1 << j);
}

1.2.3 测试

cpp 复制代码
#include"bitset.h"

int main()
{
	zx::bitset<100> bs1;
	bs1.set(50);
	bs1.set(30);
	bs1.set(90);
	for (size_t i = 0; i < 100; i++)
	{
		if (bs1.test(i))
		{
			cout << i << "->" << "在" << endl;
		}
	}
	bs1.reset(90);
	bs1.set(91);
	cout << endl << endl;
	for (size_t i = 0; i < 100; i++)
	{
		if (bs1.test(i))
		{
			cout << i << "->" << "在" << endl;
		}

	}
	return 0;
}

运行结果如下:

如何开42亿个比特位的空间,如下所示:

zx::bitset<0xffffffff> bs2;

zx::bitset<-1> bs3;

zx::bitset<UINT_MAX> bs4;

1.3 位图的代码

1.3.1 bitset.h

cpp 复制代码
#pragma once
#include<vector>
#include<iostream>

using namespace std;
namespace zx
{
	template<size_t N>
	class bitset
	{
	public:
		bitset()
		{
			_bs.resize(N / 32 + 1);
		}

		//x映射的位标记成1
		void set(size_t x)
		{
			size_t i = x / 32;
			size_t j = x % 32;
			_bs[i] |= (1 << j);
		}

		//x映射的位标记成0
		void reset(size_t x)
		{
			size_t i = x / 32;
			size_t j = x % 32;
			_bs[i] &= ~(1 << j);
		}

		//x映射的位是1返回真
		//x映射的位是0返回假
		bool test(size_t x)
		{
			size_t i = x / 32;
			size_t j = x % 32;
			return _bs[i] & (1 << j);
		}
	private:
		vector<int> _bs;
	};
}

1.3.2 test.h

cpp 复制代码
#include"bitset.h"

int main()
{
	zx::bitset<100> bs1;
	bs1.set(50);
	bs1.set(30);
	bs1.set(90);
	for (size_t i = 0; i < 100; i++)
	{
		if (bs1.test(i))
		{
			cout << i << "->" << "在" << endl;
		}
	}
	bs1.reset(90);
	bs1.set(91);
	cout << endl << endl;
	for (size_t i = 0; i < 100; i++)
	{
		if (bs1.test(i))
		{
			cout << i << "->" << "在" << endl;
		}

	}
	return 0;
}

1.4 C++中的位图bitset

bitset - C++ Reference

可以看到核心接口还是set/reset/和test,当然后面还实现了一些其他接口,如to_string将位图按位转成01字符串,再包括operator[]等支持像数组一样控制一个位的实现。

库里面的bitset有一个小bug,我们使用库里面的bitset是开不出0xffffffff个位的,为什么我们模拟实现的bitset是可以开出来的呢?

这是因为我们模拟实现的bitset里面是一个vector,vector里面的数据是在堆里面的,不再栈上,而库里面的bitset里面可能是一个静态数组,是在栈上的,我们使用sizeof来看一下空间大小。

我们可以看到:我们模拟实现的bitset无论判断多少个数在不在,大小始终是32B,数据在堆里面。而库里面的bitset数据越多,占用的空间越大,所以库里面的bitset中的数据在对象里面,如果我们想要开0xffffffff个bit位,栈上的空间就会溢出,就会报错,那使用库里面的bitset如何开0xffffffff个bit位呢?

我们可以这样写:std::bitset<0xffffffff>* ptr = new std::bitset<0xffffffff>();

在堆上new一个bitset<0xffffffff>对象,这样所有的数据就在堆里面了。

1.5 位图的优缺点

优点:增删查改快,节省空间
缺点:只适用于整形,不适应于其他类型

1.6 位图相关考察题目

1:给定100亿个整数,设计算法找到只出现一次的整数?

虽然是100亿个数,但是还是按范围开空间,所以还是开2^32个位,跟前面的题目是一样的

2:给两个文件,分别有100亿个整数,我们只有1G内存,如何找到两个文件交集?

把数据读出来,分别放到两个位图,依次遍历,同时在两个位图的值就是交集

3:一个文件有100亿个整数,1G内存,设计算法找到出现次数不超过2次的所有整数

之前我们是标记在不在,只需要一个位即可,这里要统计出现次数不超过2次的,可以每个值用两个位标记即可,00代表出现0次,01代表出现1次,10代表出现2次,11代表出现2次以上。最后统计出所有01和10标记的值即可。

代码如下所示:

bitset.h:

cpp 复制代码
#pragma once
#include<vector>
#include<iostream>

using namespace std;
namespace zx
{
	template<size_t N>
	class bitset
	{
	public:
		bitset()
		{
			_bs.resize(N / 32 + 1);
		}

		//x映射的位标记成1
		void set(size_t x)
		{
			size_t i = x / 32;
			size_t j = x % 32;
			_bs[i] |= (1 << j);
		}

		//x映射的位标记成0
		void reset(size_t x)
		{
			size_t i = x / 32;
			size_t j = x % 32;
			_bs[i] &= ~(1 << j);
		}

		//x映射的位是1返回真
		//x映射的位是0返回假
		bool test(size_t x)
		{
			size_t i = x / 32;
			size_t j = x % 32;
			return _bs[i] & (1 << j);
		}
	private:
		vector<int> _bs;
	};

	template<size_t N>
	class twobitset
	{
	public:
		void set(size_t x)
		{
			bool flag1 = _bs1.test(x);
			bool flag2 = _bs2.test(x);
			if (!flag1 && !flag2) //00->01
			{
				_bs2.set(x);
			}
			else if (!flag1 && flag2)  //01->10
			{
				_bs1.set(x);
				_bs2.reset(x);
			}  
			else if(flag1 && !flag2)  //10>11
			{
				_bs2.set(x);
			}
		}

		// 返回0 出现0次数
		// 返回1 出现1次数
		// 返回2 出现2次数
		// 返回3 出现2次及以上
		size_t get_count(size_t x)
		{
			bool flag1 = _bs1.test(x);
			bool flag2 = _bs2.test(x);
			if (!flag1 && !flag2)
			{
				return 0;
			}
			else if (!flag1 && flag2)
			{
				return 1;
			}
			else if (flag1 && !flag2)
			{
				return 2;
			}
			else
			{
				return 3;
			}
		}
	private:
		bitset<N> _bs1;
		bitset<N> _bs2;
	};
}

测试代码:test.cpp

cpp 复制代码
#include"bitset.h"

int main()
{
	zx::twobitset<100> tbs;
	int a[] = { 5,7,9,2,5,99,5,5,7,5,3,9,2,55,1,5,6,6,6,6,7,9 };
	for (auto e : a)
	{
		tbs.set(e);
	}
	for (size_t i = 0; i < 100; ++i)
	{
		//cout << i << "->" << tbs.get_count(i) << endl;
		if (tbs.get_count(i) == 1 || tbs.get_count(i) == 2)
		{
			cout << i << endl;
		}
	}
	return 0;
}

二、布隆过滤器

2.1 什么是布隆过滤器

  • 有一些场景下面,有大量数据需要判断是否存在,而这些数据不是整形,那么位图就不能使用了,使用红黑树/哈希表等内存空间可能不够。这些场景就需要布隆过滤器来解决。
  • 布隆过滤器是由布隆(Burton Howard Bloom)在1970年提出的一种紧凑型的、比较巧妙的概率型数据结构,特点是高效地插入和查询,可以用来告诉你"某样东西一定不存在或者可能存在",它是用多个哈希函数,将一个数据映射到位图结构中。此种方式不仅可以提升查询效率,也可以节省大量的内存空间。
  • 布隆过滤器的思路就是把key先映射转成哈希整型值,再映射一个位,如果只映射一个位的话,冲突率会比较多,所以可以通过多个哈希函数映射多个位,降低冲突率。
  • 布隆过滤器这里跟哈希表不一样,它无法解决哈希冲突的,因为他压根就不存储这个值,只标记映射的位。它的思路是尽可能降低哈希冲突。判断一个值key在是不准确的,判断一个值key不在是准确的。

2.2 布隆过滤器误判率推导


结论:
布隆过滤器的误判率为:


布隆过滤器(Bloom Filter)- 原理、实现和推导_布隆过滤器原理-CSDN博客

2.3 布隆过滤器代码实现

2.3.1 bitset.h

cpp 复制代码
#pragma once
#include<vector>
#include<iostream>

using namespace std;
namespace zx
{
	template<size_t N>
	class bitset
	{
	public:
		bitset()
		{
			_bs.resize(N / 32 + 1);
		}

		//x映射的位标记成1
		void set(size_t x)
		{
			size_t i = x / 32;
			size_t j = x % 32;
			_bs[i] |= (1 << j);
		}

		//x映射的位标记成0
		void reset(size_t x)
		{
			size_t i = x / 32;
			size_t j = x % 32;
			_bs[i] &= ~(1 << j);
		}

		//x映射的位是1返回真
		//x映射的位是0返回假
		bool test(size_t x)
		{
			size_t i = x / 32;
			size_t j = x % 32;
			return _bs[i] & (1 << j);
		}
	private:
		vector<int> _bs;
	};
}

2.3.2 BloomFilter.h

cpp 复制代码
#pragma once
#include<string>
#include"bitset.h"

struct HashFuncBKDR
{
	size_t operator()(const string& s)
	{
		size_t hash = 0;
		for (auto ch : s)
		{
			hash *= 31;
			hash += ch;
		}
		return hash;
	}
};

struct HashFuncAP
{
	size_t operator()(const string& s)
	{
		size_t hash = 0;
		for (size_t i = 0; i < s.size(); i++)
		{
			if ((i & 1) == 0) 
			{
				hash ^= ((hash << 7) ^ (s[i]) ^ (hash >> 3));
			}
			else
			{
				hash ^= (~((hash << 11) ^ (s[i]) ^ (hash >>
					5)));
			}
		}
		return hash;
	}
};

struct HashFuncDJB
{
	size_t operator()(const string& s)
	{
		size_t hash = 5381;
		for (auto ch : s)
		{
			hash = hash * 33 ^ ch;
		}
		return hash;
	}
};
template<size_t N,
	size_t X=5,
	class K=string,
	class Hash1= HashFuncBKDR,
	class Hash2= HashFuncAP,
	class Hash3= HashFuncDJB>
class BloomFilter
{
public:
	void set(const K& key)
	{
		size_t hash1 = Hash1()(key) % M;
		size_t hash2 = Hash2()(key) % M;
		size_t hash3 = Hash3()(key) % M;
		_bs.set(hash1);
		_bs.set(hash2);
		_bs.set(hash3);
	}
	bool test(const K& key)
	{
		size_t hash1 = Hash1()(key) % M;
		if (!_bs.test(hash1))
		{
			return false;
		}
		size_t hash2 = Hash2()(key) % M;
		if (!_bs.test(hash2))
		{
			return false;
		}
		size_t hash3 = Hash3()(key) % M;
		if (!_bs.test(hash2))
		{
			return false;
		}

		return true; // 存在误判(有可能3个位都是跟别人冲突的,所以误判)
	}

	// 获取公式计算出的误判率
	double getFalseProbability()
	{
		double p = pow((1.0 - pow(2.71, -3.0 / X)), 3.0);
		return p;
	}
private:
	static const size_t M = N * X;
	// 我们实现位图是用vector,也就是堆上开的空间
	zx::bitset<M> _bs;

	//std::bitset<M>_bs;
	//vs下std的位图是开的静态数组,M太大会存在崩的问题
	//解决方案就是bitset对象整体new一下,空间就开到堆上了
	//std::bitset<M>*_bs= new std::bitset<M>;
};

2.3.3 test.cpp

cpp 复制代码
#include<time.h>
#include"BloomFilter.h"

void TestBloomFilter1()
{
	string strs[] = { "百度","字节","腾讯" };
	BloomFilter<10> bf;
	for (auto& s : strs)
	{
		bf.set(s);
	}
	for (auto& s : strs)
	{
		cout << bf.test(s) << endl;
	}
	cout << "-------------" << endl;
	for (auto& s : strs)
	{
		cout << bf.test(s + 'a') << endl;
	}
	cout << "-------------" << endl;
	cout << bf.test("摆渡") << endl;
	cout << "-------------" << endl;
	cout << bf.test("百渡") << endl;
}

void TestBloomFilter2()
{
	srand(time(0));
	const size_t N = 1000000;
	//BloomFilter<N> bf;
	//BloomFilter<N, 3> bf;
	BloomFilter<N, 10> bf;
	std::vector<std::string> v1;
	std::string url = "https://www.cnblogs.com/-clq / archive / 2012 / 05 / 31 / 2528153.html";
	//std::string url = "https://www.baidu.com/s?ie=utf-8 & f = 8 & rsv_bp = 1 & rsv_idx = 1 & tn = 65081411_1_oem_dg & wd = ln2 & fenlei = 256 & rsv_pq = 0x8d9962630072789f & rsv_t = ceda1rulSdBxDLjBdX4484KaopD % 2BzBFgV1uZn4271RV0PonRFJm0i5xAJ % 2FDo & rqlang = en & rsv_enter = 1 & rsv_dl = ib & rsv_sug3 = 3 & rsv_sug1 = 2 & rsv_sug7 = 100 & rsv_sug2 =0 & rsv_btype = i & inputT = 330 & rsv_sug4 = 2535";
	//std::string url = "猪八戒";
	for (size_t i = 0; i < N; ++i)
	{
		v1.push_back(url + std::to_string(i));
	}
	for (auto& str : v1)
	{
		bf.set(str);
	}
	// v2跟v1是相似字符串集(前缀一样),但是后缀不一样
	v1.clear();
	for (size_t i = 0; i < N; ++i)
	{
		std::string urlstr = url;
		urlstr += std::to_string(9999999 + i);
		v1.push_back(urlstr);
	}
	size_t n2 = 0;
	for (auto& str : v1)
	{
		if (bf.test(str)) // 误判
		{
			++n2;
		}
	}
	cout << "相似字符串误判率:" << (double)n2 / (double)N << endl;
	// 不相似字符串集,前缀后缀都不一样
	v1.clear();
	for (size_t i = 0; i < N; ++i)
	{
		//string url = "zhihu.com";
		string url = "孙悟空";
		url += std::to_string(i + rand());
		v1.push_back(url);
	}
	size_t n3 = 0;
	for (auto& str : v1)
	{
		if (bf.test(str))
		{
			++n3;
		}
	}
	cout << "不相似字符串误判率:" << (double)n3 / (double)N << endl;
	cout << "公式计算出的误判率:" << bf.getFalseProbability() << endl;
}
int main()
{
	//TestBloomFilter1();
	TestBloomFilter2();
	return 0;
}

2.4 布隆过滤器删除问题

  • 布隆过滤器默认是不支持删除的(删除一个值可能会影响另一个值),因为比如"猪八戒"和"孙悟空"都映射在布隆过滤器中,他们映射的位有一个位是共同映射的(冲突的),如果我们把孙悟空删掉,那么再去查找"猪八戒"会查找不到,因为那么"猪八戒"间接被删掉了。
  • 解决方案:可以考虑计数标记的方式,一个位置用多个位标记,记录映射这个位的计数值,删除时,仅仅减减计数,那么就可以某种程度支持删除。但是这个方案也有缺陷,如果一个值不在布隆过滤器中,我们去删除,减减了映射位的计数,那么会影响已存在的值,也就是说,一个确定存在的值,可能会变成不存在,这里就很坑。当然也有人提出,我们可以考虑计数方式支持删除,但是定期重建一下布隆过滤器,这样也是一种思路。

2.5 布隆过滤器的应用

首先我们分析一下布隆过滤器的优缺点:
优点:效率高,节省空间,相比位图,可以适用于各种类型的标记过滤
缺点:存在误判(在是不准确的,不在是准确的),不好支持删除

布隆过滤器在实际中的一些应用:

1:爬虫系统中URL去重:

在爬虫系统中,为了避免重复爬取相同的URL,可以使用布隆过滤器来进行URL去重。爬取到的URL可以通过布隆过滤器进行判断,已经存在的URL则可以直接忽略,避免重复的网络请求和数据处理。

2:垃圾邮件过滤:

在垃圾邮件过滤系统中,布隆过滤器可以用来判断邮件是否是垃圾邮件。系统可以将已知的垃圾邮件的特征信息存储在布隆过滤器中,当新的邮件到达时,可以通过布隆过滤器快速判断是否为垃圾邮件,从而提高过滤的效率。

3:预防缓存穿透:

在分布式缓存系统中,布隆过滤器可以用来解决缓存穿透的问题。缓存穿透是指恶意用户请求一个不存在的数据,导致请求直接访问数据库,造成数据库压力过大。布隆过滤器可以先判断请求的数据是否存在于布隆过滤器中,如果不存在,直接返回不存在,避免对数据库的无效查询。

4:对数据库查询提效:

在数据库中,布隆过滤器可以用来加速查询操作。例如:一个app要快速判断一个电话号码是否注册过,可以使用布隆过滤器来判断一个用户电话号码是否存在于表中,如果不存在,可以直接返回不存在,避免对数据库进行无用的查询操作。如果在,再去数据库查询进行二次确认。

三、海量数据处理

3.1 10亿个整数里面求最大的前100个

经典topk问题,⽤堆解决,用前100个数据建立一个小根堆,然后遍历后面的数据,如果当前元素大于堆顶元素,就入堆。

3.2 位图章节讲解的位图相关题目

3.3 给两个文件,分别有100亿个query,我们只有1G内存,如何找到两个文件交集?

分析:假设平均每个query字符串50byte,100亿个query就是5000亿byte,约等于500G(1G约等于10亿多Byte)

哈希表/红黑树等数据结构肯定是无能为力的。

解决方案1:

这个首先可以用布隆过滤器解决,一个文件中的query放进布隆过滤器,另一个文件依次查找,在的就是交集,问题就是找到交集不够准确,因为在的值可能是误判的,但是交集一定被找到了。

解决方案2:

  • 哈希切分,首先内存的访问速度远大于硬盘,大文件放到内存搞不定,那么我们可以考虑切分为小文件,再放进内存处理。
  • 但是不要平均切分,因为平均切分以后,每个小文件都需要依次暴力处理,效率还是太低了。
  • 可以利用哈希切分,依次读取文件中query,i=HashFunc(query)%N,N为准备切分多少分小文件,N取决于切成多少份,内存能放下,query放进第i号小文件,这样A和B中相同的query算出的hash值i是一样的,相同的query就进入的编号相同的小文件就可以编号相同的文件直接找交集,不用交叉找,效率就提升了。
  • 本质是相同的query在哈希切分过程中,一定进入的同一个小文件Ai和Bi,不可能出现A中的query进入Ai,但是B中的相同query进入了和Bj的情况,所以对Ai和Bi进行求交集即可,不需要Ai和Bj求交集。(本段表述中i和j是不同的整数)
  • 哈希切分的问题就是每个小文件不是均匀切分的,可能会导致某个小文件很大内存放不下。我们细细分析一下某个小文件很大有两种情况:
    • 1.这个小文件中大部分是同一个query。
    • 2.这个小文件是有很多的不同query构成,本质是这些query冲突了。
    • 针对情况1,其实放到内存的set中是可以放下的,因为set是去重的。
    • 针对情况2,需要换个哈希函数继续二次哈希切分。所以本体我们遇到大于1G小文件,可以继续读到set中找交集,若set insert时抛出了异常(set插入数据抛异常只可能是申请内存失败了,不会有其他情况),那么就说明内存放不下是情况2,换个哈希函数进行二次哈希切分后再对应找交集。

3.4 给一个超过100G大小的logfile,log中存着ip地址,设计算法找到出现次数最多的ip地址?查找出现次数前10的ip地址

本题的思路跟上题完全类似,依次读取文件A中query,i=HashFunc(query)%500,query放进Ai号小文件,然后依次用map<string,int>对每个Ai小文件统计ip次数,同时求出现次数最多的ip或者topk ip。本质是相同的ip在哈希切分过程中,一定进入的同一个小文件Ai,不可能出现同一个ip进入Ai和Aj的情况,所以对Ai进行统计次数就是准确的ip次数。

相关推荐
明志数科10 小时前
仿真数据与真实数据:机器人训练的数据策略选择
人工智能·算法·机器学习
weyyhdke10 小时前
2026电源与MCU控制设计实战:用Gemini3.5镜像站免费优化开关电源环路与电机FOC算法硬核教程
单片机·嵌入式硬件·算法
z2005093010 小时前
今日算法(组合问题III)(回溯的使用)
java·算法·leetcode
2401_8896269210 小时前
Java语法进阶篇
算法
Sinsa_SI10 小时前
2026算法应用主题赛初赛-小学4-6组(Python)试卷(含答案+详细解析)
开发语言·python·算法
Lyon1985052810 小时前
从临床医疗说起:当一种科学理论走到边界的时候
人工智能·深度学习·算法·deepseek·ai伦理
美团技术团队10 小时前
从高拟真到真可用,LongCat-Video-Avatar 1.5 正式开源
人工智能·算法
夏日听雨眠11 小时前
数据结构(BF算法 )
数据结构·算法·排序算法
夏日听雨眠11 小时前
数据结构(KMP算法)
数据结构·算法