unordered_map和unordered_set底层使用了哈希
以不经过任何比较,一次直接从表中得到要搜索的元素。 如果构造一种存储结构,通过某种函数(hashFunc)使元素的存储位置与它的关键码之间能够建立一一映射的关系,那么在查找时通过该函数可以很快找到该元素。
当向该结构中:
插入元素:根据待插入元素的关键码,以此函数计算出该元素的存储位置并按此位置进行存放;
搜索元素:对元素的关键码进行同样的计算,把求得的函数值当做元素的存储位置,在结构中按此位置取元素比较,若关键码相等,则搜索成功
该方式即为哈希(散列)方法,哈希方法中使用的转换函数称为哈希(散列)函数,构造出来的结构称为哈希表 (Hash Table)(或者称散列表)
哈希函数设计原则:
常见哈希方法:
不同关键字通过相同哈希函数计算出相同的哈希地址,该种现象称为哈希冲突或哈希碰撞。
把具有不同关键码而具有相同哈希地址的数据元素称为“同义词”。
闭散列和开散列
闭散列又称为开放地址法,当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还有空位置,那么可以把key存放到冲突位置中的“下一个” 空位置中去。寻找下一个位置的方法:
线性探测:从发生冲突的位置开始,依次向后探测,直到寻找到下一个空位置为止。
采用闭散列处理哈希冲突时,不能随便物理删除哈希表中已有的元素,若直接删除元素会影响其他 元素的搜索。比如删除元素4,如果直接删除掉,44查找起来可能会受影响。因此线性探测采用标 记的伪删除法来删除一个元素。设置一个状态:EXIT,EMPTY,DELETE。
增容问题:散列表的载荷因子a=填入表中的元素个数/散列表的长度,一般限制在0.7~0.8以下。
线性探测优点:实现非常简单。
线性探测缺点:一旦发生哈希冲突,所有的冲突连在一起,容易产生数据“堆积”,即:不同关键码占据 了可利用的空位置,使得寻找某关键码的位置需要许多次比较,导致搜索效率降低。
二次探测::H_i = (H_0 + i^2 )% m, 或者:H_i = (H_0 - i^2)% m。其中:i = 1,2,3…, H_0是通过散列函数 Hash(x)对元素的关键码 key 进行计算得到的位置,m是表的大小。
当表的长度为质数且表装载因子a不超过0.5时,新的表项一定能够插入,而且任何一个位置 都不会被探查两次。插入时必须确保表的装载因子a不超过0.5,如果超出必须考虑增容。二次探测的缺陷是空间利用率比较低。
开散列法又叫链地址法(开链法),首先对关键码集合用散列函数计算散列地址,具有相同地址的关键码归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链接起来,各链表的头结点存储在哈希表中。
开散列增容:开散列最好的情况是:每个哈希桶中刚好挂一个节点,再继续插入元素时,每一次都会发 生哈希冲突,因此,在元素个数刚好等于桶的个数时,可以给哈希表增容。
应用链地址法处理溢出,需要增设链接指针,似乎增加了存储开销。事实上: 由于开地址法必须保持大 量的空闲空间以确保搜索效率,如二次探查法要求装载因子a <= 0.7,而表项所占空间又比指针大的 多,所以使用链地址法反而比开地址法节省存储空间。
所谓位图,就是用每一位来存放某种状态,适用于大规模数据,但数据状态又不是很多的情况。通常是用来判断某个数据存不存在的。
是将一个元素用多个哈希函数映射到一个位图中,分别计算每个哈希值对应的比特位置存储的是否为零,只要有一个为零, 代表该元素一定不在哈希表中,否则可能在哈希表中.
布隆过滤器的特点是高效地插入和查询,可以用来告诉你 “某样东西一定不存在或者可能存在”,它是用多个哈希函 数,将一个数据映射到位图结构中。此种方式不仅可以提升查询效率,也可以节省大量的内存空间。
删除元素的方法:将布隆过滤器中的每个比特位扩展成一个小的计数器,插入元素时给k个计数器(k个哈 希函数计算出的哈希地址)加一,删除元素时,给k个计数器减一,通过多占用几倍存储空间的代价来增加删 除操作。
缺陷:
1.给一个超过100G大小的log file, log中存着IP地址, 设计算法找到出现次数最多的IP地址? 与上题条件相同, 如何找到top K的IP?如何直接用Linux系统命令实现
将100G文件分成1000份,将每个IP地址映射到相应文件中:file_id = hash(ip) % 1000
在每个文件中分别求出最高频的IP,再合并 Hash分桶法
Linux命令,假设top 10:
sort log_file | uniq -c | sort -nr | head -10
//sort1:将IP进行排序,使得相同的IP相邻;
//uniq -c: 在输出行前面加上每行在输入文件的出现次数
//sort2:-n:根据数据大小排序,-r:逆序
//head -10:显示文件的前10行。
1.给定100亿个整数,设计算法找到只出现一次的整数?
100亿*4字节 = 400亿字节 = 40G
解法一:哈希分桶法
将100亿整数分为100份文件,将每个整数映射进对应文件,取每个文件中只出现了一次的整数,最后汇总。
解法二:位图
使用hash将100亿个整数映射到1000个文件中,在每个文件中使用位图(bitmap),用两个位表示出现的次数,00没有出现,01出现一次,10出现两次或两次以上,11舍弃。最后合并每个文件中出现一次的数。
2.给两个文件,分别有100亿个整数,我们只有1G内存,如何找到两个文件交集?
两个文件A,B,hash(ip)%1024模出来是几,就放到哪个小文件,就有A0~A1023,B0~B1023个小文件,每个文件大概40M,使用的相同的hash函数,所以两个文件中相同的数会被放到文件下标相同的文件中;再A0跟B0找交集,A1023~B1023找交集,最后将结果汇总。
解法1:普通查找
将其中的一个文件分为100个小文件,每一份占400M, 将每一小份轮流加到内存中,与第二个文件中的数据进行对比,找到交集。此种算 法时间复杂度为O(N*N)。
解法2:哈希切分
对两个文件分别进行哈希切分,将其分为100个小文件 ,index=key%100(index为文件下标)。
将两个文件中下标相同的小文件进行对比,找出其交集。将100个文件的交集汇总起来即为所给文件的文件交集 。此种算法时间复杂度为O(N)。
解法3:位图
我们知道,位图中的每一位就可代表一个整数的存在与否,将100亿整数分为100份文件,将第一个文件中的整数映射到位图中去拿第二个文件中的数字到第一个文件映射的位图中去对比,相同数字存在即为交集。此种算法时间复杂度为O(N)。
3.位图应用变形:1个文件有100亿个int,1G内存,设计算法找到出现次数不超过2次的所有整数
1.给两个文件,分别有100亿个query,我们只有1G内存,如何找到两个文件交集?分别给出精确算法和近似算法
精确算法:哈希切分
对两个文件分别进行哈希切分,使用相同的散列函数 (如 BKDRHash散列函数)将所有query转换为一个整数key ,再利用 index=key%1000就可将相同query分到同一个文件。(index为文件下标)
将两个文件中下标相同的小文件进行对比,找出其交集。
将100个文件的交集汇总起来即为所给文件的文件交集 。此种算法时间复杂度为O(N)。
近似算法:布隆过滤器
首先使用相同的散列函数(如 BKDRHash散列函数)将所有 query转换为一个整数key,
又因为布隆过滤器中的每 一位就可代表一个整数的存在 与否,而16G的整数用位图512M即可表示,
将第一个文件中的整数映射到位图中去,
拿第二个文件中的数字到第一个文件映射的位图中去对比,相同数字存在即为交集。
此种算法时间复杂度为O(N)。
布隆过滤器判断不存在是确定的,而存存在在可能导致误判,所以称近似算法。
2.如何扩展BloomFilter使得它支持删除元素的操作
通过多占用几倍存储空间的代价来增加删除操作。
1.给上千个文件,每个文件大小为1K—100M。给n个词,设计算法对每个词找到所有包含它的文件,你只有100K内存
对上千个文件生成1000个布隆过滤器,并将1000个布隆过滤器存入一个文件中,将内存分为两份,一份用来读取布隆过滤器中的词,一份用来读取文件, 直到每个布隆过滤器读完为止。
用一个文件info准备用来保存n个词和包含其的文件信息。
首先把n个词分成x份。对每一份用生成一个布隆过滤器(因为对n个词只生成一个布隆过滤器,内存可能不够用)。把生成的所有布隆过滤器存入外存的一个文件Filter中。
将内存分为两块缓冲区,一块用于每次读入一个 布隆过滤器,一个用于读文件(读文件这个缓冲区使用 相当于有界生产者消费者问题模型来实现同步),大文 件可以分为更小的文件,但需要存储大文件的标示信 息(如这个小文件是哪个大文件的)。
对读入的每一个单词用内存中的布隆过滤器来判 断是否包含这个值,如果不包含,从Filter文件中读 取下一个布隆过滤器到内存,直到包含或遍历完所有 布隆过滤器。如果包含,更新info 文件。直到处理完 所有数据。删除Filter文件。